1

I'm trying to focus the isUsernameFocused textField as soon as it loads on the screen, I tried doing it directly in the onAppear method but it looks like it needs a delay in order for it to focus. My concern is that for some reason the focus only occurs with a delay greater than 0.6 fractions of a second. Setting it at 0.7 fractions of a second seems to work fine but I'm afraid that eventually, this will stop working if the view gets bigger since it will need more time to load.

Is there a way to know when the VStack is fully loaded so I can trigger the isUsernameFocused? Something like, viewDidLoad in UIKit.

struct ContentView: View {
    @FocusState private var isUsernameFocused: Bool
    @State private var username = ""

    var body: some View {
        VStack {
            TextField("Username", text: $username)
                .focused($isUsernameFocused)
        }
        .onAppear{
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.7){
                self.isUsernameFocused = true
            }
        }
    }
}
fs_tigre
  • 10,650
  • 13
  • 73
  • 146
  • 1
    Due to its nature SwiftUI does not give exact info about this. Use injected UIKit-based viewDidAppear. This can be helpful https://stackoverflow.com/a/59746380/12299030. – Asperi Apr 22 '22 at 13:12
  • Interesting, I'll give it a try. Thank you! – fs_tigre Apr 22 '22 at 13:23
  • BTW, UIKit viewDidLoad is the same as in SwiftUI view creating, ie equivalent is just ContentView() – Asperi Apr 22 '22 at 13:23
  • this is not necessary because SwiftUI has its own life cycle, you can read about it [here](https://www.hackingwithswift.com/quick-start/swiftui/how-to-respond-to-view-lifecycle-events-onappear-and-ondisappear) – sergio_veliz Apr 22 '22 at 13:37
  • What's not necessary? There is no `onLoad` method in SwiftUI. As @Asperi said, due to the nature of SwiftUI the onLoad method is not available in SwiftUI. – fs_tigre Apr 22 '22 at 15:00

1 Answers1

0

If you are on macOS or tvOS you can use prefersDefaultFocus for that. It should come to iOS in June.

In the meantime, I just created this example that works around the issue. If your form appears multiple times you might want to check other values before setting the focus.

import SwiftUI
import UIKit

struct FocusTestView : View {
    @State var presented = false
    var body: some View {
        Button("Click Me") {
            presented = true
        }
        .sheet(isPresented: $presented) {
            LoginForm()
        }
    }
}

struct LoginForm : View {
    enum Field: Hashable {
        case usernameField
        case passwordField
    }

    @State private var username = ""
    @State private var password = ""
    @FocusState private var focusedField: Field?
    
    var body: some View {
        Form {
            TextField("Username", text: $username)
                .focused($focusedField, equals: .usernameField)

            SecureField("Password", text: $password)
                .focused($focusedField, equals: .passwordField)

            Button("Sign In") {
                if username.isEmpty {
                    focusedField = .usernameField
                } else if password.isEmpty {
                    focusedField = .passwordField
                } else {
                //    handleLogin(username, password)
                }
            }
            
        }
        .uiKitOnAppear {
            focusedField = .usernameField
        }
    }
}

struct UIKitAppear: UIViewControllerRepresentable {
    let action: () -> Void
    func makeUIViewController(context: Context) -> UIAppearViewController {
       let vc = UIAppearViewController()
        vc.action = action
        return vc
    }
    func updateUIViewController(_ controller: UIAppearViewController, context: Context) {
    }
}

class UIAppearViewController: UIViewController {
    var action: () -> Void = {}
    override func viewDidLoad() {
        view.addSubview(UILabel())
    }
    override func viewDidAppear(_ animated: Bool) {
        DispatchQueue.main.asyncAfter(deadline:.now()) { [weak self] in
            self?.action()
        }
        
    }
}
public extension View {
    func uiKitOnAppear(_ perform: @escaping () -> Void) -> some View {
        self.background(UIKitAppear(action: perform))
    }
}

UIKitAppear was taken from dev forum post and I added the dispatch async to call the action. LoginForm is from the docs on FocusState.

malhal
  • 26,330
  • 7
  • 115
  • 133
  • @ malhal - How does that differ from the existing `.focused()` method? Would it focus as soon as the parent view load or how would that work? Sorry if I'm not seeing the obvious. – fs_tigre Apr 28 '22 at 12:19
  • I added a workaround you could try – malhal Apr 28 '22 at 17:32