如何防止用户在 Swift 5 中的 UITextView 中添加太多换行符?

How can I prevent a user from adding too many line breaks in a UITextView in Swift 5?

我有一个 UITextView 让用户可以为他们刚刚录制的一些音频文件输入评论。
我想将他们可以使用的“\n”(换行符)的数量限制为 5 个(即,注释最多应有 5 行)。 如果他们尝试转到第六行,我想显示一条带有合理消息的警报,并且在按下相关操作中的“确定”按钮后,我想让用户能够编辑他们的文本。

委派已经建立,optional func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool 的实施是我现在正在做的事情。
我放入其中的逻辑正确显示了警报,但随后,在单击“确定”并尝试删除一些字符后,该方法再次被调用,我收到了警报。
我知道这是因为计数器仍然停留在 5 但将其重置为 0 允许 9 行或更多,所以这不是解决方案。

这是我尝试过但未按预期运行的代码:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if text == characterToCheck /* \n, declared globally */ {
        characterCounter += 1 // this was also declared as a global property
    }

    if characterCounter > 4 {
        let newlineAC = UIAlertController(title: "Too many linebreaks", message: "Please go back and make your comment fit into a maximum of 5 lines", preferredStyle: .alert)
        newlineAC.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] (_) in
            let currentText = textView.text ?? ""
            guard let currentTextRange = Range(range, in: currentText) else { return }

            self?.comments.text = currentText.replacingOccurrences(of: "\n", with: "@ ", range: currentTextRange)
        })
        present(newlineAC, animated: true)

        return false
    } else {
        return true
    }
}  

没有抛出任何错误消息,因为代码完全按照我的要求执行,但我显然以错误的方式提出要求。 我能做什么?

你所描述的逻辑正在发生,因为你总是递增计数器,但从不递减它。你 应该 递减计数器,如果你递增计数器,但最终没有将新行添加到 TextView

...
present(newlineAC, animated: true)
characterCounter-=1
return false
...

为什么要减:

通过在这个函数中returning false,TextView不会将新的变化添加到TextView中。因为没有加,所以不应该算在characterCount.

选择删除

您还必须考虑用户一次删除整个选择的文本的情况。

let text = NSString(string: textView.text)
for char in text.substring(with: range) {
    print(char)
if char == characterToCheck {
        //if the text that is going to be removed has the character
        //remove it from the count      
        characterCounter-=1
    }
}

确保如果用户删除,该函数将 return true 以便文本真正被删除。

粘贴(选择插入)

如果用户粘贴了一堆文本,我们需要检查那里的换行符。

for char in replacementString {
    if char == characterToCheck {
      characterCounter+=1
  } 
}

大家在一起

这考虑到了一切。还使用新变量更新了一些逻辑。

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    var localCount = characterCount
    //check for deletion
    let NStext = NSString(string: textView.text)
    for char in NStext.substring(with: range) {
        print(char)
        if char == characterToCheck {
            //if the text that is going to be removed has the character
            //remove it from the count      
            localCount-=1
        }
    }

    //check for insertion
    //this will also replace the simple check in the beginning
    for char in replacementString {
            if char == characterToCheck {
            //if any of the character being inserted is the one
            //will be taken into account here
                localCount+=1
            } 
        }

    if localCount > 4 {
        let newlineAC = UIAlertController(title: "Too many linebreaks", message: "Please go back and make your comment fit into a maximum of 5 lines", preferredStyle: .alert)
        newlineAC.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] (_) in
            let currentText = textView.text ?? ""
            guard let currentTextRange = Range(range, in: currentText) else { return }

            self?.comments.text = currentText.replacingOccurrences(of: "\n", with: "@ ", range: currentTextRange)
        })
        present(newlineAC, animated: true)
        return false
    } else {
        characterCounter = localCount
        //only updates if has an OK # of newlines
        return true
    }
}  

这是我的解决方案:

更新 Matt Bart 反馈

 func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if text == String(characterToCheck) /* \n, declared globally */ {
        characterCounter += 1 // this was also declared as a global property
    }

    if text == "" {
        let characters = Array(textView.text)
        if characters.count >= range.location {
            let deletedCharacter = characters[range.location]
            if  deletedCharacter == characterToCheck {
                characterCounter -= 1
            }
        }
    }

    if characterCounter > 4 {
        let newlineAC = UIAlertController(title: "Too many linebreaks", message: "Please go back and make your comment fit into a maximum of 5 lines", preferredStyle: .alert)
        newlineAC.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] (_) in
            let currentText = textView.text ?? ""
            guard let currentTextRange = Range(range, in: currentText) else { return }

            self?.comments.text = currentText.replacingOccurrences(of: "\n", with: "@ ", range: currentTextRange)
        })
        present(newlineAC, animated: true, completion: { [weak self] in
            self?.characterCounter -= 1
        })

        return false
    } else {
        return true
    }
}

}

此外,将 characterToCheck 声明为 Character 而不是 String

characterCounter 必须从 0

开始