繁体   English   中英

如何限制 UITextView 中的行数?

[英]How to limit the number of lines in UITextView?

我的屏幕上有一个 UITextView,用户应该只填写两行,当用户在第二行时,返回键应该变成Done

如何限制 UITextView 中的数? 我搜索了很多,没有一个有用的结果!

我为 Swift 找到了这个答案:

locationNoteTextView.textContainer.maximumNumberOfLines = 2
self.locationNoteTextView.textContainer.lineBreakMode = NSLineBreakMode.ByClipping

不行,这样用户可以输入无穷大的字符,但是屏幕上看到的只有两行!

因此,如果您尝试打印 textView 的文本,则会发现灾难文本。

您需要实现textView:shouldChangeTextInRange:replacementText: 每当文本要更改时都会调用此方法。 您可以使用文本属性访问文本视图的当前内容。

使用[textView.text stringByReplacingCharactersInRange:range withString:replacementText]从传递的范围和替换文本构建新内容。

然后,您可以计算行数并返回YES以允许更改或NO拒绝更改。

编辑:根据 OP 请求:

迅速:

func sizeOfString (string: String, constrainedToWidth width: Double, font: UIFont) -> CGSize {
    return (string as NSString).boundingRectWithSize(CGSize(width: width, height: DBL_MAX),
        options: NSStringDrawingOptions.UsesLineFragmentOrigin,
        attributes: [NSFontAttributeName: font],
        context: nil).size
}


func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
    let newText = (textView.text as NSString).stringByReplacingCharactersInRange(range, withString: text)
    var textWidth = CGRectGetWidth(UIEdgeInsetsInsetRect(textView.frame, textView.textContainerInset))
    textWidth -= 2.0 * textView.textContainer.lineFragmentPadding;

    let boundingRect = sizeOfString(newText, constrainedToWidth: Double(textWidth), font: textView.font!)
    let numberOfLines = boundingRect.height / textView.font!.lineHeight;

    return numberOfLines <= 2;
}

对象-C

- (CGSize) sizeOfString:(NSString*)str constrainedToWidth:(CGFloat)width andFont:(UIFont*)font
{
    return [str boundingRectWithSize:CGSizeMake(width, DBL_MAX)
                              options:NSStringDrawingUsesLineFragmentOrigin
                           attributes:@{NSFontAttributeName: font}
                              context:nil].size;
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
    CGFloat textWidth = CGRectGetWidth(UIEdgeInsetsInsetRect(textView.frame, textView.textContainerInset));
    textWidth -= 2.0 * textView.textContainer.lineFragmentPadding;
    CGSize boundingRect = [self sizeOfString:newText constrainedToWidth:textWidth andFont:textView.font];
    int numberOfLines = boundingRect.height / textView.font.lineHeight;
    return numberOfLines <= 2;
}

对于 iOS 7+

textView.textContainer.maximumNumberOfLines = 10;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;

或者

textView.textContainer.maximumNumberOfLines = 10;
[textView.layoutManager textContainerChangedGeometry:textView.textContainer];

如果有人想要实时行数计数,这就是您使用 RxSwift 和 RxCocoa 的方法

    let disposeBag = DisposeBag()

    textView.rx_text.asDriver().driveNext { text in

        let text = text as NSString

        var textWidth: CGFloat = CGRectGetWidth(UIEdgeInsetsInsetRect(self.textView.frame, self.textView.textContainerInset))

        textWidth -= 2.0 * self.textView.textContainer.lineFragmentPadding

        let textAttributes = [NSFontAttributeName: UIFont(name: “Set your font here”, size: 26.0)!]

        let boundingRect: CGRect = text.boundingRectWithSize(CGSizeMake(textWidth, 0), options: [NSStringDrawingOptions.UsesLineFragmentOrigin, NSStringDrawingOptions.UsesFontLeading], attributes: textAttributes, context: nil)

        let font = UIFont(name: “Set your font here”, size: 26.0)

        guard let lineHeight = font?.lineHeight else {return}

        let numberOfLines = CGRectGetHeight(boundingRect) / lineHeight

        if numberOfLines <= 2 {


            self.textView.typingAttributes = [NSFontAttributeName: UIFont(name: “Set your font here”, size: 26.0)!, NSForegroundColorAttributeName: “Set your color here”]

        } else {

            self.textView.typingAttributes = [NSFontAttributeName: UIFont(name: “Set your font here”, size: 18.0)!, NSForegroundColorAttributeName: “Set your color here”]

        }

    }.addDisposableTo(disposeBag)

我将@Abhinav 代码更新为 Swift 5,还添加了限制字符数的支持。

import UIKit

class ViewController: UIViewController {
    @IBOutlet private var textView: UITextView!

    private let maxNumberOfLines = 4
    private let maxCharactersCount = 100

    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
    }
}

