16.1 C
New York
Tuesday, September 3, 2024

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


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

The SwiftUI TextField("key", worth: $int16, format: .quantity) crashes at any time when a quantity too massive 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 choice is getting used)
  • AutoFill/Pasting ought to solely be allowed in the event that they’re numbers (if thats not doable then by no means)
  • 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 turn into nil. (This permits for the view to validate the Int16 as if there’s a suitable worth Int16 is not going to be nil)

In the mean time I’ve created an enum which lets you choose further guidelines for the TextField, resembling enable 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 are not looking for resembling 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 solely
  • Managing the cursor is proving tough 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 an alternative of right here 11|5 the place I imagine it will be anticipated.
  • the textfield would 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 understand it properly. All assistance is drastically appreciated, in my present venture that is proving to be fairly the hurdle for common security.

Under is an instance of the code I’ve to date.

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 in the beginning 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.accommodates(.allowZero){
            if self.accommodates(.allowMinus){
                //each
                x += "-32768...32767"
            }
            else {
                //simply zero
                x += "0...32767"
            }
        }
        else if self.accommodates(.allowMinus){
            //minus
            x += "-32768...-1, 1...32767"
        }
        else {
            x += "1...32767"
        }
        
        guard x != "Vary: " else { fatalError() }
        return x
        
    }
}

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

Thanks

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles