Skip to content

Instantly share code, notes, and snippets.

@iampatbrown
Created April 17, 2022 22:51
import SwiftUI
protocol AnyStateOrBinding {
var anyWrappedValue: Any? { get nonmutating set }
}
extension State: AnyStateOrBinding {
var anyWrappedValue: Any? {
get { wrappedValue }
nonmutating set {
guard let newValue = newValue as? Value else { return }
wrappedValue = newValue
}
}
}
extension Binding: AnyStateOrBinding {
var anyWrappedValue: Any? {
get { wrappedValue }
nonmutating set {
guard let newValue = newValue as? Value else { return }
wrappedValue = newValue
}
}
}
extension TextField {
func fixed() -> some View {
FixedTextField(textField: self)
}
}
// FIXME: This forces FixedTextField to update when stateOrBinding is State<TextFieldState>
// maybe this can be avoided some how though...
extension TextField: DynamicProperty {}
struct FixedTextField<Label: View>: View {
let textField: TextField<Label>
@State private var lastGoodState: Any?
@State private var requiresCheck: Bool = false
@State private var didSetState = false
var body: some View {
textField
// NB: .onChange(of:) ordering is important for macOS
.onChange(of: requiresCheck) { didCheckState in
self.requiresCheck = false
guard
!didCheckState,
self.text != self.displayText,
let state = self.lastGoodState
else { return }
self.stateOrBinding?.anyWrappedValue = state
self.didSetState = true
}
.onChange(of: displayText) { newText in
if self.displayText == self.text {
self.lastGoodState = self.state
}
self.requiresCheck = !self.didSetState
self.didSetState = false
}
}
var text: String? {
Mirror(reflecting: textField).descendant("_text", "_value") as? String
}
var displayText: String? {
state.flatMap { Mirror(reflecting: $0).descendant("displayText") as? String }
}
var state: Any? {
stateOrBinding.flatMap { Mirror(reflecting: $0).descendant("_value") }
}
var stateOrBinding: AnyStateOrBinding? {
Mirror(reflecting: textField).descendant("_state", "state") as? AnyStateOrBinding
?? Mirror(reflecting: textField).descendant("_state", "binding") as? AnyStateOrBinding
}
}
struct FixedTextFieldStyle<Base: TextFieldStyle>: TextFieldStyle {
let base: Base
func _body(configuration: TextField<_Label>) -> some View {
configuration.fixed().textFieldStyle(base)
}
}
extension TextFieldStyle where Self == FixedTextFieldStyle<DefaultTextFieldStyle> {
static var fixed: Self { .init(base: .automatic) }
}
extension TextFieldStyle {
static func fixed<Base: TextFieldStyle>(_ base: Base) -> FixedTextFieldStyle<Base>
where Self == FixedTextFieldStyle<Base> {
.init(base: base)
}
}
struct ContentView: View {
@State var text = ""
var body: some View {
HStack {
TextField("Type here", text: Binding { text } set: { text = String($0.prefix(5)) })
.fixed() // stateOrBinding is State<TextFieldState>
Text(text)
}
.padding()
// .textFieldStyle(.fixed(.roundedBorder)) // stateOrBinding == Binding<TextFieldState>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment