-1

I have a SwiftUI app that is targeting iOS 14 as well. I want to use the focus state only for the iOS 15 version. I don't mind much about the iOS 14 version. As focusState is only available for iOS 15.

@FocusState private var isUsernameFocused: Bool = false

I tried to define with #available but it is not working. Again, I just want to use it with iOS 15 machines, but I need a way to declare it.

I want to use it on a press of a button. Whenever I press the button, I want to active the keyboard for the text field. How can I do this on SwiftUI?

dhaval123
  • 107
  • 11
  • If you don't need to support iOS 14, have you tried changing your app's minimum deployment target to iOS 15? It would be easier than adding those #available conditionals everywhere – Arjun Mar 12 '23 at 19:53
  • I have multiple code that runs on iOS 14, like view modifiers, but for this focus state, I do not need it – dhaval123 Mar 12 '23 at 20:16
  • You may be able to write a wrapper around the focus state and erase the need for backward-compatibility – Mojtaba Hosseini Mar 12 '23 at 23:32

3 Answers3

3

For automatic call of focus:

Use ViewModifier for custom case iOS 14/15+ in work with FocusState on iOS 15+.

In iOS 14 focused don't work with FocusState.

import SwiftUI

@available(iOS 15.0, *)
private struct TextFieldFocused: ViewModifier {
    
    @FocusState private var focused: Bool
    
    init() {
        self.focused = false
    }
    
    func body(content: Content) -> some View {
        content
            .focused($focused)
            .onAppear {
                focused = true
            }
    }
}

extension View {
    
    @ViewBuilder
    func focused() -> some View {
        if #available(iOS 15.0, *) {
            self.modifier(TextFieldFocused())
        } else {
            self
        }
    }
}

In your view any text fields:

TextField("Enter text", text: .constant(""))
    .focused()

For manually call of focus:

import SwiftUI

@available(iOS 15.0, *)
private struct TextFieldFocused: ViewModifier {
    
    @FocusState private var focused: Bool
    @Binding private var externalFocused: Bool
    
    init(externalFocused: Binding<Bool>) {
        self._externalFocused = externalFocused
        self.focused = externalFocused.wrappedValue
    }
    
    func body(content: Content) -> some View {
        content
            .focused($focused)
            .onChange(of: externalFocused) { newValue in
                focused = newValue
            }
            .onChange(of: focused) { newValue in
                externalFocused = newValue
            }
            .onAppear {
                if externalFocused {
                    focused = true
                }
            }
    }
}

extension View {
    
    @ViewBuilder
    func focused(_ value: Binding<Bool>) -> some View {
        if #available(iOS 15.0, *) {
            self.modifier(TextFieldFocused(externalFocused: value))
        } else {
            self
        }
    }
}

In your view:

@State var isFocused: Bool = false
    
var body: some View {
    TextField("Enter text", text: .constant(""))
        .focused($isFocused)
}
Andrii
  • 31
  • 4
0

You didn't say how you want to use it, but generally the approach can be like this:

  1. Create a View, which will be compatible with iOS 15 only and will contain @FocusState, as well as encapsulate any behaviors specific to the focus (they can be predefined or passed in on init):
struct FocusableUsername: View {
    @FocusState private var isUsernameFocused: Bool
    @State private var username = "Anonymous"

    var body: some View {
        VStack {
            TextField("Enter your username", text: $username)
                .focused($isUsernameFocused)

            Button("Toggle Focus") {
                isUsernameFocused.toggle()
            }
        }
    }
}
  1. Now in your main view, use if #available(iOS 15, * to show this FocusableUsername, or just a regular `TextField (or whatever the component you are using):
struct MainView: View {
    var body: some View {
        if #available(iOS 15, *) {
            FocusableUsername()
        } else {
            TextField(...)
        }
    }
}

Of course you may need to control the behavior of this field from outside, so you can pass a main view as a delegate to it for example, or pass some closures to run - many options there.

timbre timbre
  • 12,648
  • 10
  • 46
  • 77
  • This is the only way? Can t I keep all in the same view? just declare the state focus for one ios version? – dhaval123 Mar 12 '23 at 20:58
  • I want to enable/call the focus from the main view, Is that possible? – dhaval123 Mar 12 '23 at 21:13
  • @dhaval123 you can, and there are many options to do that - passing a focus state on `FocusableUsername.init`, having a ViewModel for `FocusableUsername` (and many more). But you need to update your question with enough details so your use case is more clear. – timbre timbre Mar 12 '23 at 22:37
  • I did update the question. I want to active the text field with the press of a button – dhaval123 Mar 12 '23 at 23:34
0

This is what I came up with:

public func autoFocusTextField(
    title: String,
    text: Binding<String>,
    lineLimit: Int,
    multilineTextAlignment: TextAlignment
) -> some View {
    if #available(iOS 15, *) {
        return AutoFocusTextField(
            title: title,
            isFocused: FocusState(),
            text: text,
            lineLimit: lineLimit,
            multilineTextAlignment: multilineTextAlignment
        )
    } else {
        return TextField(title, text: text)
            .lineLimit(lineLimit)
            .multilineTextAlignment(multilineTextAlignment)
    }
}

@available(iOS 15.0, *)
struct AutoFocusTextField: View {
    let title: String
    @FocusState var isFocused: Bool
    @Binding var text: String
    let lineLimit: Int
    let multilineTextAlignment: TextAlignment

    var body: some View {
        TextField(title, text: $text)
            .focused($isFocused)
            .lineLimit(lineLimit)
            .multilineTextAlignment(multilineTextAlignment)
            .onAppear {
                isFocused = true
            }
    }
}

From your main view you'd call autoFocusTextField with the required arguments (obviously, you can tweak them).

Niklas
  • 23,674
  • 33
  • 131
  • 170