extension ViewController: UITextViewDelegate {
    private func textView(_ textView: UITextView, newText: String, underLineLimit: Int) -> Bool {
        guard let textViewFont = textView.font else {
            return false
        }

        let rect = textView.frame.inset(by: textView.textContainerInset)
        let textWidth = rect.width - 2.0 * textView.textContainer.lineFragmentPadding

        let boundingRect = newText.size(constrainedToWidth: textWidth,
                                        font: textViewFont)

        let numberOfLines = boundingRect.height / textViewFont.lineHeight

        let underLimit = Int(numberOfLines) <= underLineLimit
        return underLimit
    }

    func textView(_ textView: UITextView,
                  shouldChangeTextIn range: NSRange,
                  replacementText text: String) -> Bool {

        let newText = textView.text.replacingCharacters(in: range, with: text)

        let underLineLimit = self.textView(textView, newText: newText, underLineLimit: maxNumberOfLines)
        let underCharacterLimit = newText.count <= maxCharactersCount
        return underLineLimit && underCharacterLimit
    }
}

extension String {
    func replacingCharacters(in range: NSRange, with string: String) -> String {
        let currentNSString = self as NSString
        return currentNSString.replacingCharacters(in: range, with: string)
    }

    func size(constrainedToWidth width: CGFloat, font: UIFont) -> CGSize {
        let nsString = self as NSString
        let boundingSize = CGSize(width: width, height: .greatestFiniteMagnitude)
        return nsString
            .boundingRect(with: boundingSize,
                          options: .usesLineFragmentOrigin,
                          attributes: [.font: font],
                          context: nil)
            .size
    }
}

这是@Abhinav 的答案,但在Swift 中

func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {


    let newText = (textView.text as NSString).stringByReplacingCharactersInRange(range, withString: text)

    let textAttributes = [NSFontAttributeName: textView.font]

    var textWidth: CGFloat = CGRectGetWidth(UIEdgeInsetsInsetRect(textView.frame, textView.textContainerInset))

    textWidth -= 2.0 * textView.textContainer.lineFragmentPadding

    let boundingRect: CGRect = newText.boundingRectWithSize(CGSizeMake(textWidth, 0), options: NSStringDrawingOptions.UsesLineFragmentOrigin|NSStringDrawingOptions.UsesFontLeading, attributes: textAttributes, context: nil)

  var numberOfLines = CGRectGetHeight(boundingRect) / textView.font.lineHeight;

    return numberOfLines <= 2;
}

这是@Abhinav 为Swift 3更新的答案

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

    guard maxLines > 0 else {
        return true
    }

    let newText = (textView.text as NSString).replacingCharacters(in: range, with: text)

    let textAttributes = [NSAttributedStringKey.font : textView.font!]

    var textWidth: CGFloat = UIEdgeInsetsInsetRect(textView.frame, textView.textContainerInset).width

    textWidth -= 2.0 * textView.textContainer.lineFragmentPadding

    let boundingRect: CGRect = newText.boundingRect(with: CGSize(width: textWidth, height: 0), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: textAttributes, context: nil)

    let numberOfLines = Int(floor(boundingRect.height / textView.font!.lineHeight))

    return numberOfLines <= maxLines

}

其中maxLines是要限制textView的行数。 如果为 0,则行数不受限制。

以防万一,在将其应用于字符串之前,请不要忘记保护范围大小。 否则,如果用户执行此操作,则会崩溃:

键入最大长度的文本插入内容(由于长度限制,将不会插入任何内容,但iOS对此一无所知)撤消插入操作(发生崩溃,原因是范围大于实际的字符串大小)

此外,使用iOS 13用户可能会通过手势意外触发此操作

我建议您将此添加到您的项目中

extension String {
    func replace(with text: String, in range: NSRange) -> String? {
        guard range.location + range.length <= self.count else { return nil }
        return (self as NSString).replacingCharacters(in: range, with: text)
    }
}

并像这样使用它:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    guard let newText = textView.text.replace(with: text, in: range) else { return false }
    return newText.count < maxNumberOfCharacters // Or other checkup here
}

否则,您将不断崩溃于应用程序中

在下面找到 Abhinav 答案的 Objective C 版本:

-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
    CGFloat textWidth = CGRectGetWidth(UIEdgeInsetsInsetRect(textView.frame, textView.textContainerInset));
    textWidth -= 2.0 * textView.textContainer.lineFragmentPadding;

    CGSize boundingRect = [self sizeOfString:newText constrainedToWidth:textWidth font:textView.font];
    NSInteger numberOfLines = boundingRect.height / textView.font.lineHeight;

    return numberOfLines <= 2;
}

-(CGSize)sizeOfString:(NSString *)string constrainedToWidth:(double)width font:(UIFont *)font
{
    return  [string boundingRectWithSize:CGSizeMake(width, DBL_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: font} context:nil].size;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM