iosswiftui

SwiftUI: How to get value from TextField presented in a bottom sheet base on a value related to the selected item on the list?


This is how it look in the app:

enter image description here enter image description here

On the left there is a simple list created from ForEach. When user tap any element on that list BottomSheet is presented with a Binded value for selected item. How can I get that value and update NSManagedObject in core data when user tap save button? Is there a simple and easy way to do this?

The same situation in code:

struct SettingsView: View {
    @State var isTextInputPresented = false
    @State var editingTextFieldValue = ""
    private var onSave: ((String) -> Void)? = nil

    var body: some View {
        ZStack {
            ScrollView {
              ForEach(users, id: \.self) { user in //users are from @FetchRequest
                HStack {
                    TextLabel(user.name, color: theme.secondTeamColor)
                        .padding(EdgeInsets(vertical: 10))
                    Spacer()
                }
                .onTapGesture {
                    editingTextFieldValue = user.name
                    isTextInputPresented = true
                    // cannot assign onSave handler here🤷🏼‍♂️
                }
              }
            }
            BottomSheetView(title: "Edit input", isShowing: $isTextInputPresented) {
                TextInputView(text: $editingTextFieldValue, onSave: onSave)
            }
        }
    }
}


import SwiftUI

struct TextInputView: View {
    @Environment(Theme.self) private var theme
    @Binding var text: String
    @FocusState private var focus: Bool
    var onSave: ((String) -> Void)?
    var body: some View {
        HStack(spacing: 20) {
            TextField("", text: $text, prompt: Text("Placeholder").foregroundColor(.gray))
                .padding(10)
                .multilineTextAlignment(.center)
                .font(.system(size: 24))
                .foregroundStyle(theme.backgroundColor)
                .tint(theme.backgroundColor.opacity(0.4))
                .focused($focus)
                .cornerRadius(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(theme.backgroundColor.opacity(0.5), lineWidth: 3)
                )
            Button {
                onSave?(text)
            } label: {
                Image(systemName: "checkmark")
            }
            .font(.bold(withSize: 22))
            .frame(width: 56, height: 56)
            .background(theme.backgroundColor)
            .foregroundStyle(theme.textColor)
            .cornerRadius(10)
        }
        .padding(20)
        .onAppear {
            focus = true
        }
    }
}

Solution

  • You could try this approach declaring @State private var onSave: ((String) -> Void)? = nil and using onSave in the .onTapGesture as shown in the example code:

    
    struct SettingsView: View {
        @State private var isTextInputPresented = false
        @State private var editingTextFieldValue = ""
        @State private var onSave: ((String) -> Void)? = nil // <--- here
    
        // for my testing
        let users = [User(name: "user-1"), User(name: "user-2")]
        
        var body: some View {
            ZStack {
                ScrollView {
                    ForEach(users) { user in // users are from @FetchRequest
                        HStack {
                            Text(user.name)
                            Spacer()
                        }
                        .onTapGesture {
                            editingTextFieldValue = user.name
                            isTextInputPresented = true
                            onSave = { newName in
                                // save newName back to CoreData
                                print("----> saving: \(newName)")
                            }
                        }
                    }
                }
                // for my testing
                .sheet(isPresented: $isTextInputPresented) {
                    TextField("Edit input", text: $editingTextFieldValue)
                        .onSubmit {
                            onSave?(editingTextFieldValue)
                            isTextInputPresented = false
                        }
                }
    //            BottomSheetView(title: "Edit input", isShowing: $isTextInputPresented) {
    //                TextInputView(text: $editingTextFieldValue, onSave: onSave)
    //            }
            }
        }
    }
    
    

    EDIT-1

    using the TextInputView and a @Binding var onSave ...

    
    struct SettingsView: View {
        @State private var isTextInputPresented = false
        @State private var editingTextFieldValue = ""
        @State private var onSave: ((String) -> Void)? = nil // <--- here
    
        // for my testing
        let users = [User(name: "user-1"), User(name: "user-2")]
        
        var body: some View {
            ZStack {
                ScrollView {
                    ForEach(users) { user in // users are from @FetchRequest
                        HStack {
                            Text(user.name)
                            Spacer()
                        }
                        .onTapGesture {
                            editingTextFieldValue = user.name
                            isTextInputPresented = true
                            onSave = { newName in
                                // save newName back to CoreData
                                print("----> saving: \(newName)")
                            }
                        }
                    }
                }
                // for testing using TextInputView
                .sheet(isPresented: $isTextInputPresented) {
                    TextInputView(text: $editingTextFieldValue, onSave: $onSave) // <--- here
                }
            }
        }
    }
    
    struct TextInputView: View {
        @Environment(Theme.self) private var theme
        @Binding var text: String
        @FocusState private var focus: Bool
        @Binding var onSave: ((String) -> Void)?  // <--- here
        
        var body: some View {
            //...
        }
    }