简体   繁体   English

SwiftUI DatePicker Binding optional Date, valid nil

[英]SwiftUI DatePicker Binding optional Date, valid nil

I'm experimenting code from https://alanquatermain.me/programming/swiftui/2019-11-15-CoreData-and-bindings/我正在试验来自https://alanquatermain.me/programming/swiftui/2019-11-15-CoreData-and-bindings/的代码

my goal is to have DatePicker bind to Binding< Date?我的目标是让 DatePicker 绑定到 Binding< Date? > which allow for nil value instead of initiate to Date(); > 允许 nil 值而不是初始化 Date(); this is useful, if you have Date attribute in your core data model entity which accept nil as valid value.这很有用,如果你的核心数据模型实体中有 Date 属性,它接受 nil 作为有效值。

Here is my swift playground code:这是我的快速游乐场代码:

extension Binding {
    init<T>(isNotNil source: Binding<T?>, defaultValue: T) where Value == Bool {
        self.init(get: { source.wrappedValue != nil },
                  set: { source.wrappedValue = $0 ? defaultValue : nil})
    }
}

struct LiveView: View {
    @State private var testDate: Date? = nil
    var body: some View {
        VStack {
            Text("abc")

            Toggle("Has Due Date",
                   isOn: Binding(isNotNil: $testDate, defaultValue: Date()))

            if testDate != nil {
                DatePicker(
                    "Due Date",
                    selection: Binding($testDate)!,
                    displayedComponents: .date
                )
            }
        }
    }
}

let liveView = LiveView()
PlaygroundPage.current.liveView = UIHostingController(rootView: liveView)

I can't find solution to fix this code.我找不到修复此代码的解决方案。 It works when the toggle first toggled to on, but crash when the toggle turned back off.当切换第一次切换到打开时它工作,但当切换关闭时崩溃。

The code seems to behave properly when I removed the DatePicker, and change the code to following:当我删除 DatePicker 并将代码更改为以下代码时,代码似乎运行正常:

extension Binding {
    init<T>(isNotNil source: Binding<T?>, defaultValue: T) where Value == Bool {
        self.init(get: { source.wrappedValue != nil },
                  set: { source.wrappedValue = $0 ? defaultValue : nil})
    }
}

struct LiveView: View {
    @State private var testDate: Date? = nil
    var body: some View {
        VStack {
            Text("abc")

            Toggle("Has Due Date",
                   isOn: Binding(isNotNil: $testDate, defaultValue: Date()))

            if testDate != nil {
                Text("\(testDate!)")
            }
        }
    }
}

let liveView = LiveView()
PlaygroundPage.current.liveView = UIHostingController(rootView: liveView)

I suspect it's something to do with this part of the code我怀疑这与代码的这一部分有关

DatePicker("Due Date", selection: Binding($testDate)!, displayedComponents: .date )

or要么

problem when the source.wrappedValue set back to nil (refer to Binding extension) source.wrappedValue 设置回 nil 时出现问题(请参阅绑定扩展)

The problem is that DatePicker grabs binding and is not so fast to release it even when you remove it from view, due to Toggle action, so it crashes on force unwrap optional, which becomes nil ...问题是DatePicker抓取绑定并且即使您从视图中删除它也不会那么快释放它,由于Toggle操作,所以它在强制展开可选时崩溃,它变成 nil ...

The solution for this crash is此崩溃的解决方案是

DatePicker(
    "Due Date",
    selection: Binding<Date>(get: {self.testDate ?? Date()}, set: {self.testDate = $0}),
    displayedComponents: .date
)

backup 备份

An alternative solution that I use in all my SwiftUI pickers...我在所有 SwiftUI 选择器中使用的替代解决方案......

I learned almost all I know about SwiftUI Bindings (with Core Data) by reading that blog by Jim Dovey .通过阅读Jim Dovey 的博客,我几乎了解了所有关于 SwiftUI 绑定(使用核心数据)的知识。 The remainder is a combination of some research and quite a few hours of making mistakes.剩下的就是一些研究和几个小时犯错的结合。

So when I use Jim's technique to create Extensions on SwiftUI Binding then we end up with something like this for a deselection to nil...因此,当我使用 Jim 的技术在 SwiftUI Binding上创建Extensions时,我们最终会得到类似这样的取消选择为 nil ...

