ios – UIViewRepresentable TextField that may append a uneditable string and follows different guidelines

ios – UIViewRepresentable TextField that may append a uneditable string and follows different guidelines


In Swift UI I’m struggling to discover a resolution to modifying Int16 and Double values primarily linked to CoreData entities.

The SwiftUI TextField("key", worth: $int16, format: .quantity) crashes each time a quantity too giant for the Int16 kind is typed in, understandibly.

What I would love my textfield to do is

  • Bind to a Int16? and String
  • When there’s a worth inside the textfield the appending string is appended to the tip but it surely not selectable or editable in anyway
  • Any main 0s needs to be eliminated and by no means proven (except the .allowZero possibility is getting used)
  • AutoFill/Pasting ought to solely be allowed in the event that they’re numbers (if thats not doable then under no circumstances)
  • When all values inside the string are eliminated the textfield needs to be clean, the appending string ought to disappear and the Int16 binding ought to change into nil. (This permits for the view to validate the Int16 as if there’s an appropriate worth Int16 won’t be nil)

In the intervening time I’ve created an enum which lets you choose extra guidelines for the TextField, reminiscent of permit unfavourable values and permit 0. These haven’t been built-in but.

My present issues are to do with pasting/autofill.

  • Autocomplete is ready to enter values that I don’t need reminiscent of letters after which I’m unable to take away them, this shouldn’t be doable it ought to both filter out all characters that are not numbers or ignore the brand new string totally
  • Managing the cursor is proving troublesome for me, when choosing a spread of values for instance the 234 within the Int16 12345 and change it with one other worth, say 1. The cursor seems right here 115| as a substitute of right here 11|5 the place I consider it could be anticipated.
  • the textfield does not scale with dynamic kind font sizes

I’m inexperienced with the UIViewRepresentable and UITextField and so there could also be different issues seen to those that realize it properly. All assistance is tremendously appreciated, in my present mission that is proving to be fairly the hurdle for common security.

Under is an instance of the code I’ve thus far.

struct Int16TextField: UIViewRepresentable {
    
    let titleKey: String
    @Binding var textual content: String
    @Binding var int16: Int16?
    let appending: String
    let choices: Set
    
    init(_ titleKey: String, textual content: Binding, int16: Binding, choices: Set = [], appending: String = "") {
        let x = NSLocalizedString(titleKey, remark: "")
        self.titleKey = x
        self._text = textual content
        self._int16 = int16
        self.appending = appending
        self.choices = choices
    }
    
    func makeUIView(context: Context) -> UITextField {
        let textField = getTextField()
        textField.delegate = context.coordinator
        return textField
    }
    
    //SwiftUI to UIKit
    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.textual content = textual content
    }
    
    personal func getTextField() -> UITextField{
        let textField = UITextField()
        let placeHolder: NSAttributedString = NSAttributedString(string: titleKey, attributes: [:])
        textField.attributedPlaceholder = placeHolder
        textField.keyboardType = .numberPad
        return textField
    }
    
    //UIKit to SwiftUI
    func makeCoordinator() -> Coordinator {
        return Coordinator(textual content: $textual content, int16: $int16, choices: choices, appending: appending)
    }
    
    class Coordinator: NSObject, UITextFieldDelegate {
        
        @Binding var textual content: String
        @Binding var int16: Int16?
        let appending: String
        let choices: Set
        
        init(textual content: Binding, int16: Binding, choices: Set, appending: String) {
            self._text = textual content
            self._int16 = int16
            self.appending = appending
            self.choices = choices
        }
        
        func textField(_ textField: UITextField, shouldChangeCharactersIn vary: NSRange, replacementString string: String) -> Bool {
            
            var oldValue = textField.textual content ?? ""
            var newValue = string
            
            print(oldValue.debugDescription, newValue.debugDescription)
            
            if oldValue.hasSuffix(appending){
                oldValue = String(oldValue.dropLast(appending.depend))
            }
            
            if newValue.isEmpty && oldValue.depend == 1 {
                int16 = nil
                textField.textual content = ""
                textual content = ""
                return false
            }
            
            // Guarantee solely numbers are allowed
            let allowedCharacters = CharacterSet.decimalDigits
            let characterSet = CharacterSet(charactersIn: newValue)
            guard allowedCharacters.isSuperset(of: characterSet) else {
                return false
            }
            
            //place new digit(s) wherever the cursor is vary.location == cursour place
            guard let textRange = Vary(vary, in: oldValue) else {
                
                return false
            }
            
            //If cursor is at first and we're including solely 0's, dont!
            if vary.location == 0{
                if newValue.hasPrefix("0"){
                    whereas newValue.hasPrefix("0") {
                        newValue.removeFirst()
                    }
                    if newValue.isEmpty{
                        return false
                    }
                }
            }
            var updatedText = oldValue.replacingCharacters(in: textRange, with: newValue)
            
            //Trim main 0's
            whereas updatedText.hasPrefix("0") {
                updatedText.removeFirst()
            }
            
            //guarantee Int16 conformance
            if updatedText != ""{
                guard let newInt16 = Int16(updatedText), newInt16 <= Int16.max else {
                    return false
                }
                int16 = newInt16
            }
            else {
                int16 = nil
            }
            
            updatedText += appending
            
            if updatedText == appending {
                int16 = nil
                textField.textual content = ""
                textual content = ""
                return false
            }
            
            textField.textual content = updatedText
            textual content = updatedText
            
            // Calculate the brand new cursor place
            let newCursorPosition: Int = (string.depend == 0 ? vary.location : vary.location + string.depend - vary.size)
            if let newPosition = textField.place(from: textField.beginningOfDocument, offset: newCursorPosition) {
                
                textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
            }
            
            return false
            
        }
        
        func textFieldDidChangeSelection(_ textField: UITextField) {
            if !appending.isEmpty {
                guard let selectedRange = textField.selectedTextRange else { return }
                let cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.begin)
                let textLength = textField.textual content?.depend ?? 0
                
                if selectedRange.begin == textField.beginningOfDocument && selectedRange.finish == textField.endOfDocument {
                    let newPosition = textField.place(from: textField.endOfDocument, offset: -appending.depend)
                    if let newPosition = newPosition{
                        textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: newPosition)
                    }
                }
                for decrement in 0...(appending.depend - 1) {
                    if cursorPosition == (textLength - decrement) {
                        let newPosition = textField.place(from: textField.endOfDocument, offset: -appending.depend)
                        if let newPosition = newPosition {
                            textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
                        }
                        break
                    }
                }
            }
        }
    }
}

And here’s a ContentView and the Int16TextFieldOptions enum for use in previews.

struct ContentView: View {
    @State var textual content: String = ""
    @State var int16: Int16?
    @State var appending: String = "cm"
    @State var choices: Set = [.allowMinus]
    
    func testInt16() -> String {
        guard let new = int16 else {return "nil"}
        return new.description
    }
    
    var physique: some View {
        VStack {
            Type{
                
                HStack{
                    Textual content("String:")
                    Spacer()
                    Textual content(textual content.debugDescription)
                }
                HStack{
                    Textual content("Int16:")
                    Spacer()
                    Textual content(testInt16())
                }
                Int16TextField(choices.rangeString(), textual content: $textual content, int16: $int16, choices: choices, appending: appending)
            }
        }
    }
}

enum Int16TextFieldOptions{
    case allowMinus, allowZero
}

extension Set {
    func rangeString() -> String {
        
        var x = "Vary: "
        
        if self.incorporates(.allowZero){
            if self.incorporates(.allowMinus){
                //each
                x += "-32768...32767"
            }
            else {
                //simply zero
                x += "0...32767"
            }
        }
        else if self.incorporates(.allowMinus){
            //minus
            x += "-32768...-1, 1...32767"
        }
        else {
            x += "1...32767"
        }
        
        guard x != "Vary: " else { fatalError() }
        return x
        
    }
}

Any assist can be tremendously appreciated as I really feel I’ve come to a lifeless finish on this drawback.

Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *