Reputation: 3095
I am excited to see the TextField enhancement: focused(...): https://developer.apple.com/documentation/swiftui/view/focused(_:)
I want to use it to show a very simple SwitfUI view that contains only one TextField that has the focus with keyboard open immediately. Not able to get it work:
struct EditTextView: View {
@FocusState private var isFocused: Bool
@State private var name = "test"
// ...
var body: some View {
NavigationView {
VStack {
HStack {
TextField("Enter your name", text: $name).focused($isFocused)
.onAppear {
isFocused = true
}
// ...
Anything wrong? I have trouble to give it default value.
Upvotes: 24
Views: 12883
Reputation: 15579
I was also not able to get this work on Xcode 13, beta 5. To fix, I delayed the call to isFocused = true
. That worked!
The theory I have behind the bug is that at the time of onAppear
the TextField is not ready to become first responder, so isFocused = true
and iOS calls becomeFirstResponder
behind the scenes, but it fails (ex. the view hierarchy is not yet done setting up).
struct MyView: View {
@State var text: String
@FocusState private var isFocused: Bool
var body: some View {
Form {
TextEditor(text: $text)
.focused($isFocused)
.onChange(of: isFocused) { isFocused in
// this will get called after the delay
}
.onAppear {
// key part: delay setting isFocused until after some-internal-iOS setup
DispatchQueue.main.asyncAfter(deadline: .now()+0.7) {
isFocused = true
}
}
}
}
}
Upvotes: 18
Reputation: 30561
I faced the same problem and had the idea to solve it by embedding a UIViewController so could use viewDidAppear. Here is a working example:
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
// If your form appears multiple times you might want to check other values before setting the focus.
}
}
}
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) {
// had to delay the action to make it work.
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 this dev forum post, modified with dispatch async to call the action. LoginForm
is from the docs on FocusState with the uiKitOnAppear
modifier added to set the initial focus state.
It could perhaps be improved by using a first responder method of the VC rather than the didAppear, then perhaps the dispatch async could be avoided.
Upvotes: 0
Reputation: 29
//This work in iOS 15.You can try it.
struct ContentView: View {
@FocusState private var isFocused: Bool
@State private var username = "Test"
var body: some View {
VStack {
TextField("Enter your username", text: $username)
.focused($isFocused).onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
isFocused = true
}
}
}
}
}
Upvotes: 2
Reputation: 382
I was also not able to get this work on Xcode 13, beta 5. To fix, I delayed the call to isFocused = true. That worked!
It also works without delay.
DispatchQueue.main.async {
isFocused = true
}
Upvotes: 4
Reputation: 31
I've had success adding the onAppear
to the outermost view (in your case NavigationView
):
struct EditTextView: View {
@FocusState private var isFocused: Bool
@State private var name = "test"
// ...
var body: some View {
NavigationView {
VStack {
HStack {
TextField("Enter your name", text: $name).focused($isFocused)
}
}
}
.onAppear {
isFocused = true
}
}
// ...
I’m not certain but perhaps your onAppear
attached to the TextField
isn’t running. I would suggest adding a print
inside of the onAppear
to confirm the code is executing.
Upvotes: 3