首页 > 解决方案 > 在 SwiftUI 中处理派生状态

问题描述

说我正在创建一个“日期编辑器”视图。目标是: - 采用默认的种子日期。- 它允许用户更改输入。- 如果用户随后选择,他们可以按“保存”,在这种情况下,视图的所有者可以决定对数据进行处理。

这是实现它的一种方法:

struct AlarmEditor : View {
    var seedDate : Date
    var handleSave : (Date) -> Void

    @State var editingDate : Date?

    var body : some View {
        let dateBinding : Binding<Date> = Binding(
            get: {
                return self.editingDate ?? seedDate
            },
            set: { date in
                self.editingDate = date
            }
        )

        return VStack {
            DatePicker(
                selection: dateBinding,
                displayedComponents: .hourAndMinute,
                label: { Text("Date") }
            )
            Spacer()
            Button(action: {
                self.handleSave(dateBinding.wrappedValue)
            }) {
                Text("Save").font(.headline).bold()
            }
        }
    }
}

问题

如果所有者更改 的值seedDate怎么办?

说在那种情况下,我想做的是将值重置为editingDate新的种子日期。

这样做的惯用方式是什么?

标签: swiftswiftui

解决方案


我不确定我是否理解seedDate这里的目的。但我认为您过于依赖事件(一种 UIKit 方式)而不是单一事实来源原则(SwiftUI 方式)。

更新:添加了一种取消日期版本的方法。在这种情况下,编辑器视图应该Binding只在保存时改变。为此,它使用State将用于日期选择器的私有。这样,事实的来源被保留,因为使用的私有状态永远不会离开编辑视图的上下文。

struct ContentView: View {
    @State var dateEditorVisible = false
    @State var date: Date = Date() // source of truth

    var body: some View {
        NavigationView {
            VStack {
                Text("\(date.format("HH:mm:ss"))")

                Button(action: self.showDateEditor) {
                    Text("Edit")
                }
                .sheet(isPresented: $dateEditorVisible) {
                    // Here we provide a two way binding to the `date` state
                    // and a way to dismiss the editor view.
                    DateEditorView(date: self.$date, dismiss: self.hideDateEditor)
                }
            }
        }
    }

    func showDateEditor() {
        dateEditorVisible = true
    }

    func hideDateEditor() {
        dateEditorVisible = false
    }
}
struct DateEditorView: View {
    // Only a binding.
    // Updating this value will update the `@State date` of the parent view
    @Binding var date: Date

    @State private var editingDate: Date = Date()
    private var dismiss: () -> Void

    init(date: Binding<Date>, dismiss: @escaping () -> Void) {
        self._date = date
        self.dismiss = dismiss

        // assign the wrapped value as default value for edition
        self.editingDate = date.wrappedValue
    }

    var body: some View {
        VStack {
            DatePicker(selection: $editingDate, displayedComponents: .hourAndMinute) {
                Text("Date")
            }

            HStack {
                Button(action: self.save) {
                    Text("Save")
                }

                Button(action: self.dismiss) {
                    Text("Cancel")
                }
            }
        }
    }

    func save() {
        date = editingDate
        dismiss()
    }
}

通过这种方式,您无需定义保存操作来更新父视图或使当前值与某些默认值保持同步。你只有一个单一的事实来源来驱动你的所有 UI。

编辑

Date使其构建的扩展。

extension Date {
    private static let formater = DateFormatter()

    func format(_ format: String) -> String {
        Self.formater.dateFormat = format
        return Self.formater.string(from: self)
    }
}

推荐阅读