Saturday, October 14, 2023
HomeiOS Developmentios - Unusual conduct of SwiftUI keyboard with edit textual content

ios – Unusual conduct of SwiftUI keyboard with edit textual content


I’ve a big iOS venture that was migrated from XIBs to SwiftUI views. The migration appears to have been OK however for a wierd conduct of the keyboard associated with the kinds.

To point out you my drawback I’ve develop a really quick pattern of this drawback the place it may be very simply reproduced (I am utilizing iOS 16). That is the code of my pattern (for design functions I can not use instantly SwiftUI textfields, I’ve tu use UITextFields and wrap them for use by SwiftUI):

MyApp:

import SwiftUI

@major
struct MyApp: App {
    var physique: some Scene {
        WindowGroup {
            FirstView()
        }
    }
}

FirstView

import SwiftUI

struct FirstView: View {
    
    @State var hasToGoToSecondView = false
    
    var physique: some View {
        
        NavigationView {
            
            VStack {
                
                NavigationLink(vacation spot:SecondView().navigationBarHidden(true),
                               isActive: $hasToGoToSecondView) {
                }
                .navigationBarHidden(true)
                .hidden()

                Button(motion: {
                    print("Button clicked")
                    hasToGoToSecondView = true
                }) {
                
                    VStack {
                        
                        Picture(systemName: "globe")
                            .imageScale(.giant)
                            .foregroundColor(.accentColor)
                        Textual content("Hey, world!")
                    }
                }
            }
        }
    }
}

SecondView

import SwiftUI

struct SecondView: View {
    
    @State var userUsernameOrEmail: String = ""
    @State var userPassword: String = ""
    @State var fieldFocus = [false, false]
    @Setting(.presentationMode) var presentationMode: Binding<PresentationMode>

    var physique: some View {
        
        VStack {
            
            CustomNavigationBar(backAction: {
                                    presentationMode.wrappedValue.dismiss()
                                })
            
            Spacer()

            Textual content("Type")
                .padding(.backside, 32)
            
            CustomTextField(inputText: $userUsernameOrEmail,
                            focusable: $fieldFocus,
                            tag: 0,
                            returnKeyType: .subsequent,
                            keyboardType: .emailAddress)
            .body(top: 30)
            .padding(32)

            CustomTextField(inputText: $userPassword,
                            focusable: $fieldFocus,
                            tag: 1,
                            returnKeyType: .performed)
            .body(top: 30)
            .padding(32)
            
            Spacer()
        }
    }
}

CustomNavigationBar

struct CustomNavigationBar: View {
        
    var backAction: (() -> Void)?

    init(backAction: (() -> Void)? = nil) {
        self.backAction = backAction
    }
    
    var physique: some View {

        ZStack {
            
            HStack {
                
                Button {
                    backAction?()
                } label: {
                    Textual content("< Again")
                }
                .padding([.leading, .trailing], 10)
                
                Spacer()
            }
        }
        .body(top: 54)
        .body(maxWidth: .infinity)
        .padding([.leading, .trailing], 10)
    }
}

CustomTextField

struct CustomTextField: View {
    
    @Binding var inputText: String
    var focusable: Binding<[Bool]>? = nil
    var tag: Int? = nil
    var returnKeyType: UIReturnKeyType = .default
    var keyboardType: UIKeyboardType = .default

    init(inputText: Binding<String>,
         focusable: Binding<[Bool]>? = nil,
         tag: Int? = nil,
         returnKeyType: UIReturnKeyType = .default,
         keyboardType: UIKeyboardType = .default) {
        
        self._inputText = inputText
        self.focusable = focusable
        self.tag = tag
        self.returnKeyType = returnKeyType
        self.keyboardType = keyboardType
    }

    var physique: some View {
        
        HStack {
            
            CustomUITextField(textual content: $inputText,
                              focusable: focusable,
                              returnKeyType: returnKeyType,
                              keyboardType: keyboardType,
                              tag: tag)
            .foregroundColor(Shade.black)
            .padding([.leading, .trailing], 24)
            .padding([.top, .bottom], 18)
        }
        .background(Shade.yellow)
        .cornerRadius(10)
    }
}

CustomUITextField

struct CustomUITextField: UIViewRepresentable {
    
    @Binding var textual content: String
    var focusable: Binding<[Bool]>? = nil
    var returnKeyType: UIReturnKeyType = .default
    var keyboardType: UIKeyboardType = .default
    var tag: Int? = nil
    
    func makeUIView(context: Context) -> UITextField {
        
        let textField = UITextField(body: .zero)
        textField.delegate = context.coordinator
        textField.returnKeyType = returnKeyType
        textField.autocapitalizationType = .none
        textField.spellCheckingType = .no
        textField.autocorrectionType = .no
        textField.keyboardType = keyboardType
        textField.isSecureTextEntry = false
        textField.textAlignment = .left
        if let tag = tag {
            textField.tag = tag
        }
        textField.addTarget(context.coordinator, motion: #selector(Coordinator.textFieldDidChange(_:)), for: .editingChanged)
        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) {
        
        uiView.textual content = textual content
        uiView.isSecureTextEntry = false
        if let focusable = focusable?.wrappedValue {
            
            for (index, centered) in focusable.enumerated() {
                if uiView.tag == index && centered {
                    DispatchQueue.major.asyncAfter(deadline: .now() + 0.05) {
                        uiView.becomeFirstResponder()
                    }
                    break
                }
            }
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self, maxChars: 20, allowedChars: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890@._%+-")
    }
    
    ultimate class Coordinator: NSObject, UITextFieldDelegate {
        
        let management: CustomUITextField
        var maxChars: Int32 = 0
        var allowedChars: String = ""
        
        init(_ management: CustomUITextField,
             maxChars: Int32,
             allowedChars: String) {
            self.management = management
            self.maxChars = maxChars
            self.allowedChars = allowedChars
        }
        
        func textFieldDidBeginEditing(_ textField: UITextField) {
            guard var focusable = management.focusable?.wrappedValue else { return }
            
            for i in 0...(focusable.rely - 1) {
                focusable[i] = (textField.tag == i)
            }
            
            management.focusable?.wrappedValue = focusable
        }
        
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            guard var focusable = management.focusable?.wrappedValue else {
                DispatchQueue.major.asyncAfter(deadline: .now() + 0.05) {
                    textField.resignFirstResponder()
                }
                return true
            }
            
            for i in 0...(focusable.rely - 1) {
                focusable[i] = (textField.tag + 1 == i)
            }
            
            management.focusable?.wrappedValue = focusable
            
            if textField.tag == focusable.rely - 1 {
                DispatchQueue.major.asyncAfter(deadline: .now() + 0.05) {
                    textField.resignFirstResponder()
                }
            }
            
            return true
        }
        
        func textField(_ textField: UITextField, shouldChangeCharactersIn vary: NSRange, replacementString string: String) -> Bool {
            
            let textLength = textField.textual content?.rely ?? 0
            let newLength = textLength + string.rely - vary.size
            if (maxChars > 0) && (newLength > maxChars) {
                return false
            } else if allowedChars != "" {
                let set = NSCharacterSet(charactersIn: allowedChars)
                let forbiddenChars = set.inverted
                let matchRange = string.rangeOfCharacter(from: forbiddenChars)
                return matchRange == nil
            }
            return true
        }
        
        
        func textFieldDidEndEditing(_ textField: UITextField) {
            print("editfieldend")
        }
        
        @objc func textFieldDidChange(_ textField: UITextField) {
            management.textual content = textField.textual content ?? ""
            print("editfieldchanged")
        }
    }
}

After I go to SecondView, set values within the textual content fields and return to FirstView (think about the person regrets and desires to be again) the keyboard/views make a really unusual flickering impact.

enter image description here

I’ve tried to cover the keyboard simply earlier than leaving the view:

    CustomNavigationBar(backAction: {
                            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                                            to: nil,
                                                            from: nil,
                                                            for: nil)
                            presentationMode.wrappedValue.dismiss()
                        })

And even with a protracted delay (only for testing functions):

    CustomNavigationBar(backAction: {
                            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                                            to: nil,
                                                            from: nil,
                                                            for: nil)
                            DispatchQueue.major.asyncAfter(deadline: .now() + 1.0) {
                                presentationMode.wrappedValue.dismiss()
                            }
                        })

That is the end result:

enter image description here

After seeing this final gif I’ve thought that it appears the keyboard is hidden accurately however there’s “one other” keyboard “floating” within the view. So I’ve tried to cover the keyboard with each textual content subject change, to make sure “there is just one keyboard at a time”. I’ve tried this:

func updateUIView(_ uiView: UITextField, context: Context) {

uiView.textual content = textual content
uiView.isSecureTextEntry = false
if let focusable = focusable?.wrappedValue {
    var resignResponder = true

    for (index, centered) in focusable.enumerated() {
        if uiView.tag == index && centered {
            DispatchQueue.major.asyncAfter(deadline: .now() + 0.05) {
                uiView.becomeFirstResponder()
            }
            resignResponder = false
            break
        }
    }
    
    if resignResponder {
        DispatchQueue.major.asyncAfter(deadline: .now() + 0.05) {
            uiView.resignFirstResponder()
        }
    }
}
}

However that is even worse becuase it provides a flickering within the keyboard altering textual content fields.

enter image description here

I’ve confirmed that if I shut manually the keyboard earlier than clicking again, it really works okay.

enter image description here

Lastly, I’ve although about make one thing like this:

    CustomNavigationBar(backAction: {
                            resignFocusedField()
                            presentationMode.wrappedValue.dismiss()
                        })
non-public func resignFocusedField() {

    for i in 0..<fieldFocus.rely {

        let isFocused = fieldFocus[i]

        if isFocused {

            // resign i textfiels

        }
    }
}

However I do not understand how might I do that.
Any assist could be very appreciated.



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments