Home > Software design >  How to move around a date picker created with UIKit in SwiftUI?
How to move around a date picker created with UIKit in SwiftUI?

Time:01-13

I'm trying to create a Date Picker in Swiftui, but i need to do it in UIKit because I need the "minute Interval" thing, I created it, but I can not move around, is stuck in the same place on my screen. How can i make this work ? Is there a SwiftUI solution ?

struct DatePickerUIKit: UIViewRepresentable {
    @Binding private var selection: Date

    private let range: ClosedRange<Date>?

    private var minimumDate: Date? {
         range?.lowerBound
    }

    private var maximumDate: Date? {
        range?.upperBound
    }

    private var minuteInterval: Int

    private let datePicker = UIDatePicker()

    init(selection: Binding<Date>, in range: ClosedRange<Date>?, minuteInterval: Int = 1) {
        self._selection = selection
        self.range = range
        self.minuteInterval = minuteInterval
    }

    func makeUIView(context: Context) -> UIDatePicker {
        datePicker.datePickerMode = .time
        datePicker.minuteInterval = minuteInterval
        datePicker.minimumDate = minimumDate
        datePicker.maximumDate = maximumDate
        datePicker.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)
        return datePicker
    }

    func updateUIView(_ uiView: UIDatePicker, context: Context) {
        datePicker.date = selection

    }

    func makeCoordinator() -> DatePickerUIKit.Coordinator {
        Coordinator(selection: $selection, in: range, minuteInterval: minuteInterval)
    }

    class Coordinator: NSObject {
        private let selection: Binding<Date>
        private let range: ClosedRange<Date>?
        private let minuteInterval: Int

        init(selection: Binding<Date>, in range: ClosedRange<Date>? = nil, minuteInterval: Int = 1) {
            self.selection = selection
            self.range = range
            self.minuteInterval = minuteInterval
        }

        @objc func changed(_ sender: UIDatePicker) {
            self.selection.wrappedValue = sender.date
        }
    }
}
  var body: some View {
        let minMaxRange = Date.now...Date.now
        VStack {
            Spacer()
        DatePickerUIKit(selection: $dateFrom,
                        in: minMaxRange, minuteInterval: 30)
         
        }
}
}

Here is a picture where is always located . picture

CodePudding user response:

Based on "lorem ipsum" comment, I fixed it, finally.

let minMaxRange = Date.now...Date.now
DatePickerUIKit(selection: $dateFrom,in: minMaxRange, minuteInterval: 30)
.frame(width: 100, height: 100, alignment: .center)

CodePudding user response:

Default UIDatePicker does not have intrinsic content size which is used by SwiftUI UIViewRepresentable to determine frame of view.

Here is possible approach to improve your views so it fits own size (w/o hardcoding)

Tested with Xcode 13.2 / iOS 15.2 (red border is for testing, also removed spacer to show how date picker looks by itself)

demo

struct DemoUIDatePickerView: View {
    @State private var dateFrom: Date = Date()

    var body: some View {
        let minMaxRange = Date.now...Date.now
        VStack {
            DatePickerUIKit(selection: $dateFrom,
                            in: minMaxRange, minuteInterval: 30)
                .border(Color.red)                 // << for testing !!!
        }
    }
}

extension UIDatePicker {
    open override var intrinsicContentSize: CGSize {   // << required !!
        // calculates minimal needed size
        sizeThatFits(.zero)
    }
}

struct DatePickerUIKit: UIViewRepresentable {

    @Binding private var selection: Date

    private let range: ClosedRange<Date>?

    private var minimumDate: Date? {
        range?.lowerBound
    }

    private var maximumDate: Date? {
        range?.upperBound
    }

    private var minuteInterval: Int

    init(selection: Binding<Date>, in range: ClosedRange<Date>?, minuteInterval: Int = 1) {
        self._selection = selection
        self.range = range
        self.minuteInterval = minuteInterval
    }

    func makeUIView(context: Context) -> UIDatePicker {
        let datePicker = UIDatePicker()

        datePicker.datePickerMode = .time
        datePicker.minuteInterval = minuteInterval
        datePicker.minimumDate = minimumDate
        datePicker.maximumDate = maximumDate

        // Next constrains are also required to avoid control hugging
        datePicker.setContentHuggingPriority(.defaultHigh, for: .vertical)  // << !!
        datePicker.setContentHuggingPriority(.defaultHigh, for: .horizontal)  // << !!

        datePicker.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)

        return datePicker
    }

    func updateUIView(_ datePicker: UIDatePicker, context: Context) {
        datePicker.date = selection
    }

    func makeCoordinator() -> DatePickerUIKit.Coordinator {
        Coordinator(selection: $selection, in: range, minuteInterval: minuteInterval)
    }

    class Coordinator: NSObject {
        private let selection: Binding<Date>
        private let range: ClosedRange<Date>?
        private let minuteInterval: Int

        init(selection: Binding<Date>, in range: ClosedRange<Date>? = nil, minuteInterval: Int = 1) {
            self.selection = selection
            self.range = range
            self.minuteInterval = minuteInterval
        }

        @objc func changed(_ sender: UIDatePicker) {
            self.selection.wrappedValue = sender.date
        }
    }
}
  •  Tags:  
  • Related