iosswiftuitvos

TextField customization for tvOS in SwiftUI


I wanted to customize looks of a TextField in tvOS but I can't seem to figure out way to alter default look of textfield when it's focused.

struct ContentView: View {
    @State var name: String = ""
    @State var address: String = ""
    var body: some View {
        VStack {
            TextField("Name", text: $name)
                .textFieldStyle(OutlinedTextFieldStyle())
            TextField("Address", text: $address)
        }
        
        .padding(200.0)
        .background(.white)
        .preferredColorScheme(.light)
    }
}

struct OutlinedTextFieldStyle: TextFieldStyle {
    @FocusState var isFocused: Bool
    func _body(configuration: TextField<Self._Label>) -> some View {
        configuration
            .focused($isFocused)
            .background(.clear)
            .overlay {
                RoundedRectangle(cornerRadius: 8, style: .continuous)
                    .stroke(.gray, lineWidth: isFocused ? 4 : 2)
            }
    }
}

This is the code I tried and here is the output:

Output

As you can see when I focus on the textfield by default TextField expands and shadow appears. I want to remove this effect and add some more customization when TextField is focused. How can I achieve that?


Solution

  • This is related to the hoverEffect which is not much customizable in tvOS.

    But here is a close try:

    func _body(configuration: TextField<Self._Label>) -> some View {
        let shape = RoundedRectangle(cornerRadius: 8, style: .continuous)
    
        configuration
            .focused($isFocused)
            .defaultHoverEffect(nil) // 👈 Suppress the lifting effect
            .background(.clear)
            .padding(.horizontal, isFocused ? 16 : 0) // 👈 Suppress the padding effect 
            .overlay {
                shape.strokeBorder(.gray, lineWidth: isFocused ? 4 : 2)
            }
            .mask(shape) // 👈 Suppress the default shadow
    }
    

    Demo

    Also, if you don't like the padding animation, you can:

    TextField("", text: $name)
        .textFieldStyle(OutlinedTextFieldStyle(placeholder: "Name", show: name.isEmpty))
    
    .overlay(alignment: .leading) {
        Text(placeholder)
            .padding()
            .foregroundStyle(.gray)
            .opacity(show ? 1 : 0)
        }
    

    Note this is a work around and has side effects like loosing the title of the field in the edit screen and etc.