Skip to content

Instantly share code, notes, and snippets.

@allenhumphreys
Last active October 11, 2022 19:34
Show Gist options
  • Save allenhumphreys/b6da04f6123bb7b794794d12dc5f7506 to your computer and use it in GitHub Desktop.
Save allenhumphreys/b6da04f6123bb7b794794d12dc5f7506 to your computer and use it in GitHub Desktop.
SwiftUI Focus 2-way Binding
protocol FocusTracking<FocusField>: AnyObject {
associatedtype FocusField: Hashable
var focusedField: FocusField? { get set }
}
struct FocusTrackingView<Content, Target: FocusTracking>: View where Content: View, Target.FocusField: Hashable {
@FocusState.Binding var focusedField: Target.FocusField?
let content: Content
let boundTo: Target
let defaultValue: Target.FocusField?
let defaultValueDelay: Double?
@inlinable public init(
focusedFieldBinding: FocusState<Target.FocusField?>.Binding,
boundTo: Target,
defaultValue: Target.FocusField? = nil,
defaultValueDelay: Double? = nil,
@ViewBuilder content: () -> Content
) {
_focusedField = focusedFieldBinding
self.boundTo = boundTo
self.defaultValue = defaultValue
self.defaultValueDelay = defaultValueDelay
self.content = content()
}
var body: some View {
content
.onAppear {
guard let defaultValue = defaultValue else { return }
let setDefault = {
focusedField = defaultValue
}
// Delay is needed otherwise the focus won't actually be applied
if let delay = defaultValueDelay {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: setDefault)
} else {
setDefault()
}
}
.onChange(of: boundTo.focusedField) { newValue in
focusedField = newValue
}
.onChange(of: focusedField) { newValue in
boundTo.focusedField = newValue
}
}
}
extension View {
func bindFocus<T: FocusTracking>(
from: FocusState<T.FocusField?>.Binding,
to: T, defaultFocus: T.FocusField? = nil, // swiftlint:disable:this identifier_name
defaultValueDelay: Double? = nil
) -> some View {
FocusTrackingView(focusedFieldBinding: from, boundTo: to, defaultValue: defaultFocus, defaultValueDelay: defaultValueDelay) {
self
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment