14 Posts under Mango Snippets

« Previous page Page 2 of 2

SwiftUI: How To Programmatically Make a TextField First Responder

Published under Mango Snippets, Oct 5, 2020

It's a common scenario to make a text field first responder when it first appears. A typical example is the login screen. As soon as it appears, you want the account field to be in focus and the keyboard to appear. Then the user can start typing right way.

SwiftUI in iOS 15 introduced a new property wrapper called @FocusState. It allows you to control which input has focus. It can be bound to a Bool or an enum:

struct ContentView: View {
    @FocusState private var focused: Bool
    @State private var name = "Mango Umbrella"

    var body: some View {
        VStack {
            TextField("Name", text: self.$name)
                .focused(self.$focused)
            Button("Focus on name") {
                self.focused = true
            }
        }
    }

But how can you make the text field focus as soon as the view appears? You can't assign an initial value to the property. Another thought is to change its value in .onAppear like this:

struct ContentView: View {
    @FocusState private var focused: Bool

    var body: some View {
        VStack {
            // ...
        }
        .onAppear {
            self.focused = true
        }
    }
}

This won't work unfortunately. It appears that @FocusState only works when the view has been rendered after some time. A not-so-good workaround is to add a delay:

struct ContentView: View {
    @FocusState private var focused: Bool

    var body: some View {
        VStack {
            // ...
        }
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                self.focused = true
            }
        }
    }
}

I don't recommend this approach though. The delay is arbitrary, and slower devices might need a longer delay. It isn't a great user experience anyway.

For this task, it's best falling back to UIKit's becomeFirstResponder(). It's easy to wrap a UITextField in a UIViewRepresentable like this:

struct MyTextField: UIViewRepresentable {
    typealias UIViewType = UITextField

    @Binding var becomeFirstResponder: Bool

    func makeUIView(context: Context) -> UITextField {
        return UITextField()
    }
    
    func updateUIView(_ textField: UITextField, context: Context) {
        if self.becomeFirstResponder {
            DispatchQueue.main.async {
                textField.becomeFirstResponder()
                self.becomeFirstResponder = false
            }
        }
    }
}

Notice the async call. It is necessary because it's modifying the state and it isn't allowed in updateUIView. If you forget, Xcode will warn you Modifying state during view update, this will cause undefined behavior.

To make it first responder when the view appears, just call it in .onAppear. And it works without a delay:

struct ContentView: View {
    @State private var becomeFirstResponder = false

    var body: some View {
        MyTextField(becomeFirstResponder: self.$becomeFirstResponder)
            .onAppear {
                self.becomeFirstResponder = true
            }
    }
}

TIP: If you use SwiftUI Instrospect, you can directly inspect the backing UITextField without your own wrapper:

struct ContentView: View {
    @State private var name = "Mango Umbrella"
    @State private var becomeFirstResponder = true

    var body: some View {
        TextField("Name", text: self.$name)
            .introspectTextField { textField in
                if self.becomeFirstResponder {
                    textField.becomeFirstResponder()
                    self.becomeFirstResponder = false
                }
            }
        }
    }
}

SwiftUI: How To Draw Borders With Rounded Rectangles

Published under Mango Snippets, Jul 23, 2020

In SwiftUI, it’s easy to find out there is a .border modifier, and a .cornerRadius modifier. However, those tools can’t create borders with rounded corners. Instead, you can use the .overlay modifier with a RoundedRectangle shape:

Text("DA Tips")
    .padding()
    .overlay(
        RoundedRectangle(cornerRadius: 16)
            .stroke(Color.pink, lineWidth: 2)
    )

Swift: How To Count Occurrences of a Character in a Swift String

Published under Mango Snippets, Jul 21, 2020

To count occurrences of a character in a Swift String, you can use Sequence.filter and then Array.count:

"Mango Snippets".filter({ $0 == "n"}).count

Output:

2

SwiftUI: Set a Max Width on Spacer()

Published under Mango Snippets, Oct 5, 0202

SwiftUI's Spacer() has a minLength parameter, but some times we also want a maxLength. There isn't one, but you can use .frame(maxWidth:) to achieve the same effect:

Spacer()
    .frame(maxWidth: 16)

« Previous page Page 2 of 2