首页 > 解决方案 > TextField with animation crashes app and looses focus

问题描述

Minimal reproducible example:

In SceneDelegate.swift:

let contentView = Container()

In ContentView.swift:

struct SwiftUIView: View {
    @State var text: String = ""

    var body: some View {
        VStack {
            CustomTextFieldView(text: $text)
        }
    }
}

struct Container: View {

    @State var bool: Bool = false
    @State var text: String = ""

    var body: some View {

        Button(action: {
            self.bool.toggle()
        }) {
            Text("Sheet!")
        }
        .sheet(isPresented: $bool) {
            SwiftUIView()
        }
    }
}

In CustomTextField.swift:

struct CustomTextFieldView: View {

    @Binding var text: String
    @State var editing: Bool = false


    var body: some View {
        Group {
            if self.editing {
                textField
                    .background(Color.red)
            } else {
                ZStack(alignment: .leading) {
                    textField
                        .background(Color.green)
                    Text("Placeholder")
                }
            }
        }
        .onTapGesture {
            withAnimation {
                self.editing = true
            }
        }
    }

    var textField: some View {
        TextField("", text: $text)
    }

Problem:

After running the above code and focusing the text field, the app crashes. Some things I noticed:

  1. If I remove the withAnimation code, or the ZStack in CustomTextField file, the app doesn't crash, but the TextField looses focus.
  2. If I remove the VStack in SwiftUIView, the app doesn't crash, but the TextField looses focus.
  3. If I use a NavigationLink or present the TextField without a sheet, the app doesn't crash, but the TextField looses focus.

Questions:

标签: swiftswiftui

解决方案


  1. How can I keep the focus of the TextField after the body is recalculated because of a change in state?

You have two of them. Two different TextField could not be in editing state at the same time.

The approach suggested by Asperi is the only possible.

The reason, why your code crash is not easy explain, but expected in current SwiftUI.

You have to understand, that Group is not a standard container, it just like a "block" on which you can apply some modifiers. Removing Group and using wraping body in ViewBuilder

struct CustomTextFieldView: View {

    @Binding var text: String
    @State var editing: Bool = false

    @ViewBuilder
    var body: some View {
            if self.editing {
                TextField("", text: $text)
                    .background(Color.red)
                    .onTapGesture {
                        self.editing.toggle()
                }
            } else {
                ZStack(alignment: .leading) {
                    TextField("", text: $text)
                        .background(Color.green)
                        .onTapGesture {
                                self.editing.toggle()
                        }
                }
        }

    }
}

the code will stop to crash, but there is other issue, the keyboard will dismiss immediately. That is due the tap gesture applied.

So, believe or not, you have to use ONE TextField ONLY.

struct CustomTextFieldView: View {

    @Binding var text: String
    @State var editing = false

    var textField: some View {
        TextField("", text: $text, onEditingChanged: { edit in
            self.editing = edit
        })
    }

    var body: some View {

        textField.background(editing ? Color.green : Color.red)

    }
}

Use this custom text field elsewhere in your code, as you want


推荐阅读