Skip to content

Instantly share code, notes, and snippets.

@ericlewis
Last active February 19, 2022 04:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericlewis/5362ab3634aa523214e25a3ece99dd4d to your computer and use it in GitHub Desktop.
Save ericlewis/5362ab3634aa523214e25a3ece99dd4d to your computer and use it in GitHub Desktop.
Experimental library for creating useful TextFieldStyles in SwiftUI.

Install via SPM per usual. Supports iOS 13+. MIT License.

import SwiftUI
import Introspect
public protocol BetterTextFieldStyle: TextFieldStyle {}
extension View {
public func textFieldStyle<S: BetterTextFieldStyle>(_ style: S) -> some View {
textFieldStyle(CombinedStyle(style))
}
}
@propertyWrapper
public struct TextFieldState<Value>: DynamicProperty {
@Environment(\.textFieldState)
private var state
private let keyPath: KeyPath<FieldState, Value>
public init() where Value == FieldState {
self.keyPath = \.self
}
public init(_ keyPath: KeyPath<FieldState, Value>) {
self.keyPath = keyPath
}
public var wrappedValue: Value {
get { state[keyPath: keyPath] }
}
}
public struct FieldState {
public var isEditing: Bool = false
public var text: Binding<String>
struct Key: EnvironmentKey {
static var defaultValue: FieldState = .init(text: .constant(""))
}
}
import SwiftUI
extension EnvironmentValues {
var textFieldState: FieldState {
get { self[FieldState.Key.self] }
set { self[FieldState.Key.self] = newValue }
}
}
struct CombinedStyle<S: TextFieldStyle>: TextFieldStyle {
let style: S
init(_ style: S) {
self.style = style
}
func _body(configuration: TextField<_Label>) -> some View {
configuration
.textFieldStyle(CoordinatorStyle())
.textFieldStyle(style)
}
}
struct CoordinatorStyle: TextFieldStyle {
@StateObject
private var coordinator = Coordinator()
func _body(configuration: TextField<_Label>) -> some View {
configuration
.environment(\.textFieldState, .init(
isEditing: coordinator.isEditing,
text: Mirror(reflecting: configuration).descendant("_text") as! Binding<String>)
)
.introspectTextField { textField in
coordinator.textField = textField
}
}
class Coordinator: NSObject, ObservableObject, UITextFieldDelegate {
@Published
private(set) var isEditing: Bool = false
weak var textField: UITextField? {
didSet {
if delegate == nil, let delegate = textField?.delegate {
self.delegate = delegate
textField?.delegate = self
}
}
}
weak var delegate: UITextFieldDelegate?
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
delegate?.textFieldShouldBeginEditing?(textField) ?? true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
isEditing = true
delegate?.textFieldDidBeginEditing?(textField)
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
delegate?.textFieldShouldEndEditing?(textField) ?? true
}
func textFieldDidEndEditing(_ textField: UITextField) {
isEditing = false
delegate?.textFieldDidEndEditing?(textField)
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
isEditing = false
delegate?.textFieldDidEndEditing?(textField, reason: reason)
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
delegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true
}
func textFieldDidChangeSelection(_ textField: UITextField) {
delegate?.textFieldDidChangeSelection?(textField)
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
delegate?.textFieldShouldClear?(textField) ?? false
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
delegate?.textFieldShouldReturn?(textField) ?? true
}
}
}
// swift-tools-version: 5.6
import PackageDescription
let package = Package(
name: "swiftui-better-textfieldstyle",
platforms: [.iOS(.v13)],
products: [
.library(
name: "BetterTextFieldStyle",
targets: ["BetterTextFieldStyle"]),
],
dependencies: [
.package(url: "https://github.com/siteline/SwiftUI-Introspect", from: "0.1.4")
],
targets: [
.target(
name: "BetterTextFieldStyle",
dependencies: [
.product(name: "Introspect", package: "swiftui-introspect")
],
path: ".",
exclude: ["Package.swift"]
)
]
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment