Home > OS >  SwiftUI TextField loses focus when styled
SwiftUI TextField loses focus when styled

Time:09-06

I'm trying to apply some validation to a TextField to add a red border around the field when the content is invalid (in this case, I'm validating that the content is a positive number that is less than the specified maxLength).

The validation works fine and the border is applied when the value is out of range. However, when the border is applied to the TextField, the TextField loses focus in the UI (and also loses focus when the border is removed).

Here is a snippet of my code (I've included some extensions I'm using, but I don't think those are relevant to the issue)

import Foundation
import SwiftUI
import Combine

struct MyView : View {
    @Binding var value: Int
    var label: String
    var maxLength: Int
    
    @State private var valid: Bool = true
    
    var body: some View {
        TextField(label, value: $value, format: .number)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .fixedSize()
            .multilineTextAlignment(.trailing)
            .onReceive(Just(value), perform: validate)
            .if(!valid, transform: { $0.border(.red)})
    }
    
    func validate(val: Int) {
        let newVal = val.clamped(to: 0...maxLength)
        if newVal != val {
            valid = false
        } else {
            valid = true
        }
    }
}

extension View {
    @ViewBuilder
    func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View {
        if condition { transform(self) }
        else { self }
    }
}

extension Comparable {
    func clamped(to limits: ClosedRange<Self>) -> Self {
        return min(max(self, limits.lowerBound), limits.upperBound)
    }
}

Is there any way to preserve focus on the TextField when styles are conditionally applied to it?

Or am I approaching validation wrong altogether? Is there a better way to check fields and apply conditional styling?

CodePudding user response:

Because of the way your if modifier is structured, SwiftUI is unable to see that the underlying View is the same in the two conditions. For more detail on this, I'd suggest you watch Demystifying SwiftUI from WWDC 2021.

One solution is the simplify your border modifier into the following:

.border(valid ? .clear : .red)

This way, SwiftUI can still tell that this is the same underlying View.

  • Related