A powerful, fluidly animated, and compact custom textfield for SwiftUI
Sheathed-TextField-SwiftUI
The pen is mightier than the sword!
This custom textfield is a very unique and fluid centerpiece for any form or search menu built using SwiftUI. It was designed to be as eye-catching and functional as possible, every element of this textfield adds not just stylistic value, but purpose as well. From the name you’d be right to guess the naming convention was derived from swords, as the transition of the textfield resembles the unsheathing of a sword; high precision, flamboyant, and purposeful; you’re uncovering a tool of great importance, a gateway of personalization for your application.
Supported Platforms:
- iOS
Minimum Required iOS Version:
- iOS 16
Minimum Required Xcode / Swift Version to build this package:
- Xcode 14 / Swift: 5.7
Installation:
- Launch your target Xcode project
- Go to the menu bar
- Click
File -> Add Packages Packages -> Search bar
- Enter the URL of this repository
https://github.com/jcook03266/Sheathed-TextField-SwiftUI/
- Set the dependency rule to
Up to Next Major Version
- Press the
Add Package
button at the bottom of the window. - Done!
How to use this package in your code:
- At the top of the swift files in which this textfield will be referenced, add:
import Sheathed_TextField_SwiftUI
- Define a
SheathedTextFieldModel
- Configure the model using the built in
configurator
function - Define a
SheathedTextField
view and insert the configured model into that view - Install the
SheathedTextField
inside of a parent view, Note: The textfield is already centered, and it has a device appropriate width you can also specify in the view’sinit
constructor
Example:
View Model:
import SwiftUI
import Sheathed_TextField_SwiftUI
class LoginScreenViewModel: ObservableObject {
// MARK: - Published / TextField Models
@Published var username_emailTextFieldModel: SheathedTextFieldModel!
@Published var passwordTextFieldModel: SheathedTextFieldModel!
...
init(...) {
setModels()
}
func setModels() {
username_emailTextFieldModel = .init()
username_emailTextFieldModel.configurator { [weak self] model in
guard let self = self else { return }
model.title = self.username_emailTextFieldTitle
model.placeholderText = "user123 / sampleEmail@Test.com"
model.icon = Icons.getIconImage(named: .person_fill)
model.keyboardType = .emailAddress
model.textContentType = .emailAddress
model.submitLabel = .next
model.onSubmitAction = { [weak self] in
guard let self = self else { return }
self.passwordTextFieldModel.focus()
}
}
passwordTextFieldModel = .init()
passwordTextFieldModel.configurator { [weak self] model in
guard let self = self else { return }
model.title = self.passwordTextFieldTitle
model.placeholderText = "Cr3&TiV3Password!23"
model.icon = Icons.getIconImage(named: .lock_fill)
model.keyboardType = .asciiCapable
model.textContentType = .password
model.submitLabel = .done
// Validation
model.entryValidationEnabled = true
model.validationCondition = { text in
!text.contains {
$0 == "."
}
}
model.inFieldButtonIcon = Icons.getIconImage(named: .eye_slash)
model.protected = true
model.inFieldButtonAction = {
model.protected.toggle()
model.inFieldButtonIcon = model.protected ? Icons.getIconImage(named: .eye_slash) : Icons.getIconImage(named: .eye)
}
}
}
View:
import SwiftUI
import Sheathed_TextField_SwiftUI
// MARK: - Observed
@StateObject var model: LoginScreenViewModel
...
struct LoginScreen: View {
// TextFields
var username_EmailTextField: some View {
let textField = SheathedTextField(model: model.username_emailTextFieldModel)
return textField
}
var password_TextField: some View {
let textField = SheathedTextField(model: model.passwordTextFieldModel)
return textField
}
var textFields: some View {
VStack(spacing: textFieldSpacing) {
username_EmailTextField
password_TextField
}
}
var body: some View {
GeometryReader { geom in
ScrollView {
VStack {
...
textFields
}
.frame(width: geom.size.width)
...
}
.submitScope(true) // Note: Prevents submissions from this page from backtracking and triggering other submissions from separate views in the hierarchy
...
}
}
...
}
Brief Overview of necessary property wrappers:
Sheathed-TextField
models must be marked asPublished
when embedded in a view model, orStateObject
when they’re embedded in aView
- In order for the parent view to respond to changes in this textfield, new events have to be emitted by a
Publisher
and broadcasted through anObservableObject
, or listened to as an observable object by a view, namely as a state object to prevent a loss of state when the view reloads given a new value emitted by the model - It’s not recommended to embed the textfield model directly into a view, for lack of simplicity. The model should be housed in a view model in order to centralize all emissions from important objects currently present in the scene.
- Adhere to the functionality of SwiftUI property wrappers and you’re good to go!
Extended Information / Q&A:
How do I access the text I enter?
[textFieldModel].textEntry
How can I focus the text field from an external source?
[textFieldModel].focus()
Why does my text clear upon editing past content when the textfield is marked as protected?
This is expected behavior because a secure text field clears past text when you go back to edit it.
How do I validate a text entry?
Specify a validation condition to be executed every time the text entry changes, and set entryValidationEnabled to True
to enable validation
[textFieldModel].entryValidationEnabled = true
// This is just an example, use REGEX to properly validate the text input in this closure
[textFieldModel].validationCondition = { text in
!text.contains {
$0 == "." || $0 == "/"
}
}
How do I trigger an ‘unsheathe’ when an icon view isn’t present / when I don’t define a side icon?
You can trigger an unsheathe action at any time by long pressing the textfield, this is also possible when an icon is present. When the icon view is rendered you can simply tap on it to trigger this event, but, this same functionality isn’t available for the textfield component of the view because all touches will be eaten up by the onTap
listener.
Demonstration
Here’s a demo of a login form built using two sheathed textfields in a VStack. Auto-fill functionality works as expected with this custom textfield.
Demo.mp4
Support
If you really enjoyed this simple library, then consider checking out my other work, and or supporting me through my sponsor links on the side menu. Thanks for reading; keep calm and code on! <3