iOS Keyboard & Input Patterns skill

Comprehensive guide to keyboard handling, text input, and hardware keyboard support.

by wondelai·MIT license·★ 2,235 Stars on the repo·GitHub ↗

Use now

Files of iOS Keyboard & Input Patterns

wondelai/main1 file
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

  1. Software Keyboard Types
  2. Text Content Types
  3. Input Accessory Views
  4. Keyboard Avoidance
  5. Hardware Keyboard Support
  6. Text Editing
  7. Secure Text Entry
  8. Search Input
  9. 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 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
Email .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
  1. Scroll to show field - Focused field should be visible
  2. Don't cover important actions - Submit buttons should remain accessible
  3. Animate adjustments - Match keyboard animation (0.25s ease-out)
  4. 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 
3Comprehensive guide to keyboard handling, text input, and hardware keyboard support.
4 
5 
6## Table of Contents
71. [Software Keyboard Types](#software-keyboard-types)
82. [Text Content Types](#text-content-types)
93. [Input Accessory Views](#input-accessory-views)
104. [Keyboard Avoidance](#keyboard-avoidance)
115. [Hardware Keyboard Support](#hardware-keyboard-support)
126. [Text Editing](#text-editing)
137. [Secure Text Entry](#secure-text-entry)
148. [Search Input](#search-input)
159. [Common Patterns](#common-patterns)
16 
17---
18 
19## Software Keyboard Types
20 
21### Choosing the Right Keyboard
22 
23Match 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```swift
40TextField("Email", text: $email)
41 .keyboardType(.emailAddress)
42 .textContentType(.emailAddress)
43 .autocapitalization(.none)
44 .disableAutocorrection(true)
45```
46 
47### UIKit Implementation
48 
49```swift
50textField.keyboardType = .emailAddress
51textField.textContentType = .emailAddress
52textField.autocapitalizationType = .none
53textField.autocorrectionType = .no
54```
55 
56---
57 
58## Text Content Types
59 
60Enable 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```swift
83// Login form
84TextField("Email", text: $email)
85 .textContentType(.username)
86 .keyboardType(.emailAddress)
87 
88SecureField("Password", text: $password)
89 .textContentType(.password)
90 
91// Registration form
92SecureField("Create Password", text: $newPassword)
93 .textContentType(.newPassword)
94```
95 
96### One-Time Code (2FA)
97 
98```swift
99TextField("Verification Code", text: $code)
100 .textContentType(.oneTimeCode)
101 .keyboardType(.numberPad)
102```
103 
104iOS 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 
112Input 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```swift
120struct 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```swift
142struct 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 
189SwiftUI 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```swift
196struct 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 
2131. **Scroll to show field** - Focused field should be visible
2142. **Don't cover important actions** - Submit buttons should remain accessible
2153. **Animate adjustments** - Match keyboard animation (0.25s ease-out)
2164. **Test on different devices** - Keyboard heights vary
217 
218---
219 
220## Hardware Keyboard Support
221 
222### Why It Matters
223 
224iPads 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```swift
250struct 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
263List {
264 ForEach(items) { item in
265 ItemRow(item: item)
266 }
267}
268.onDeleteCommand(perform: deleteSelected) // ⌘⌫
269```
270 
271### Keyboard Shortcut Discoverability
272 
273Hold ⌘ to show available shortcuts. Ensure your shortcuts appear:
274 
275```swift
276Button("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```swift
302TextField("Username", text: $username)
303 .autocapitalization(.none)
304 .disableAutocorrection(true)
305```
306 
307### Text Input Traits Summary
308 
309```swift
310TextField("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```swift
325SecureField("Password", text: $password)
326 .textContentType(.password)
327```
328 
329### Show/Hide Toggle Pattern
330 
331```swift
332struct 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```swift
361struct 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```swift
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```swift
397struct 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```swift
463TextField("Search", text: $query)
464 .submitLabel(.search)
465 .onSubmit { performSearch() }
466```
467 

Discussion