1

I have broken our production code down to the essentials.

We wrote a UIViewRepresentable for the UITextField. To make the textfeild the first responder we defined IsEditing as a bindable value. We pass this value to the coordinator and update it accordingly in didBeginEditing and didEndEditing of the UITextFieldDelegate. Basically everything works as expected, but as soon as you display a view above this view that also claims the FirstResponder and navigate back again the UI below seems to be broken. Everything looks right at first glance, but if you take a closer look in the view debugger, the horror becomes obvious. The controls seem to be positioned correctly on the visual plane, but the frames are slightly offset under the hood.

This is the Code for our UIViewRepresentable:

import SwiftUI

struct UITextFieldRepresentable: UIViewRepresentable {
    @Binding var isEditing: Bool

    init(isEditing: Binding<Bool>) {
        self._isEditing = isEditing
    }

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        if isEditing && !uiView.isFirstResponder {
            uiView.becomeFirstResponder()
        } else if !isEditing && uiView.isFirstResponder {
            uiView.resignFirstResponder()
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(isEditing: $isEditing)
    }

    class Coordinator: NSObject, UITextFieldDelegate {
        @Binding var isEditing: Bool

        init(isEditing: Binding<Bool>) {
            self._isEditing = isEditing
        }

        func textFieldDidBeginEditing(_ textField: UITextField) {
            if !isEditing {
               self.isEditing = true
            }
        }

        func textFieldDidEndEditing(_ textField: UITextField) {
            if isEditing {
               self.isEditing = false
            }
        }
    }
}

And this the code which produces the error.

struct ContentView: View {
    var body: some View {
        TestView()
    }
}

class ViewModel: ObservableObject {
    @Published var isEditing1: Bool = false
}

struct TestView: View {
    @StateObject var viewModel = ViewModel()

    @Environment(\.presentationMode) var presentationMode

    @State var showSheet = false

    var body: some View {
        ScrollView {
            VStack {
                Button("dismiss") {
                    presentationMode.wrappedValue.dismiss()
                }
                UITextFieldRepresentable(isEditing: $viewModel.isEditing1)
                    .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.blue, style: StrokeStyle()))
                Button("TestButton") {

                }
                .background(Color.red)
                Button("TestButton") {

                }
                .background(Color.yellow)
                Button("ShowSheet") {
                    showSheet = true
                }
                .background(Color.green)
            }
        }.sheet(isPresented: $showSheet, content: {
            TestView()
        })
    }
}

Here are the steps to reproduce the behavior:

  1. You need to activate the first textfield.
  2. Press the show sheet dialog
  3. Active the textfield in the newly displayed view
  4. Dismiss the sheet by pressing the dismiss button
  5. Try to click the show sheet button -> The position of the click now seems to have an offset

Broken Frames

We have already tried many things to solve the problem, but have not yet found a good solution or the actual cause. Does anyone have an idea what is going wrong?

UPDATE: The Issue also appears when using a simple SwiftUITextField.

Sebastian Boldt
  • 4,947
  • 9
  • 46
  • 60

0 Answers0