Files of iOS Keyboard & Input Patterns
wondelai/
keyboard-input.md
Show the full text467 lines
iOS Keyboard & Input Patterns
Comprehensive guide to keyboard handling, text input, and hardware keyboard support.
Table of Contents
- Software Keyboard Types
- Text Content Types
- Input Accessory Views
- Keyboard Avoidance
- Hardware Keyboard Support
- Text Editing
- Secure Text Entry
- Search Input
- Common Patterns
Software Keyboard Types
Choosing the Right Keyboard
Match the keyboard to the expected input:
| Input Type | Keyboard | UIKeyboardType |
|---|---|---|
| General text | Default | .default |
| Email-optimized (@ and . prominent) | .emailAddress |
|
| URL | URL-optimized (/, .com) | .URL |
| Phone number | Number pad | .phonePad |
| Numeric (with punctuation) | Numbers + punctuation | .numbersAndPunctuation |
| Numeric only | Decimal pad | .decimalPad |
| Twitter handle | Twitter keyboard | .twitter |
| Web search | Search keyboard | .webSearch |
| ASCII only | ASCII-capable | .asciiCapable |
SwiftUI Implementation
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
.autocapitalization(.none)
.disableAutocorrection(true)
UIKit Implementation
textField.keyboardType = .emailAddress
textField.textContentType = .emailAddress
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
Text Content Types
Enable autofill by specifying content types:
| Content | textContentType | Enables |
|---|---|---|
| Name | .name |
Contact autofill |
| Given name | .givenName |
|
| Family name | .familyName |
|
.emailAddress |
Email autofill | |
| Phone | .telephoneNumber |
Phone autofill |
| Address | .streetAddressLine1 |
Address autofill |
| City | .addressCity |
|
| State | .addressState |
|
| ZIP | .postalCode |
|
| Country | .countryName |
|
| Credit card | .creditCardNumber |
Camera card scanning |
| Username | .username |
Keychain autofill |
| Password | .password |
Keychain autofill |
| New password | .newPassword |
Password generation |
| One-time code | .oneTimeCode |
SMS autofill |
Password and Login Fields
// Login form
TextField("Email", text: $email)
.textContentType(.username)
.keyboardType(.emailAddress)
SecureField("Password", text: $password)
.textContentType(.password)
// Registration form
SecureField("Create Password", text: $newPassword)
.textContentType(.newPassword)
One-Time Code (2FA)
TextField("Verification Code", text: $code)
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
iOS automatically suggests codes from SMS messages when this content type is set.
Input Accessory Views
When to Use
Input accessory views appear above the keyboard for:
- Navigation between fields (Previous/Next)
- Custom actions (Done, formatting buttons)
- Context-specific tools
Standard Toolbar Pattern
struct FormTextField: View {
@Binding var text: String
@FocusState private var isFocused: Bool
var body: some View {
TextField("Value", text: $text)
.focused($isFocused)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
isFocused = false
}
}
}
}
}
Multi-Field Navigation
struct FormView: View {
@FocusState private var focusedField: Field?
enum Field {
case firstName, lastName, email
}
var body: some View {
Form {
TextField("First Name", text: $firstName)
.focused($focusedField, equals: .firstName)
TextField("Last Name", text: $lastName)
.focused($focusedField, equals: .lastName)
TextField("Email", text: $email)
.focused($focusedField, equals: .email)
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button(action: focusPrevious) {
Image(systemName: "chevron.up")
}
.disabled(!canFocusPrevious)
Button(action: focusNext) {
Image(systemName: "chevron.down")
}
.disabled(!canFocusNext)
Spacer()
Button("Done") {
focusedField = nil
}
}
}
}
}
Keyboard Avoidance
Automatic Behavior (SwiftUI)
SwiftUI automatically adjusts for keyboard in most cases:
- ScrollViews scroll to keep focused field visible
- Safe area adjusts for keyboard height
Manual Keyboard Handling
struct KeyboardAdaptive: ViewModifier {
@State private var keyboardHeight: CGFloat = 0
func body(content: Content) -> some View {
content
.padding(.bottom, keyboardHeight)
.onReceive(Publishers.keyboardHeight) { height in
withAnimation(.easeOut(duration: 0.25)) {
keyboardHeight = height
}
}
}
}
Best Practices
- Scroll to show field - Focused field should be visible
- Don't cover important actions - Submit buttons should remain accessible
- Animate adjustments - Match keyboard animation (0.25s ease-out)
- Test on different devices - Keyboard heights vary
Hardware Keyboard Support
Why It Matters
iPads commonly use hardware keyboards. Apps should:
- Support standard keyboard shortcuts
- Provide discoverability
- Not break when hardware keyboard is attached
Standard Shortcuts to Support
| Shortcut | Action | Notes |
|---|---|---|
| ⌘N | New item | Standard creation |
| ⌘S | Save | |
| ⌘⌫ | Delete | With confirmation |
| ⌘F | Find/Search | |
| ⌘Z | Undo | |
| ⌘⇧Z | Redo | |
| ⌘C/⌘V/⌘X | Copy/Paste/Cut | |
| ⌘, | Settings/Preferences | |
| ⌘W | Close window/modal | |
| Escape | Cancel/dismiss | |
| Tab | Next field | |
| ⇧Tab | Previous field | |
| Return | Submit form | When appropriate |
SwiftUI Keyboard Shortcuts
struct ContentView: View {
var body: some View {
NavigationStack {
ItemList()
.toolbar {
Button("New Item", action: createItem)
.keyboardShortcut("n", modifiers: .command)
}
}
}
}
// For custom shortcuts in list items
List {
ForEach(items) { item in
ItemRow(item: item)
}
}
.onDeleteCommand(perform: deleteSelected) // ⌘⌫
Keyboard Shortcut Discoverability
Hold ⌘ to show available shortcuts. Ensure your shortcuts appear:
Button("Save", action: save)
.keyboardShortcut("s", modifiers: .command)
// Appears in keyboard shortcut overlay
Text Editing
Autocapitalization
| Style | Use For |
|---|---|
.sentences |
General text, messages |
.words |
Names, titles |
.allCharacters |
Codes, abbreviations |
.none |
Email, usernames, URLs |
Autocorrection
| Setting | Use For |
|---|---|
| Enabled (default) | Prose, messages |
| Disabled | Code, usernames, specific values |
TextField("Username", text: $username)
.autocapitalization(.none)
.disableAutocorrection(true)
Text Input Traits Summary
TextField("Email", text: $email)
.keyboardType(.emailAddress) // Keyboard layout
.textContentType(.emailAddress) // Autofill hint
.autocapitalization(.none) // No auto-caps
.disableAutocorrection(true) // No autocorrect
.textInputAutocapitalization(.never) // iOS 15+
Secure Text Entry
Password Fields
SecureField("Password", text: $password)
.textContentType(.password)
Show/Hide Toggle Pattern
struct PasswordField: View {
@Binding var password: String
@State private var isSecure = true
var body: some View {
HStack {
if isSecure {
SecureField("Password", text: $password)
} else {
TextField("Password", text: $password)
}
Button(action: { isSecure.toggle() }) {
Image(systemName: isSecure ? "eye.slash" : "eye")
.foregroundColor(.secondary)
}
}
.textContentType(.password)
}
}
Search Input
Search Field Behavior
struct SearchView: View {
@State private var searchText = ""
var body: some View {
NavigationStack {
List(filteredItems) { item in
ItemRow(item: item)
}
.searchable(
text: $searchText,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Search items"
)
}
}
}
Search Suggestions
.searchable(text: $searchText) {
ForEach(suggestions) { suggestion in
Text(suggestion.name)
.searchCompletion(suggestion.name)
}
}
Common Patterns
Form with All Best Practices
struct RegistrationForm: View {
@State private var email = ""
@State private var password = ""
@State private var confirmPassword = ""
@FocusState private var focusedField: Field?
enum Field: Hashable {
case email, password, confirmPassword
}
var body: some View {
Form {
Section("Account") {
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.textContentType(.username)
.autocapitalization(.none)
.disableAutocorrection(true)
.focused($focusedField, equals: .email)
.submitLabel(.next)
.onSubmit { focusedField = .password }
SecureField("Password", text: $password)
.textContentType(.newPassword)
.focused($focusedField, equals: .password)
.submitLabel(.next)
.onSubmit { focusedField = .confirmPassword }
SecureField("Confirm Password", text: $confirmPassword)
.textContentType(.newPassword)
.focused($focusedField, equals: .confirmPassword)
.submitLabel(.done)
.onSubmit(register)
}
Section {
Button("Create Account", action: register)
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button("Previous") { moveFocus(-.previous) }
Button("Next") { moveFocus(.next) }
Spacer()
Button("Done") { focusedField = nil }
}
}
}
}
Submit Label Options
| Label | Use For |
|---|---|
.done |
Final field, closes keyboard |
.go |
Triggers action (search, navigate) |
.next |
Moves to next field |
.return |
Inserts newline (text areas) |
.search |
Search field |
.send |
Message composition |
.continue |
Multi-step forms |
.join |
Joining/connecting |
.route |
Navigation apps |
TextField("Search", text: $query)
.submitLabel(.search)
.onSubmit { performSearch() }
| 1 | # iOS Keyboard & Input Patterns |
| 2 | |
| 3 | Comprehensive guide to keyboard handling, text input, and hardware keyboard support. |
| 4 | |
| 5 | |
| 6 | ## Table of Contents |
| 7 | [Software Keyboard Types] |
| 8 | [Text Content Types] |
| 9 | [Input Accessory Views] |
| 10 | [Keyboard Avoidance] |
| 11 | [Hardware Keyboard Support] |
| 12 | [Text Editing] |
| 13 | [Secure Text Entry] |
| 14 | [Search Input] |
| 15 | [Common Patterns] |
| 16 | |
| 17 | |
| 18 | |
| 19 | ## Software Keyboard Types |
| 20 | |
| 21 | ### Choosing the Right Keyboard |
| 22 | |
| 23 | Match the keyboard to the expected input: |
| 24 | |
| 25 | | Input Type | Keyboard | UIKeyboardType | |
| 26 | |------------|----------|----------------| |
| 27 | | General text | Default | `.default` | |
| 28 | | Email | Email-optimized (@ and . prominent) | `.emailAddress` | |
| 29 | | URL | URL-optimized (/, .com) | `.URL` | |
| 30 | | Phone number | Number pad | `.phonePad` | |
| 31 | | Numeric (with punctuation) | Numbers + punctuation | `.numbersAndPunctuation` | |
| 32 | | Numeric only | Decimal pad | `.decimalPad` | |
| 33 | | Twitter handle | Twitter keyboard | `.twitter` | |
| 34 | | Web search | Search keyboard | `.webSearch` | |
| 35 | | ASCII only | ASCII-capable | `.asciiCapable` | |
| 36 | |
| 37 | ### SwiftUI Implementation |
| 38 | |
| 39 | |
| 40 | TextField("Email", text: $email) |
| 41 | .keyboardType(.emailAddress) |
| 42 | .textContentType(.emailAddress) |
| 43 | .autocapitalization(.none) |
| 44 | .disableAutocorrection(true) |
| 45 | |
| 46 | |
| 47 | ### UIKit Implementation |
| 48 | |
| 49 | |
| 50 | textField.keyboardType = .emailAddress |
| 51 | textField.textContentType = .emailAddress |
| 52 | textField.autocapitalizationType = .none |
| 53 | textField.autocorrectionType = .no |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 | ## Text Content Types |
| 59 | |
| 60 | Enable autofill by specifying content types: |
| 61 | |
| 62 | | Content | textContentType | Enables | |
| 63 | |---------|-----------------|---------| |
| 64 | | Name | `.name` | Contact autofill | |
| 65 | | Given name | `.givenName` | | |
| 66 | | Family name | `.familyName` | | |
| 67 | | Email | `.emailAddress` | Email autofill | |
| 68 | | Phone | `.telephoneNumber` | Phone autofill | |
| 69 | | Address | `.streetAddressLine1` | Address autofill | |
| 70 | | City | `.addressCity` | | |
| 71 | | State | `.addressState` | | |
| 72 | | ZIP | `.postalCode` | | |
| 73 | | Country | `.countryName` | | |
| 74 | | Credit card | `.creditCardNumber` | Camera card scanning | |
| 75 | | Username | `.username` | Keychain autofill | |
| 76 | | Password | `.password` | Keychain autofill | |
| 77 | | New password | `.newPassword` | Password generation | |
| 78 | | One-time code | `.oneTimeCode` | SMS autofill | |
| 79 | |
| 80 | ### Password and Login Fields |
| 81 | |
| 82 | |
| 83 | // Login form |
| 84 | TextField("Email", text: $email) |
| 85 | .textContentType(.username) |
| 86 | .keyboardType(.emailAddress) |
| 87 | |
| 88 | SecureField("Password", text: $password) |
| 89 | .textContentType(.password) |
| 90 | |
| 91 | // Registration form |
| 92 | SecureField("Create Password", text: $newPassword) |
| 93 | .textContentType(.newPassword) |
| 94 | |
| 95 | |
| 96 | ### One-Time Code (2FA) |
| 97 | |
| 98 | |
| 99 | TextField("Verification Code", text: $code) |
| 100 | .textContentType(.oneTimeCode) |
| 101 | .keyboardType(.numberPad) |
| 102 | |
| 103 | |
| 104 | iOS automatically suggests codes from SMS messages when this content type is set. |
| 105 | |
| 106 | |
| 107 | |
| 108 | ## Input Accessory Views |
| 109 | |
| 110 | ### When to Use |
| 111 | |
| 112 | Input accessory views appear above the keyboard for: |
| 113 | Navigation between fields (Previous/Next) |
| 114 | Custom actions (Done, formatting buttons) |
| 115 | Context-specific tools |
| 116 | |
| 117 | ### Standard Toolbar Pattern |
| 118 | |
| 119 | |
| 120 | struct FormTextField: View { |
| 121 | @Binding var text: String |
| 122 | @FocusState private var isFocused: Bool |
| 123 | |
| 124 | var body: some View { |
| 125 | TextField("Value", text: $text) |
| 126 | .focused($isFocused) |
| 127 | .toolbar { |
| 128 | ToolbarItemGroup(placement: .keyboard) { |
| 129 | Spacer() |
| 130 | Button("Done") { |
| 131 | isFocused = false |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | |
| 139 | ### Multi-Field Navigation |
| 140 | |
| 141 | |
| 142 | struct FormView: View { |
| 143 | @FocusState private var focusedField: Field? |
| 144 | |
| 145 | enum Field { |
| 146 | case firstName, lastName, email |
| 147 | } |
| 148 | |
| 149 | var body: some View { |
| 150 | Form { |
| 151 | TextField("First Name", text: $firstName) |
| 152 | .focused($focusedField, equals: .firstName) |
| 153 | |
| 154 | TextField("Last Name", text: $lastName) |
| 155 | .focused($focusedField, equals: .lastName) |
| 156 | |
| 157 | TextField("Email", text: $email) |
| 158 | .focused($focusedField, equals: .email) |
| 159 | } |
| 160 | .toolbar { |
| 161 | ToolbarItemGroup(placement: .keyboard) { |
| 162 | Button(action: focusPrevious) { |
| 163 | Image(systemName: "chevron.up") |
| 164 | } |
| 165 | .disabled(!canFocusPrevious) |
| 166 | |
| 167 | Button(action: focusNext) { |
| 168 | Image(systemName: "chevron.down") |
| 169 | } |
| 170 | .disabled(!canFocusNext) |
| 171 | |
| 172 | Spacer() |
| 173 | |
| 174 | Button("Done") { |
| 175 | focusedField = nil |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | |
| 183 | |
| 184 | |
| 185 | ## Keyboard Avoidance |
| 186 | |
| 187 | ### Automatic Behavior (SwiftUI) |
| 188 | |
| 189 | SwiftUI automatically adjusts for keyboard in most cases: |
| 190 | ScrollViews scroll to keep focused field visible |
| 191 | Safe area adjusts for keyboard height |
| 192 | |
| 193 | ### Manual Keyboard Handling |
| 194 | |
| 195 | |
| 196 | struct KeyboardAdaptive: ViewModifier { |
| 197 | @State private var keyboardHeight: CGFloat = 0 |
| 198 | |
| 199 | func body(content: Content) -> some View { |
| 200 | content |
| 201 | .padding(.bottom, keyboardHeight) |
| 202 | .onReceive(Publishers.keyboardHeight) { height in |
| 203 | withAnimation(.easeOut(duration: 0.25)) { |
| 204 | keyboardHeight = height |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | |
| 211 | ### Best Practices |
| 212 | |
| 213 | **Scroll to show field** - Focused field should be visible |
| 214 | **Don't cover important actions** - Submit buttons should remain accessible |
| 215 | **Animate adjustments** - Match keyboard animation (0.25s ease-out) |
| 216 | **Test on different devices** - Keyboard heights vary |
| 217 | |
| 218 | |
| 219 | |
| 220 | ## Hardware Keyboard Support |
| 221 | |
| 222 | ### Why It Matters |
| 223 | |
| 224 | iPads commonly use hardware keyboards. Apps should: |
| 225 | Support standard keyboard shortcuts |
| 226 | Provide discoverability |
| 227 | Not break when hardware keyboard is attached |
| 228 | |
| 229 | ### Standard Shortcuts to Support |
| 230 | |
| 231 | | Shortcut | Action | Notes | |
| 232 | |----------|--------|-------| |
| 233 | | ⌘N | New item | Standard creation | |
| 234 | | ⌘S | Save | | |
| 235 | | ⌘⌫ | Delete | With confirmation | |
| 236 | | ⌘F | Find/Search | | |
| 237 | | ⌘Z | Undo | | |
| 238 | | ⌘⇧Z | Redo | | |
| 239 | | ⌘C/⌘V/⌘X | Copy/Paste/Cut | | |
| 240 | | ⌘, | Settings/Preferences | | |
| 241 | | ⌘W | Close window/modal | | |
| 242 | | Escape | Cancel/dismiss | | |
| 243 | | Tab | Next field | | |
| 244 | | ⇧Tab | Previous field | | |
| 245 | | Return | Submit form | When appropriate | |
| 246 | |
| 247 | ### SwiftUI Keyboard Shortcuts |
| 248 | |
| 249 | |
| 250 | struct ContentView: View { |
| 251 | var body: some View { |
| 252 | NavigationStack { |
| 253 | ItemList() |
| 254 | .toolbar { |
| 255 | Button("New Item", action: createItem) |
| 256 | .keyboardShortcut("n", modifiers: .command) |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | // For custom shortcuts in list items |
| 263 | List { |
| 264 | ForEach(items) { item in |
| 265 | ItemRow(item: item) |
| 266 | } |
| 267 | } |
| 268 | .onDeleteCommand(perform: deleteSelected) // ⌘⌫ |
| 269 | |
| 270 | |
| 271 | ### Keyboard Shortcut Discoverability |
| 272 | |
| 273 | Hold ⌘ to show available shortcuts. Ensure your shortcuts appear: |
| 274 | |
| 275 | |
| 276 | Button("Save", action: save) |
| 277 | .keyboardShortcut("s", modifiers: .command) |
| 278 | // Appears in keyboard shortcut overlay |
| 279 | |
| 280 | |
| 281 | |
| 282 | |
| 283 | ## Text Editing |
| 284 | |
| 285 | ### Autocapitalization |
| 286 | |
| 287 | | Style | Use For | |
| 288 | |-------|---------| |
| 289 | | `.sentences` | General text, messages | |
| 290 | | `.words` | Names, titles | |
| 291 | | `.allCharacters` | Codes, abbreviations | |
| 292 | | `.none` | Email, usernames, URLs | |
| 293 | |
| 294 | ### Autocorrection |
| 295 | |
| 296 | | Setting | Use For | |
| 297 | |---------|---------| |
| 298 | | Enabled (default) | Prose, messages | |
| 299 | | Disabled | Code, usernames, specific values | |
| 300 | |
| 301 | |
| 302 | TextField("Username", text: $username) |
| 303 | .autocapitalization(.none) |
| 304 | .disableAutocorrection(true) |
| 305 | |
| 306 | |
| 307 | ### Text Input Traits Summary |
| 308 | |
| 309 | |
| 310 | TextField("Email", text: $email) |
| 311 | .keyboardType(.emailAddress) // Keyboard layout |
| 312 | .textContentType(.emailAddress) // Autofill hint |
| 313 | .autocapitalization(.none) // No auto-caps |
| 314 | .disableAutocorrection(true) // No autocorrect |
| 315 | .textInputAutocapitalization(.never) // iOS 15+ |
| 316 | |
| 317 | |
| 318 | |
| 319 | |
| 320 | ## Secure Text Entry |
| 321 | |
| 322 | ### Password Fields |
| 323 | |
| 324 | |
| 325 | SecureField("Password", text: $password) |
| 326 | .textContentType(.password) |
| 327 | |
| 328 | |
| 329 | ### Show/Hide Toggle Pattern |
| 330 | |
| 331 | |
| 332 | struct PasswordField: View { |
| 333 | @Binding var password: String |
| 334 | @State private var isSecure = true |
| 335 | |
| 336 | var body: some View { |
| 337 | HStack { |
| 338 | if isSecure { |
| 339 | SecureField("Password", text: $password) |
| 340 | } else { |
| 341 | TextField("Password", text: $password) |
| 342 | } |
| 343 | |
| 344 | Button(action: { isSecure.toggle() }) { |
| 345 | Image(systemName: isSecure ? "eye.slash" : "eye") |
| 346 | .foregroundColor(.secondary) |
| 347 | } |
| 348 | } |
| 349 | .textContentType(.password) |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | |
| 354 | |
| 355 | |
| 356 | ## Search Input |
| 357 | |
| 358 | ### Search Field Behavior |
| 359 | |
| 360 | |
| 361 | struct SearchView: View { |
| 362 | @State private var searchText = "" |
| 363 | |
| 364 | var body: some View { |
| 365 | NavigationStack { |
| 366 | List(filteredItems) { item in |
| 367 | ItemRow(item: item) |
| 368 | } |
| 369 | .searchable( |
| 370 | text: $searchText, |
| 371 | placement: .navigationBarDrawer(displayMode: .always), |
| 372 | prompt: "Search items" |
| 373 | ) |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | |
| 379 | ### Search Suggestions |
| 380 | |
| 381 | |
| 382 | .searchable(text: $searchText) { |
| 383 | ForEach(suggestions) { suggestion in |
| 384 | Text(suggestion.name) |
| 385 | .searchCompletion(suggestion.name) |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | |
| 390 | |
| 391 | |
| 392 | ## Common Patterns |
| 393 | |
| 394 | ### Form with All Best Practices |
| 395 | |
| 396 | |
| 397 | struct RegistrationForm: View { |
| 398 | @State private var email = "" |
| 399 | @State private var password = "" |
| 400 | @State private var confirmPassword = "" |
| 401 | @FocusState private var focusedField: Field? |
| 402 | |
| 403 | enum Field: Hashable { |
| 404 | case email, password, confirmPassword |
| 405 | } |
| 406 | |
| 407 | var body: some View { |
| 408 | Form { |
| 409 | Section("Account") { |
| 410 | TextField("Email", text: $email) |
| 411 | .keyboardType(.emailAddress) |
| 412 | .textContentType(.username) |
| 413 | .autocapitalization(.none) |
| 414 | .disableAutocorrection(true) |
| 415 | .focused($focusedField, equals: .email) |
| 416 | .submitLabel(.next) |
| 417 | .onSubmit { focusedField = .password } |
| 418 | |
| 419 | SecureField("Password", text: $password) |
| 420 | .textContentType(.newPassword) |
| 421 | .focused($focusedField, equals: .password) |
| 422 | .submitLabel(.next) |
| 423 | .onSubmit { focusedField = .confirmPassword } |
| 424 | |
| 425 | SecureField("Confirm Password", text: $confirmPassword) |
| 426 | .textContentType(.newPassword) |
| 427 | .focused($focusedField, equals: .confirmPassword) |
| 428 | .submitLabel(.done) |
| 429 | .onSubmit(register) |
| 430 | } |
| 431 | |
| 432 | Section { |
| 433 | Button("Create Account", action: register) |
| 434 | } |
| 435 | } |
| 436 | .toolbar { |
| 437 | ToolbarItemGroup(placement: .keyboard) { |
| 438 | Button("Previous") { moveFocus(-.previous) } |
| 439 | Button("Next") { moveFocus(.next) } |
| 440 | Spacer() |
| 441 | Button("Done") { focusedField = nil } |
| 442 | } |
| 443 | } |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | |
| 448 | ### Submit Label Options |
| 449 | |
| 450 | | Label | Use For | |
| 451 | |-------|---------| |
| 452 | | `.done` | Final field, closes keyboard | |
| 453 | | `.go` | Triggers action (search, navigate) | |
| 454 | | `.next` | Moves to next field | |
| 455 | | `.return` | Inserts newline (text areas) | |
| 456 | | `.search` | Search field | |
| 457 | | `.send` | Message composition | |
| 458 | | `.continue` | Multi-step forms | |
| 459 | | `.join` | Joining/connecting | |
| 460 | | `.route` | Navigation apps | |
| 461 | |
| 462 | |
| 463 | TextField("Search", text: $query) |
| 464 | .submitLabel(.search) |
| 465 | .onSubmit { performSearch() } |
| 466 | |
| 467 |
Discussion
Browse more free Claude skills.