public extension Binding where Value: Equatable {
    init(_ source: Binding<Value>, deselectTo value: Value) {
        self.init(get: { source.wrappedValue },
                  set: { source.wrappedValue = $0 == source.wrappedValue ? value : $0 }
        )
    }
}

Which can then be used throughout your code like this...然后可以像这样在整个代码中使用它......

Picker("Due Date", 
       selection: Binding($testDate, deselectTo: nil),
       displayedComponents: .date
) 

OR when using .pickerStyle(.segmented)或者当使用.pickerStyle(.segmented)

Picker("Date Format Options", // for example 
       selection: Binding($selection, deselectTo: -1)) { ... }
    .pickerStyle(.segmented)

... which sets the index of the segmented style picker to -1 as per the documentation for UISegmentedControl and selectedSegmentIndex . ...根据UISegmentedControlselectedSegmentIndex的文档,将分段样式选择器的index设置为 -1。

The default value is noSegment (no segment selected) until the user touches a segment.默认值为 noSegment (未选择段),直到用户触摸一个段。 Set this property to -1 to turn off the current selection.将此属性设置为 -1 以关闭当前选择。

Here is my solution, I added a button to remove the date and add a default date.这是我的解决方案,我添加了一个按钮来删除日期并添加默认日期。 All it's wrapped in a new component所有这些都包含在一个新组件中

https://gist.github.com/Fiser12/62ef54ba0048e5b62cf2f2a61f279492 https://gist.github.com/Fiser12/62ef54ba0048e5b62cf2f2a61f279492

import SwiftUI

struct NullableBindedValue<T>: View {
    var value: Binding<T?>
    var defaultView: (Binding<T>, @escaping (T?) -> Void) -> AnyView
    var nullView: ( @escaping (T?) -> Void) -> AnyView

    init(
        _ value: Binding<T?>,
        defaultView: @escaping (Binding<T>, @escaping (T?) -> Void) -> AnyView,
        nullView: @escaping ( @escaping (T?) -> Void) -> AnyView
    ) {
        self.value = value
        self.defaultView = defaultView
        self.nullView = nullView
    }

    func setValue(newValue: T?) {
        self.value.wrappedValue = newValue
    }

    var body: some View {
        HStack(spacing: 0) {
            if value.unwrap() != nil {
                defaultView(value.unwrap()!, setValue)
            } else {
                nullView(setValue)
            }
        }
    }
}

struct DatePickerNullable: View {
    var title: String
    var selected: Binding<Date?>
    @State var defaultToday: Bool = false

    var body: some View {
        NullableBindedValue(
            selected,
            defaultView: { date, setDate in
                let setDateNil = {
                    setDate(nil)
                    self.defaultToday = false
                }

                return AnyView(
                    HStack {
                        DatePicker(
                            "",
                            selection: date,
                            displayedComponents: [.date, .hourAndMinute]
                        ).font(.title2)
                        Button(action: setDateNil) {
                            Image(systemName: "xmark.circle")
                                .foregroundColor(Color.defaultColor)
                                .font(.title2)
                        }
                        .buttonStyle(PlainButtonStyle())
                        .background(Color.clear)
                        .cornerRadius(10)
                    }
                )
            },
            nullView: { setDate in
                let setDateNow = {
                    setDate(Date())
                }

                return AnyView(
                    HStack {
                        TextField(
                            title,
                            text: .constant("Is empty")
                        ).font(.title2).disabled(true).textFieldStyle(RoundedBorderTextFieldStyle())
                        Button(action: setDateNow) {
                            Image(systemName: "plus.circle")
                                .foregroundColor(Color.defaultColor)
                                .font(.title2)
                        }
                        .buttonStyle(PlainButtonStyle())
                        .background(Color.clear)
                        .cornerRadius(10)
                    }.onAppear(perform: {
                        if self.defaultToday {
                            setDateNow()
                        }
                    })
                )
            }
        )
    }
}

Most optional binding problems can be solved with this:大多数可选的绑定问题都可以用这个解决:

public func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
    Binding(
        get: { lhs.wrappedValue ?? rhs },
        set: { lhs.wrappedValue = $0 }
    )
}

Here's how I use it with DatePicker:以下是我如何将它与 DatePicker 一起使用:

DatePicker(
    "",
    selection: $testDate ?? Date(),
    displayedComponents: [.date]
)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM