Binding’s inside NavigationSplitView detail (TextField, TextEditor)

235 Views Asked by At

I'm using a two-column NavigationSplitView. Trying to figure out how to update the data model via .onSubmit modifier and use a TextField view without Binding.constant.

Within the detail section, I have TextField and TextEditor.

  1. How to avoid Binding.contant()? I mean, I need mutation.
  2. This is a correct way to update value property in Model?

I need a single selection in List.

Here's my sample code (70 line’s):

struct Model: Identifiable, Hashable {
    var id = UUID()
    var title: String = "Brand new"
    var value: String = ""
    
    func updateValue() async -> Model {
        return Model(id: id, title: title, value: "The boar  is running through the field happily")
    }
}

final class DataModel: ObservableObject {
    @Published
    var models: [Model] = [
        .init(title: "First", value: "fur"),
        .init(title: "Second", value: "meow"),
        .init(title: "Another", value: "Make SwiftUI, not war")
    ]
    
    @MainActor
    func updateModel(for model: Model.ID) async -> Void {
        var findModel = models.first { $0.id == model }
        findModel = await findModel?.updateValue()
    }
}

struct ModelView: View {
    @StateObject
    private var dataModel = DataModel()
    
    @State
    private var listSelection: Model.ID?
    
    private var selectedModel: Model? {
        guard let selection = listSelection else { return nil }
        return dataModel.models.first { $0.id == selection }
    }
    
    var body: some View {
        NavigationSplitView {
            List(dataModel.models, selection: $listSelection) { model in
                NavigationLink(model.title, value: model.id)
            }
        } detail: {
            if let selectedModel {
                VStack {
                    TextField("Title", text: .constant(selectedModel.title))
                        .padding()
                        .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20))
                        .submitLabel(.go)
                        .onSubmit {
                            Task {
                                // Update Model.value by hit `Go`
                                await dataModel.updateModel(for: selectedModel.id)
                            }
                        }
                    TextEditor(text: .constant(selectedModel.value))
                }
                .padding()
                .navigationTitle(selectedModel.title)
            }
        }
    }
}

struct ModelView_Previews: PreviewProvider {
    static var previews: some View {
        ModelView()
            .colorScheme(.light)
    }
}
1

There are 1 best solutions below

0
On

After a couple of days, I realized what I could do. No one answered the question, so I solved the problem this way.

The final solution is below:

struct Model: Identifiable, Hashable {
    var id = UUID()
    var title: String = "Brand new"
    var value: String = ""
    
    func updateValue() async -> Model {
        return Model(id: id, title: title, value: "The boar  is running through the field happily")
    }
}

final class DataModel: ObservableObject {
    @Published
    var models: [Model] = [
        .init(title: "First", value: "fur"),
        .init(title: "Second", value: "meow"),
        .init(title: "Another", value: "Make SwiftUI, not war")
    ]
    
    @MainActor
    func updateModel(for model: Binding<Model>) async -> Void {
        model.wrappedValue = await model.wrappedValue.updateValue()
    }
    
    func bindingToModel(_ model: Model.ID) -> Binding<Model> {
        Binding<Model> {
            guard let index = self.models.firstIndex(where: { $0.id == model }) else {
                return Model()
            }
            return self.models[index]
        } set: { newModel in
            guard let index = self.models.firstIndex(where: { $0.id == model }) else { return }
            self.models[index] = newModel
        }
    }
}

struct ModelView: View {
    @StateObject
    private var dataModel = DataModel()
    
    @State
    private var listSelection: Model.ID?
    
    var body: some View {
        NavigationSplitView {
            List(dataModel.models, selection: $listSelection) { model in
                NavigationLink(model.title, value: model.id)
            }
        } detail: {
            if let listSelection, let bindModel = dataModel.bindingToModel(listSelection)  {
                VStack {
                    TextField("Title", text: bindModel.title)
                        .padding()
                        .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20))
                        .submitLabel(.go)
                        .onSubmit {
                            Task {
                                // Update Model.value by hit `Go`
                                await dataModel.updateModel(for: bindModel)
                            }
                        }
                    TextEditor(text: bindModel.value)
                }
                .padding()
                .navigationTitle(bindModel.title)
            }
        }
    }
}