es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

SwiftUI iOS15 Binding ForEach está desestimando el teclado de TextField.

Como se menciona en WWDC21 “Novedades en SwiftUI”, ahora es posible manejar el binding dentro de ForEach, pero tengo un problema con TextField, si agrego un carácter, el teclado desaparece.

struct TESTVIEW: View {

    public struct MyTask: Identifiable, Codable, Hashable {
        public var id: UUID = UUID()
        var title: String = ""
    }

    @State var tasks: [MyTask] = []

    var body: some View {

        VStack {
            Text("NEW")
            List {
                ForEach ($tasks, id:\.self) { $task in
                    TextField("Click", text: $task.title)
                }
                .onDelete(perform: delete)
                Button(action: {
                    tasks.append(MyTask())
                }) {
                    Label("New task", systemImage: "plus.circle.fill")
                }
                .padding()
                .accentColor(.white)
            }
            Text("OLD")
            List {
                ForEach (tasks.indices, id:\.self) { idx in
                    let bindingTask = Binding(get: {tasks[idx]},set: {value in tasks[idx] = value})
                    TextField("Click", text: bindingTask.title)
                }
                .onDelete(perform: delete)
                Button(action: {
                    tasks.append(MyTask())
                }) {
                    Label("New task", systemImage: "plus.circle.fill")
                }
                .padding()
                .accentColor(.white)
            }
        }
        .preferredColorScheme(.dark)
    }

    func delete(at offsets: IndexSet) {
        tasks.remove(atOffsets: offsets)
    }
}

struct TESTVIEW_Previews: PreviewProvider {
    static var previews: some View {
        TESTVIEW()
    }
}

¿Alguna sugerencia?

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Basado en el código proporcionado, parece que el problema con el teclado que se oculta cuando se agrega un carácter al TextField se debe al uso de $task.title como enlace para el TextField.

    Una posible solución a este problema es crear una variable @State separada para almacenar el texto ingresado en el TextField y luego usar esa variable de estado para actualizar el objeto MyTask dentro del bucle ForEach.

    Aquí tienes un fragmento de código actualizado que muestra este enfoque:

    struct TESTVIEW: View {
    
        public struct MyTask: Identifiable, Codable, Hashable {
            public var id: UUID = UUID()
            var title: String = ""
        }
    
        @State var tasks: [MyTask] = []
        @State var newTaskTitle: String = ""
    
        var body: some View {
    
            VStack {
                Text("NUEVO")
                List {
                    ForEach($tasks) { taskBinding in
                        TextField("Haz clic", text: taskBinding.title)
                    }
                    .onDelete(perform: delete)
                    Button(action: {
                        tasks.append(MyTask())
                    }) {
                        Label("Nueva tarea", systemImage: "plus.circle.fill")
                    }
                    .padding()
                    .accentColor(.white)
                }
                .onChange(of: newTaskTitle) { title in
                    if !title.isEmpty {
                        tasks.append(MyTask(title: title))
                        newTaskTitle = ""
                    }
                }
                .listStyle(.plain)
                Text("ANTIGUO")
                List {
                    ForEach(tasks.indices, id: \.self) { idx in
                        let bindingTask = Binding(
                            get: {tasks[idx]},
                            set: {value in tasks[idx] = value}
                        )
                        TextField("Haz clic", text: binding(for: bindingTask))
                    }
                    .onDelete(perform: delete)
                    Button(action: {
                        tasks.append(MyTask())
                    }) {
                        Label("Nueva tarea", systemImage: "plus.circle.fill")
                    }
                    .padding()
                    .accentColor(.white)
                }
                .listStyle(.plain)
                .onChange(of: newTaskTitle) { title in
                    if !title.isEmpty {
                        tasks.append(MyTask(title: title))
                        newTaskTitle = ""
                    }
                }
            }
            .preferredColorScheme(.dark)
        }
    
        func delete(at offsets: IndexSet) {
            tasks.remove(atOffsets: offsets)
        }
    
        private func binding(for task: Binding<mytask>) -> Binding<string> {
            Binding<string>(
                get: { task.wrappedValue.title },
                set: { newValue in task.wrappedValue.title = newValue }
            )
        }
    }
    
    struct TESTVIEW_Previews: PreviewProvider {
        static var previews: some View {
            TESTVIEW()
        }
    }
    

    En este código actualizado, hemos agregado una nueva variable @State llamada newTaskTitle, que se utiliza para guardar el texto ingresado por el usuario en el TextField que se utiliza para crear una nueva tarea.

    También hemos agregado un modificador .onChange a ambas vistas List, que se activa cuando la variable de estado newTaskTitle cambia. Dentro del cierre, verificamos si el nuevo título no está vacío y, de ser así, creamos un nuevo objeto MyTask con el título proporcionado y lo agregamos al array tasks. También restablecemos la variable newTaskTitle a una cadena vacía para borrar el TextField.

    Para usar la variable de estado newTaskTitle, hemos actualizado el primer bucle ForEach para usar $tasks, que nos proporciona enlaces a los objetos MyTask en el array. Dentro de este bucle, simplemente usamos el enlace al title para actualizar el texto del TextField.

    Para el segundo bucle ForEach, hemos creado una función binding(for:) que toma un Binding<mytask> y devuelve un Binding<string> que podemos usar para actualizar el texto del TextField. Dentro de la función, usamos la propiedad wrappedValue del Binding<mytask> para acceder al objeto MyTask y luego usamos eso para crear un nuevo Binding<string> que actualiza la propiedad title del objeto MyTask.

    En general, estos cambios deberían permitirte administrar enlaces dentro de ForEach y al mismo tiempo poder ingresar texto en el TextField sin ocultar el teclado.

Comments are closed.