在Swift中设置UITextField的最大字符长度

124

我知道这个话题已经有其他相关的讨论了,但是我似乎找不到如何实现它。

我正在尝试将一个UITextField限制为只能输入5个字符。

最好是字母数字、-._

我看到了这段代码:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange,
                       replacementString string: String) -> Bool
{
    let maxLength = 4
    let currentString: NSString = textField.text
    let newString: NSString =
             currentString.stringByReplacingCharactersInRange(range, withString: string)
    return newString.length <= maxLength
}

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    let length = count(textField.text.utf16) + count(string.utf16) - range.length
    return length <= 10
}

我该如何实际应用它?我应该用哪个“textfield”来替换我的自定义UITextField?


Swift 4 实现 https://stackoverflow.com/a/46513151/2303865 - Leo Dabus
快速提醒 - 在Swift中缩短一个String的长度,现在你终于可以用.prefix(n)来实现了。 - Fattie
23个回答

191
  1. 你的视图控制器应该符合以下代码中所示的UITextFieldDelegate:

  2. class MyViewController: UIViewController, UITextFieldDelegate {
    
    }
    
  3. 设置文本框的代理:myTextField.delegate = self

  4. 在您的视图控制器中实现该方法:

    textField(_:shouldChangeCharactersInRange:replacementString:)
    

全部在一起:

class MyViewController: UIViewController, UITextFieldDelegate  // Set delegate to class

@IBOutlet var mytextField: UITextField             //  textfield variable

override func viewDidLoad() {
    super.viewDidLoad()
    mytextField.delegate = self                  // set delegate
}


func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange,
                       replacementString string: String) -> Bool
{
    let maxLength = 4
    let currentString: NSString = textField.text
    let newString: NSString =  currentString.stringByReplacingCharactersInRange(range, withString: string)

    return newString.length <= maxLength
}

对于Swift 4版本

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let maxLength = 1
    let currentString: NSString = (textField.text ?? "") as NSString
    let newString: NSString =  currentString.replacingCharacters(in: range, with: string) as NSString

    return newString.length <= maxLength
}

适用于Swift 5

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let maxLength = 1
    let currentString = (textField.text ?? "") as NSString
    let newString = currentString.replacingCharacters(in: range, with: string)

    return newString.count <= maxLength
}

仅允许在指定的文本字段中输入特定字符

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    var result = true

    if mytextField == textField {
        if count(string) > 0 {
            let disallowedCharacterSet = NSCharacterSet(charactersInString: "0123456789.-").invertedSet
            let replacementStringIsLegal = string.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
            result = replacementStringIsLegal
        }
    }

    return result
}

如何编程iOS文本框,仅接受数字输入并具有最大长度限制


非常感谢您的及时回复!如果我将此文本字段设置为委托,我是否能够修改其他文本字段? - ishkur88
但是第二个参数应该放在哪里呢?我在将myTextField设置为委托后就不再引用它了。 - ishkur88
比如说,如果我想要创建一个只能输入0-9数字的文本框,用于输入电话号码。 - ishkur88
1
每次编辑文本框时,都会调用回调函数shouldChangeCharactersInRange,这适用于所有文本框。您可以在同一位置shouldChangeCharactersInRange中接收回调,并且在此方法内部,通过传递的参数textField,您可以知道哪个文本框正在被编辑。例如,您可以为每个文本框分配一个标签,并在shouldChangeCharactersInRange中进行测试,并为每个文本框执行内容验证。 - Alaeddine
@ishkur88 我已经更新了答案,并添加了另一个示例,请注意 if mytextField == numberField { 来检查正在编辑的文本字段。 - Alaeddine
显示剩余5条评论

138

现代Swift

请注意,许多在线示例代码已经非常过时。

将以下内容粘贴到项目中的任何Swift文件中,例如“Handy.swift”。

这解决了iOS中最愚蠢的问题之一:

enter image description here

您的文本字段现在具有.maxLength属性。

可以在Storyboard中设置该值,或者在应用程序运行时通过代码进行设置。

// Handy.swift

import UIKit
private var __maxLengths = [UITextField: Int]()
extension UITextField {
    @IBInspectable var maxLength: Int {
        get {
            guard let l = __maxLengths[self] else {
               return 150 // (global default-limit. or just, Int.max)
            }
            return l
        }
        set {
            __maxLengths[self] = newValue
            addTarget(self, action: #selector(fix), for: .editingChanged)
        }
    }
    func fix(textField: UITextField) {
        let t = textField.text
        textField.text = t?.prefix(maxLength).string
    }
}

就是这么简单。

更简单的一次性版本...

上述代码修复了整个项目中的所有文本字段。

如果您只想让特定文本字段仅限于"4",那就这样吧...

class PinCodeEntry: UITextField {
    
    override func didMoveToSuperview() {
        
        super.didMoveToSuperview()
        addTarget(self, action: #selector(fixMe), for: .editingChanged)
    }
    
    @objc private func fixMe() { text = text?.prefix(4) }
}

就是这样了。

(这里有一个与UITextView相关的非常有用的类似提示, https://dev59.com/hnRB5IYBdhLWcg3wAjXR#42333832)


19
所有这些需要做的事情,只是为了实现如此普通和简单的目标,这让我感到惊讶。他们本可以提供一个简单的内置textField.maxLength...无论如何,你的解决方案很棒,谢谢! - mylovemhz
1
对于Swift 4,self.characters已被弃用。现在只需使用let c = self - Alan Scarpa
2
这个解决方案很方便,但是顶部的文本字段映射实际上会产生一个保留循环。 - Angel G. Olloqui
6
使用此解决方案需注意!该解决方案存在一些问题。其中之一是,如果用户在textField开头键入,您将允许他们键入新字符,并删除最后一个字符,同时光标会跳到字段中的最后一个字符。另一个问题是,如果您以编程方式设置文本,它将允许您设置一个大于限制的文本。如果您撤消更改(使用外部键盘的CMD+Z),则会崩溃,如果先前尝试添加超过限制的数字,则会出现另一个问题。 - juancazalla
1
它给我一个错误,说前缀上的表达式类型“()”没有更多的上下文是模棱两可的。 - Dylan
显示剩余14条评论

36

在 Swift 4 中,只需要使用:

public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    return range.location < 10
}

14
它不起作用。如果您在字符串中间轻敲,您可以输入超过X个字符的内容。 - Slavcho
你可以使用textField.text.length < 10代替range.location < 10。这个解决方案简单而优雅。 - Saqib Saud
你可以使用以下解决方案:如果textField.text?.count >= 12 { return false } - Serge Bilyk
3
如果你想要在过去的动作中使用它,你应该添加 string.count < MAX_LENGTH。但是,如果你想要将文本粘贴到其中,它不起作用。 - Reza Dehnavi
2
这不可能起作用。位置不是字符串的长度,而是光标/选择的位置。 - iDev
显示剩余2条评论

15

使用相同的方式,但使用Swift 3.0,就像Steven Schmatz所做的一样:

//max Length
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool
{
    let maxLength = 4
    let currentString: NSString = textField.text! as NSString
    let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
    return newString.length <= maxLength
}

这里没有叫做“Steven Schmatz”的人。它指的是哪个答案?是 SwiftMatt's answer 吗?其他三个可能性是“Alaeddine”、“ZaEeM ZaFaR”和“Cloy”(已删除答案 - 仅链接答案)。 - Peter Mortensen

9

11
虽然这个解决方案看起来很简单,但实际上需要更多的代码来实现。仅凭此回答可能并不太有帮助,最好附加解释以及其他相关代码的内容可能会更有帮助。 - FontFamily

8

不使用代理的简单解决方案:

TEXT_FIELD.addTarget(self, action: #selector(editingChanged(sender:)), for: .editingChanged)


@objc private func editingChanged(sender: UITextField) {

        if let text = sender.text, text.count >= MAX_LENGHT {
            sender.text = String(text.dropLast(text.count - MAX_LENGHT))
            return
        }
}

1
这是一个简单而优雅的解决方案,没有样板代码。 - zeeshan
这样行吗?MAX_LENGHT看起来拼错了。为什么不是MAX_LENGTH?它在哪里定义的?词典:*length* - Peter Mortensen
定义在任何地方都没有 :) 我认为他的意图是让评论读者用所需的最大长度替换它。除此之外,这个答案应该是解决方案。 - TD540

7
我觉得使用扩展更加方便。查看完整答案 在此处
private var maxLengths = [UITextField: Int]()

// 2
extension UITextField {

  // 3
  @IBInspectable var maxLength: Int {
    get {
      // 4
      guard let length = maxLengths[self] else {
        return Int.max
      }
      return length
    }
    set {
      maxLengths[self] = newValue
      // 5
      addTarget(
        self,
        action: #selector(limitLength),
        forControlEvents: UIControlEvents.EditingChanged
      )
    }
  }

  func limitLength(textField: UITextField) {
    // 6
    guard let prospectiveText = textField.text
      where prospectiveText.characters.count > maxLength else {
        return
    }

    let selection = selectedTextRange
    // 7
    text = prospectiveText.substringWithRange(
      Range<String.Index>(prospectiveText.startIndex ..< prospectiveText.startIndex.advancedBy(maxLength))
    )
    selectedTextRange = selection
  }

}

5

这是我用Swift 4编写的shouldChangeCharactersIn函数:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool {

    guard let preText = textField.text as NSString?,
        preText.replacingCharacters(in: range, with: string).count <= MAX_TEXT_LENGTH else {
        return false
    }

    return true
}

4

之前发布的其他解决方案由于textfield映射而产生了保留循环。此外,maxLength属性如果未设置应该是可空的,而不是人为构造的Int.max;如果更改了 maxLength,则目标将被多次设置。

这里提供了一个使用弱映射来防止内存泄漏和其他修复的Swift4更新解决方案。

private var maxLengths = NSMapTable<UITextField, NSNumber>(keyOptions: NSPointerFunctions.Options.weakMemory, valueOptions: NSPointerFunctions.Options.strongMemory)

extension UITextField {

    var maxLength: Int? {
        get {
            return maxLengths.object(forKey: self)?.intValue
        }
        set {
            removeTarget(self, action: #selector(limitLength), for: .editingChanged)
            if let newValue = newValue {
                maxLengths.setObject(NSNumber(value: newValue), forKey: self)
                addTarget(self, action: #selector(limitLength), for: .editingChanged)
            } else {
                maxLengths.removeObject(forKey: self)
            }
        }
    }

    @IBInspectable var maxLengthInspectable: Int {
        get {
            return maxLength ?? Int.max
        }
        set {
            maxLength = newValue
        }
    }

    @objc private func limitLength(_ textField: UITextField) {
        guard let maxLength = maxLength, let prospectiveText = textField.text, prospectiveText.count > maxLength else {
            return
        }
        let selection = selectedTextRange
        text = String(prospectiveText[..<prospectiveText.index(from: maxLength)])
        selectedTextRange = selection
    }
}

谢谢您的回答,您能解释一下maxLengths吗? - Kawe

3

我基于@Frouo的回答给出一个补充。我认为他的回答是最美妙的方法。因为它是我们可以重复使用的普通控件。而且这里没有任何泄漏问题。

private var kAssociationKeyMaxLength: Int = 0

extension UITextField {

    @IBInspectable var maxLength: Int {
        get {
            if let length = objc_getAssociatedObject(self, &kAssociationKeyMaxLength) as? Int {
                return length
            } else {
                return Int.max
            }
        }
        set {
            objc_setAssociatedObject(self, &kAssociationKeyMaxLength, newValue, .OBJC_ASSOCIATION_RETAIN)
            self.addTarget(self, action: #selector(checkMaxLength), for: .editingChanged)
        }
    }

    // The method is used to cancel the check when using
    // the Chinese Pinyin input method.
    // Becuase the alphabet also appears in the textfield
    // when inputting, we should cancel the check.
    func isInputMethod() -> Bool {
        if let positionRange = self.markedTextRange {
            if let _ = self.position(from: positionRange.start, offset: 0) {
                return true
            }
        }
        return false
    }


    func checkMaxLength(textField: UITextField) {

        guard !self.isInputMethod(), let prospectiveText = self.text,
            prospectiveText.count > maxLength
        else {
            return
        }

        let selection = selectedTextRange
        let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
        text = prospectiveText.substring(to: maxCharIndex)
        selectedTextRange = selection
    }

}

这里没有叫做“Frouo”的人。它指的是什么答案? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接