UITextField
Credit Card Input
iOS Development
Swift Programming
Input Formatting

Formatting a UITextField for credit card input like xxxx xxxx xxxx xxxx

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Formatting a credit card field as xxxx xxxx xxxx xxxx is a common iOS UI task because grouped digits are easier to verify visually than one long number. The usual approach is to intercept each edit, strip non-digits, cap the length, and then reinsert spaces every four digits before updating the UITextField.

Format the text in the delegate

The simplest implementation lives in textField(_:shouldChangeCharactersIn:replacementString:). You let the user type normally, rebuild the string from digits only, and then replace the field text with the formatted version.

swift
1import UIKit
2
3final class PaymentViewController: UIViewController, UITextFieldDelegate {
4    @IBOutlet private weak var cardTextField: UITextField!
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8        cardTextField.delegate = self
9        cardTextField.keyboardType = .numberPad
10        cardTextField.placeholder = "1234 5678 9012 3456"
11    }
12
13    func textField(
14        _ textField: UITextField,
15        shouldChangeCharactersIn range: NSRange,
16        replacementString string: String
17    ) -> Bool {
18        let current = textField.text ?? ""
19        guard let textRange = Range(range, in: current) else {
20            return false
21        }
22
23        let updated = current.replacingCharacters(in: textRange, with: string)
24        let digitsOnly = updated.filter(\.isNumber)
25        let limitedDigits = String(digitsOnly.prefix(16))
26
27        textField.text = formatCardNumber(limitedDigits)
28        return false
29    }
30
31    private func formatCardNumber(_ digits: String) -> String {
32        stride(from: 0, to: digits.count, by: 4).map { index in
33            let start = digits.index(digits.startIndex, offsetBy: index)
34            let end = digits.index(start, offsetBy: 4, limitedBy: digits.endIndex) ?? digits.endIndex
35            return String(digits[start..<end])
36        }.joined(separator: " ")
37    }
38}

This gives you grouped output on every keystroke and also sanitizes pasted input. If a user pastes 4242424242424242, the field still becomes 4242 4242 4242 4242.

Why rebuilding from digits is better than inserting spaces manually

A lot of first attempts try to insert a space only when the length reaches 4, 8, or 12. That works until the user deletes, pastes, or edits the middle of the string. Rebuilding from digits each time is more robust because the formatter always works from a clean canonical value.

This model also separates storage from presentation:

  1. The canonical value is the digits-only string.
  2. The visible value is the grouped text shown to the user.

That separation is important because payment APIs generally expect the raw digits, not the formatted text with spaces.

Extract the raw card number when submitting

When the user taps the pay button, remove the spaces before sending the value anywhere:

swift
1@IBAction private func payButtonTapped(_ sender: UIButton) {
2    let rawCardNumber = (cardTextField.text ?? "").filter(\.isNumber)
3    print(rawCardNumber)
4}

This keeps the UI user-friendly without polluting the backend or validation logic with formatting characters.

Consider variable card lengths and schemes

The 4-4-4-4 grouping is common, but not universal. American Express often uses 4-6-5, and some cards are shorter or longer than 16 digits. If the field must support multiple brands, make the grouping dynamic based on a detected card type instead of hardcoding a single pattern.

A simple extension point looks like this:

swift
1private func grouping(for digits: String) -> [Int] {
2    if digits.hasPrefix("34") || digits.hasPrefix("37") {
3        return [4, 6, 5]
4    }
5    return [4, 4, 4, 4]
6}

The formatter can then apply a group array rather than assuming fixed groups of four. Even if you start with the simpler 4-4-4-4 case, it is worth keeping the code organized so brand-specific rules can be added later.

User experience details that matter

Formatting alone is not enough for a good payment field. Set the keyboard to .numberPad, show a clear placeholder, and consider using textContentType where appropriate. Many apps also pair formatting with lightweight validation such as a Luhn check after editing ends, rather than showing an error while the user is still typing.

If you need perfect cursor handling for editing in the middle of the field, you must also compute the new caret position after reformatting. The simplified example above works well for ordinary typing and pasting, but middle edits may move the caret to the end because you are replacing the full text value.

Common Pitfalls

The most common bug is formatting the visible string repeatedly without first stripping spaces. That eventually produces malformed output such as doubled separators or incorrect lengths.

Another issue is sending the formatted string to the backend. Payment services almost always want the raw digits, so remove spaces before validation and submission.

Developers also hardcode a 16-digit maximum without thinking about card types. That is acceptable for a narrow use case, but broader payment forms should account for different issuers.

Finally, be careful with cursor behavior. Replacing the entire field text is simple, but it can feel jumpy when users edit in the middle unless you also restore the selection range intelligently.

Summary

  • Intercept text changes in the delegate, strip non-digits, and rebuild the grouped display value.
  • Keep a digits-only canonical value and treat the spaces as presentation only.
  • Reformatting from scratch on every edit is more reliable than manual space insertion rules.
  • Extract the raw number before validation or submission.
  • If the field must support multiple card brands, make grouping and max length configurable.

Course illustration
Course illustration

All Rights Reserved.