2

Honestly, I have never really done this so I'm very much lost here. I have an app, presenting a UIPickerView with days: hours: minutes to the user.

I have to make sure that the minimum selected time is 10 minutes and maximum selected time is 7 days. If the user selects more than 7 days, the days and hours components should be reset back to 0. I also have to get the data and display it in a UILabel that's above the picker. Here's what I got in terms of code:

struct PollTime {
    let time: Int
    let description: String
}

struct PollModel {
    let days: [PollTime]
    let hours: [PollTime]
    let minutes: [PollTime]

    var currentTime: (day: PollTime, hour: PollTime, minute: PollTime)?

    init() {
        days = [Int](0...7).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "days" : "day") }
        hours = [Int](0...23).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "hours" : "hour") }
        minutes = [Int](0...59).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "min" : "min") }
    }

    func validateCurrenTime() -> Bool {
        guard let currentTime = currentTime else { return false }
        //if (//TODO: check to see if the currentTime falls within the specified time required.) {
           // return true
       // }
        return false
    }
}


var pollModel = PollModel()
extension PostPollVC : UIPickerViewDelegate,UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 3
    }
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        switch component {
        case 0:
            return pollModel.days.count
        case 1:
            return pollModel.hours.count
        case 2:
            return pollModel.minutes.count
        default:
            return 0
        }
    }

    func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
        return pickerView.frame.size.width / 3
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

        let currentDate = Date()
        var dateComponents = DateComponents()

        let days = pickerView.selectedRow(inComponent: 0)
        let hours = pickerView.selectedRow(inComponent: 1)
        let minutes = pickerView.selectedRow(inComponent: 2)

        pollModel.currentTime = (PollTime(time: days, description: ""), PollTime(time: hours, description: ""), PollTime(time: minutes, description: ""))

        if days != 0 && hours != 0 && minutes != 0 && !pollModel.validateCurrenTime() {

            let alert = UIAlertController(title: "Invalid Time", message: "You must select a time greater than 10 minutes, up to 7 days.", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default) { [weak self] action in
                //TODO: Reset the components of the picker to one less than or great than the invalid choices....
            })
            self.present(alert, animated: true, completion: nil)
        }

        dateComponents.day = days
        dateComponents.hour = hours
        dateComponents.minute = minutes

        let calendar = Calendar(identifier: .gregorian)
        let futureDate = calendar.date(byAdding: dateComponents, to: currentDate)

        print("The current date is: \(currentDate.description)")
        print("Your future date is: \(futureDate?.description ?? "")")

    }

    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
        let label = UILabel()
        label.textColor = Colors.mainBlueColor
        label.textAlignment = .center
        label.font = UIFont(name: Fonts.OpenSans_Bold, size: 14)

        switch component {
        case 0:
            label.text = "\(pollModel.days[row].time) \(pollModel.days[row].description)"
            return label
        case 1:
            label.text = "\(pollModel.hours[row].time) \(pollModel.hours[row].description)"
            return label
        case 2:
            label.text = "\(pollModel.minutes[row].time) \(pollModel.minutes[row].description)"
            return label
        default:
            return label
        }
    }
}
regina_fallangi
  • 2,080
  • 2
  • 18
  • 38
Dani
  • 3,427
  • 3
  • 28
  • 54

1 Answers1

2

Here is the working code. First important thing: Never forget to set the delegate and dataSource of your pickerView. I understood the absolute maximum was 7 days and not a minute more, but you can tweak the validation conditions as you'd like:

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

    @IBOutlet weak var pickerView: UIPickerView!
    var pollModel = PollModel()

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 3
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        pickerView.delegate = self
        pickerView.dataSource = self
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        switch component {
        case 0:
            return pollModel.days.count
        case 1:
            return pollModel.hours.count
        case 2:
            return pollModel.minutes.count
        default:
            return 0
        }
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

        let currentDate = Date()
        var dateComponents = DateComponents()

        let days = pickerView.selectedRow(inComponent: 0)
        let hours = pickerView.selectedRow(inComponent: 1)
        let minutes = pickerView.selectedRow(inComponent: 2)

        pollModel.currentTime = (PollTime(time: days, description: ""), PollTime(time: hours, description: ""), PollTime(time: minutes, description: ""))

        if !pollModel.validateCurrenTime() {

            let alert = UIAlertController(title: "Invalid Time", message: "You must select a time greater than 10 minutes, up to 7 days.", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default) { [weak self] action in
                [0,1,2].forEach { self?.pickerView.selectRow(0, inComponent: $0, animated: true) }
                pickerView.reloadAllComponents()
            })
            self.present(alert, animated: true, completion: nil)
        }

        dateComponents.day = days
        dateComponents.hour = hours
        dateComponents.minute = minutes

        let calendar = Calendar(identifier: .gregorian)
        let futureDate = calendar.date(byAdding: dateComponents, to: currentDate)

        print("The current date is: \(currentDate.description)")
        print("Your future date is: \(futureDate?.description ?? "")")

    }

    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
        let label = UILabel()
        label.textAlignment = .center

        switch component {
        case 0:
            label.text = "\(pollModel.days[row].time) \(pollModel.days[row].description)"
            return label
        case 1:
            label.text = "\(pollModel.hours[row].time) \(pollModel.hours[row].description)"
            return label
        case 2:
            label.text = "\(pollModel.minutes[row].time) \(pollModel.minutes[row].description)"
            return label
        default:
            return label
        }
    }


}

struct PollTime {
    let time: Int
    let description: String
}

struct PollModel {
    let days: [PollTime]
    let hours: [PollTime]
    let minutes: [PollTime]

    var currentTime: (day: PollTime, hour: PollTime, minute: PollTime)?

    init() {
        days = [Int](0...7).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "days" : "day") }
        hours = [Int](0...23).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "hours" : "hour") }
        minutes = [Int](0...59).map { PollTime(time: $0, description: $0 == 0 || $0 > 1 ? "min" : "min") }
    }

    private func timeBiggerThan10Minutes() -> Bool {
        guard let currentTime = currentTime else {
            return false
        }
        return currentTime.minute.time > 9 || currentTime.hour.time > 0 || currentTime.day.time > 0
    }

    private func timeLessThan7Days() -> Bool {
        guard let currentTime = currentTime else {
            return false
        }
        if currentTime.day.time == 7 {
            return currentTime.hour.time == 0 && currentTime.minute.time == 0
        }
        return currentTime.day.time < 8
    }

    func validateCurrenTime() -> Bool {
        return timeBiggerThan10Minutes() && timeLessThan7Days()
    }
}
regina_fallangi
  • 2,080
  • 2
  • 18
  • 38
  • I'm getting an error at this line: `[0,1,2].forEach { self?.pickerView.selectRow(0, inComponent: $0, animated: true) }` it says "Ambiguous reference to member 'pickerView(_:numberOfRowsInComponent:)'" – Dani Jul 02 '18 at 09:37
  • Notice that this code includes ALL the code that you already had. That message sounds like you have the function `pickerView(_:numberOfRowsInComponent:)` repeated. Can you check? I have tested and it works for me. – regina_fallangi Jul 02 '18 at 09:39
  • The `self` was messing with the code on my side. Just one final question - how can I get the selected time and present it in a `UILabel`? I don't need the final date, but a date in the following format - for example `6 days 3h 10 min` ? I cannot do it based on `postModel.currentTime` and convert it into a String – Dani Jul 02 '18 at 09:54
  • 1
    within `didSelectRow` you can easily get that. Write `print ("\(days) days \(hours)h \(minutes) min")` after `if !pollModel...` and see the result. – regina_fallangi Jul 02 '18 at 09:59
  • 1
    Excellent! Thank you for the time and the help! :) – Dani Jul 02 '18 at 10:08