uikit-textfield
uikit-textfield copied to clipboard
SwiftUI view to make using UITextField a breeze
uikit-textfield
uikit-textfield
offers UIKitTextField
which makes using UITextField
in SwiftUI a breeze.
From data binding, focus binding, handling callbacks from UITextFieldDelegate
,
custom UITextField
subclass, horizontal/vertical stretching, inputView
/inputAccessoryView
,
to extra configuration, UIKitTextField
is the complete solution.
- Installation
-
Usage
-
Data Binding
- Text
- Formatted Value
- Custom Data Binding
- Placeholder
- Font
- Text Color
- Clear on Begin Editing
- Clear on Insertion
- Clear Button Mode
-
Focus Binding
- Bool
- Hashable
- Stretching
- Input View / Input Accessory View
- Custom Text Field Class
- Extra Configuration
- UITextFieldDelegate
- UITextInputTraits
-
Data Binding
- Examples
- Additional Notes
Installation
To install through Xcode, follow the official guide to add the following your Xcode project
https://github.com/vinceplusplus/uikit-textfield.git
To install through Swift Package Manager, add the following as package dependency and target dependency respectively
.package(url: "https://github.com/vinceplusplus/uikit-textfield.git", from: "2.0")
.product(name: "UIKitTextField", package: "uikit-textfield")
Usage
All configurations are done using UIKitTextField.Configuration
and are in turn passed to
UIKitTextField
's .init(config: )
Data binding
Text
func value(text: Binding<String>) -> Self
@State var name: String = ""
var body: some View {
VStack(alignment: .leading) {
UIKitTextField(
config: .init()
.value(text: $name)
)
.border(Color.black)
Text("\(name.isEmpty ? "Please enter your name above" : "Hello \(name)")")
}
.padding()
}

Formatted value
@available(iOS 15.0, *)
func value<F>(value: Binding<F.FormatInput>, format: F) -> Self
where
F: ParseableFormatStyle,
F.FormatOutput == String
@available(iOS 15.0, *)
func value<F>(value: Binding<F.FormatInput?>, format: F) -> Self
where
F: ParseableFormatStyle,
F.FormatOutput == String
func value<V>(value: Binding<V>, formatter: Formatter) -> Self
func value<V>(value: Binding<V?>, formatter: Formatter) -> Self
When the text field is not the first responder, it will take value from the binding and display in the specified formatted way
When the text field is the first responder, every change will try to update the binding. If it's a binding of a non optional value,
an invalid input will preserve the last value. If it's a binding of an optional value, an invalid input will set the value to nil
.
@State var value: Int = 0
// ...
Text("Enter a number:")
UIKitTextField(
config: .init()
.value(value: $value, format: .number)
//.value(value: $value, formatter: NumberFormatter())
)
.border(Color.black)
// NOTE: avoiding the formatting behavior which comes from Text()
Text("Your input: \("\(value)")")

Custom Data Binding
func value(
updateViewValue: @escaping (_ textField: UITextFieldType) -> Void,
onViewValueChanged: @escaping (_ textField: UITextFieldType) -> Void
) -> Self
Placeholder
func placeholder(_ placeholder: String?) -> Self
UIKitTextField(
config: .init()
.placeholder("some placeholder...")
)
Font
func font(_ font: UIFont?) -> Self
Note that since there are no ways to convert back from a Font
to UIFont
, the configuration
can only take a UIFont
UIKitTextField(
config: .init()
.font(.systemFont(ofSize: 16))
)
Text Color
func textColor(_ color: Color?) -> Self
UIKitTextField(
config: .init()
.textColor(.red)
)
Text Alignment
func textAlignment(_ textAlignment: NSTextAlignment?) -> Self
UIKitTextField(
config: .init()
.textAlignment(.center)
)
Clear on Begin Editing
func clearsOnBeginEditing(_ clearsOnBeginEditing: Bool?) -> Self
UIKitTextField(
config: .init()
.clearsOnBeginEditing(true)
)
Clear on Insertion
func clearsOnInsertion(_ clearsOnInsertion: Bool?) -> Self
UIKitTextField(
config: .init()
.clearsOnInsertion(true)
)
Clear Button Mode
func clearButtonMode(_ clearButtonMode: UITextField.ViewMode?) -> Self
UIKitTextField(
config: .init()
.clearButtonMode(.always)
)
Focus Binding
Similar to @FocusState
, we could use an orindary @State
to do a 2 way focus binding
Bool
func focused(_ binding: Binding<Bool>) -> Self
@State var name = ""
@State var isFocused = false
VStack {
Text("Your name:")
UIKitTextField(
config: .init()
.value(text: $name)
.focused($isFocused)
)
Button {
if name.isEmpty {
isFocused = true
} else {
isFocused = false
}
} label: {
Text("Submit")
}
}
Hashable
func focused<Value>(_ binding: Binding<Value?>, equals value: Value?) -> Self where Value: Hashable
enum Field {
case firstName
case lastName
}
@State var firstName = ""
@State var lastName = ""
@State var focusedField: Field?
VStack {
Text("First Name:")
UIKitTextField(
config: .init()
.value(text: $firstName)
.focused(focusedField, equals: .firstName)
)
Text("Last Name:")
UIKitTextField(
config: .init()
.value(text: $lastName)
.focused(focusedField, equals: .lastName)
)
Button {
if firstName.isEmpty {
focusedField = .firstName
} else if lastName.isEmpty {
focusedField = .lastName
} else {
focusedField = nil
}
} label: {
Text("Submit")
}
}
Stretching
By default, UIKitTextField
will stretch horizontally but not vertically
func stretches(horizontal: Bool, vertical: Bool) -> Self
UIKitTextField(
config: .init {
PaddedTextField()
}
.stretches(horizontal: true, vertical: false)
)
.border(Color.black)
Note that PaddedTextField
is just a simple internally padded UITextField
, see more in custom init

UIKitTextField(
config: .init {
PaddedTextField()
}
.stretches(horizontal: false, vertical: false)
)
.border(Color.black)

Input View / Input Accessory View
Supporting UITextField.inputView
and UITextField.inputAccessoryView
by accepting a user defined SwiftUI
view for each of them
func inputView(content: InputViewContent<UITextFieldType>) -> Self
func inputAccessoryView(content: InputViewContent<UITextFieldType>) -> Self
VStack(alignment: .leading) {
UIKitTextField(
config: .init {
PaddedTextField()
}
.placeholder("Enter your expression")
.value(text: $expression)
.focused($isFocused)
.inputView(content: .view { uiTextField in
KeyPad(uiTextField: uiTextField, onEvaluate: onEvaluate)
})
.shouldReturn { _ in
onEvaluate()
return false
}
)
.padding(4)
.border(Color.black)
Text("Result: \(result)")
Divider()
Button {
isFocused = false
} label: {
Text("Dismiss")
}
}
.padding()
Implementation of KeyPad
can be found in InputViewPage
from the example code. But the idea is to accept a UITextField
parameter and render some buttons that do uiTextField.insertText("...")
or uiTextField.deleteBackward()
, like the
following:
struct CustomKeyboard: View {
let uiTextField: UITextField
var body: some View {
VStack {
HStack {
Button {
uiTextField.insertText("1")
} label: {
Text("1")
}
Button {
uiTextField.insertText("2")
} label: {
Text("2")
}
Button {
uiTextField.insertText("3")
} label: {
Text("3")
}
}
HStack { /* ... */ }
// ...
}
}
}

Custom Text Field Class
init(_ makeUITextField: @escaping () -> UITextFieldType)
A common use case of a UITextField
subclass is to provide some internal padding which is also tappable. The following
example demonstrates some extra leading padding to accomodate even an icon image
class CustomTextField: BaseUITextField {
let padding = UIEdgeInsets(top: 4, left: 8 + 32 + 8, bottom: 4, right: 8)
public override func textRect(forBounds bounds: CGRect) -> CGRect {
super.textRect(forBounds: bounds).inset(by: padding)
}
public override func editingRect(forBounds bounds: CGRect) -> CGRect {
super.editingRect(forBounds: bounds).inset(by: padding)
}
}
UIKitTextField(
config: .init {
CustomTextField()
}
.focused($isFocused)
.textContentType(.emailAddress)
.keyboardType(.emailAddress)
.autocapitalizationType(UITextAutocapitalizationType.none)
.autocorrectionType(.no)
)
.background(alignment: .leading) {
HStack(spacing: 0) {
Color.clear.frame(width: 8)
ZStack {
Image(systemName: "mail")
}
.frame(width: 32)
}
}
.border(Color.black)

UITextFieldType
needs to conform to UITextFieldProtocol
which is shown below:
public protocol UITextFieldProtocol: UITextField {
var inputViewController: UIInputViewController? { get set }
var inputAccessoryViewController: UIInputViewController? { get set }
}
Basically, it needs have inputViewController
and inputAccessoryViewController
writable so the support
for custom input view and custom input accessory view will work
For most use cases, BaseUITextField
, which provides baseline implementation of UITextFieldProtocol
,
can be subclassed to add more user defined behavior
Extra Configuration
func configure(handler: @escaping (_ uiTextField: UITextFieldType) -> Void) -> Self
If there are configurations that UIKitTextField
doesn't support out of the box, this is the place where we could add them.
The extra configuration will be executed at the end of updateUIView()
after applying all supported configuration (like data binding, etc)
class PaddedTextField: BaseUITextField {
var padding = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8) {
didSet {
setNeedsLayout()
}
}
public override func textRect(forBounds bounds: CGRect) -> CGRect {
super.textRect(forBounds: bounds).inset(by: padding)
}
public override func editingRect(forBounds bounds: CGRect) -> CGRect {
super.editingRect(forBounds: bounds).inset(by: padding)
}
}
@State var text = "some text..."
@State var pads = true
var body: some View {
VStack {
Toggle("Padding", isOn: $pads)
UIKitTextField(
config: .init {
PaddedTextField()
}
.value(text: $text)
.configure { uiTextField in
uiTextField.padding = pads ? .init(top: 4, left: 8, bottom: 4, right: 8) : .zero
}
)
.border(Color.black)
}
.padding()
}
The above example provides a button to toggle internal padding


UITextFieldDelegate
UITextFieldDelegate
is fully supported
func shouldBeginEditing(handler: @escaping (_ uiTextField: UITextFieldType) -> Bool) -> Self
func onBeganEditing(handler: @escaping (_ uiTextField: UITextFieldType) -> Void) -> Self
func shouldEndEditing(handler: @escaping (_ uiTextField: UITextFieldType) -> Bool) -> Self
func onEndedEditing(handler: @escaping (_ uiTextField: UITextFieldType, _ reason: UITextField.DidEndEditingReason) -> Void) -> Self
func shouldChangeCharacters(handler: @escaping (_ uiTextField: UITextFieldType, _ range: NSRange, _ replacementString: String) -> Bool) -> Self
func onChangedSelection(handler: @escaping (_ uiTextField: UITextFieldType) -> Void) -> Self
func shouldClear(handler: @escaping (_ uiTextField: UITextFieldType) -> Bool) -> Self
func shouldReturn(handler: @escaping (_ uiTextField: UITextFieldType) -> Bool) -> Self
UITextInputTraits
Most of the commonly used parts of UITextInputTraits
are supported
func keyboardType(_ keyboardType: UIKeyboardType?) -> Self
func keyboardAppearance(_ keyboardAppearance: UIKeyboardAppearance?) -> Self
func returnKeyType(_ returnKeyType: UIReturnKeyType?) -> Self
func textContentType(_ textContentType: UITextContentType?) -> Self
func isSecureTextEntry(_ isSecureTextEntry: Bool?) -> Self
func enablesReturnKeyAutomatically(_ enablesReturnKeyAutomatically: Bool?) -> Self
func autocapitalizationType(_ autocapitalizationType: UITextAutocapitalizationType?) -> Self
func autocorrectionType(_ autocorrectionType: UITextAutocorrectionType?) -> Self
func spellCheckingType(_ spellCheckingType: UITextSpellCheckingType?) -> Self
func smartQuotesType(_ smartQuotesType: UITextSmartQuotesType?) -> Self
func smartDashesType(_ smartDashesType: UITextSmartDashesType?) -> Self
func smartInsertDeleteType(_ smartInsertDeleteType: UITextSmartInsertDeleteType?) -> Self
Examples
Examples/Example
is an example app that demonstrate how to use UIKitTextField
Additional Notes
UITextField.isEnabled
is actually supported by the vanilla .disabled(/* ... */)
which might not be very obvious