Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> iOS

Cách đếm ký tự real-time khi nhập TextField trong iOS

Thường thấy trên form đăng ký: hiển thị số ký tự đã nhập / giới hạn (ví dụ: 140/280 ký tự tweet). Dùng UITextFieldDelegate method shouldChangeCharactersIn.

UITextFieldDelegate Docs

Triển khai cơ bản

Bước 1: Tạo dự án

Xcode → Single View App → Tên: "TextFieldCount".

Bước 2: Storyboard

Thêm UITextField + UILabel (hiển thị count) → @IBOutlet tên txtInputBox, lblCount.

Cách đếm ký tự real-time khi nhập TextField trong iOS

Bước 3: ViewController.swift


import UIKit

class ViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet weak var txtInputBox: UITextField!
    @IBOutlet weak var lblCount: UILabel!
    
    let maxCharacters = 140 // Giới hạn ký tự
    
    override func viewDidLoad() {
        super.viewDidLoad()
        txtInputBox.delegate = self
        lblCount.text = "0/\(maxCharacters)"
    }
    
    // Delegate method: Gọi TRƯỚC KHI text thay đổi
    func textField(_ textField: UITextField, 
                   shouldChangeCharactersIn range: NSRange, 
                   replacementString string: String) -> Bool {
        
        guard textField == txtInputBox else { return true }
        
        let currentText = textField.text ?? ""
        let newText = (currentText as NSString).replacingCharacters(in: range, with: string)
        let newLength = newText.count
        
        // Cập nhật label
        lblCount.text = "\(newLength)/\(maxCharacters)"
        lblCount.textColor = newLength > maxCharacters ? .systemRed : .systemGray
        
        // Cho phép hoặc chặn nhập
        return newLength <= maxCharacters
    }
}

Bước 4: Chạy app

Gõ text → Label cập nhật real-time. Vượt giới hạn → không gõ được, label đỏ.

Cách đếm ký tự real-time khi nhập TextField trong iOS

Mở rộng: Cho phép paste nhưng cắt bớt nếu vượt limit


func textField(_ textField: UITextField, 
               shouldChangeCharactersIn range: NSRange, 
               replacementString string: String) -> Bool {
    
    guard textField == txtInputBox else { return true }
    
    let currentText = textField.text ?? ""
    let newText = (currentText as NSString).replacingCharacters(in: range, with: string)
    let newLength = newText.count
    
    if newLength > maxCharacters {
        // Cho phép paste nhưng tự cắt bớt
        let allowedText = String(newText.prefix(maxCharacters))
        textField.text = allowedText
        lblCount.text = "\(maxCharacters)/\(maxCharacters)"
        lblCount.textColor = .systemRed
        
        // Gửi notification text did change (vì set text trực tiếp không trigger delegate)
        NotificationCenter.default.post(name: UITextField.textDidChangeNotification, object: textField)
        return false // Chặn paste gốc
    }
    
    lblCount.text = "\(newLength)/\(maxCharacters)"
    lblCount.textColor = newLength > maxCharacters ? .systemRed : .systemGray
    return true
}

Dùng cho UITextView (Text View)


class ViewController: UIViewController, UITextViewDelegate {
    
    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var countLabel: UILabel!
    let maxChars = 500
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
    }
    
    func textView(_ textView: UITextView, 
                  shouldChangeTextIn range: NSRange, 
                  replacementText text: String) -> Bool {
        
        let currentText = textView.text ?? ""
        let newText = (currentText as NSString).replacingCharacters(in: range, with: text)
        let newLength = newText.count
        
        countLabel.text = "\(newLength)/\(maxChars)"
        countLabel.textColor = newLength > maxChars ? .systemRed : .systemGray
        
        return newLength <= maxChars
    }
}

Modern Swift: Combine / SwiftUI


// Combine (UIKit)
textField.textPublisher
    .map { $0?.count ?? 0 }
    .map { "\($0)/\(maxCharacters)" }
    .assign(to: \.text, on: lblCount)
    .store(in: &cancellables)

// SwiftUI
TextField("Nhập nội dung", text: $text)
    .onChange(of: text) { newValue in
        if newValue.count > maxChars {
            text = String(newValue.prefix(maxChars))
        }
    }
Text("\(text.count)/\(maxChars)")
    .foregroundColor(text.count > maxChars ? .red : .gray)

Lưu ý

  • shouldChangeCharactersIn gọi trước khi text thay đổi - tính toán dựa trên range + replacementString
  • Emoji counts as 1 character trong Swift (String.count = extended grapheme clusters)
  • Return false để chặn thay đổi, true để cho phép
  • Cần set textField.delegate = self (Storyboard hoặc code)