ios UILabel将文本字符串拆分为多行

6tdlim6h  于 2023-05-30  发布在  iOS
关注(0)|答案(1)|浏览(203)

我有一个具有固定宽度和高度的UILabel。我已经为autohrink属性和truncate tail设置了一个最小字体比例,作为换行符属性。线属性设置为0。(下图)
我的问题是,对于某些文本,UILabel将一个单词分成两行。有没有办法解决这个问题?例如,对于单词“友谊”,UILabel在一行中有“friendshi”,在下一行中有“p”。
我尝试过设置不同的换行符类型,但这些更改并没有解决这个问题。

a8jjtwal

a8jjtwal1#

尝试在多行标签上实现字体缩放和换行是一项长期存在的任务。
第一个问题与使用自动收缩没有直接关系。
从换行模式下的Apple's docs
casebyWordWrapping
指示换行发生在单词边界处的值,除非单词不适合一行。
因此,UILabelsystem font / bold / 60,行数:0,约束为w: 300.0, h: 160.0,我们得到:

如果我们将自动收缩设置为0.5的最小尺度,我们会得到同样的结果...因为标签足够高以缠绕在多条线上。
如果我们将表的高度更改为120.0-所以它只够一行的高度-字体缩放将会发生,我们会得到这样的结果:

现在,我假设-因为你有行数设置为零,标签是远远高于需要-你将有更多的文本,而不仅仅是一个单词...但是,让我们先来一个单词的“修复”。
一些搜索发现这篇文章(不是我的):Dynamic Text Resizing in Swift,它使用了几个UILabel扩展来“将文本适合标签”。下面是相关代码-唯一的修改是将旧的NSAttributedStringKey更改为NSAttributedString.Key

extension UIFont {
    
    /**
     Will return the best font conforming to the descriptor which will fit in the provided bounds.
     */
    static func bestFittingFontSize(for text: String, in bounds: CGRect, fontDescriptor: UIFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> CGFloat {
        let constrainingDimension = min(bounds.width, bounds.height)
        let properBounds = CGRect(origin: .zero, size: bounds.size)
        var attributes = additionalAttributes ?? [:]
        
        let infiniteBounds = CGSize(width: CGFloat.infinity, height: CGFloat.infinity)
        var bestFontSize: CGFloat = constrainingDimension
        
        for fontSize in stride(from: bestFontSize, through: 0, by: -1) {
            let newFont = UIFont(descriptor: fontDescriptor, size: fontSize)
            attributes[.font] = newFont
            
            let currentFrame = text.boundingRect(with: infiniteBounds, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes, context: nil)
            
            if properBounds.contains(currentFrame) {
                bestFontSize = fontSize
                break
            }
        }
        return bestFontSize
    }
    
    static func bestFittingFont(for text: String, in bounds: CGRect, fontDescriptor: UIFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> UIFont {
        let bestSize = bestFittingFontSize(for: text, in: bounds, fontDescriptor: fontDescriptor, additionalAttributes: additionalAttributes)
        return UIFont(descriptor: fontDescriptor, size: bestSize)
    }
}

extension UILabel {
    
    /// Will auto resize the contained text to a font size which fits the frames bounds.
    /// Uses the pre-set font to dynamically determine the proper sizing
    func fitTextToBounds() {
        guard let text = text, let currentFont = font else { return }
        
        let bestFittingFont = UIFont.bestFittingFont(for: text, in: bounds, fontDescriptor: currentFont.fontDescriptor, additionalAttributes: basicStringAttributes)
        font = bestFittingFont
    }
    
    private var basicStringAttributes: [NSAttributedString.Key: Any] {
        var attribs = [NSAttributedString.Key: Any]()
        
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = self.textAlignment
        paragraphStyle.lineBreakMode = self.lineBreakMode
        attribs[.paragraphStyle] = paragraphStyle
        
        return attribs
    }
}

然后我们调用label.fitTextToBounds()viewDidLayoutSubviews()中的 *,因为标签大小需要有效 *),结果-同样是system font / bold / 60,行数:0,约束为w: 300.0, h: 160.0

我们看起来很不错
但有两个问题…
首先,这段代码计算标签边界的“最佳拟合”点大小,如果我们将文本更改为“friend”:

我们最终得到了一个111的点大小--我假设这不是你的目标。
所以,让我们做一个小小的修改:

extension UILabel {
    
    func fitTextToBounds(maxPointSize: CGFloat) {
        guard let text = text, let currentFont = font else { return }
        
        let bestFittingFont = UIFont.bestFittingFont(for: text, in: bounds, fontDescriptor: currentFont.fontDescriptor, additionalAttributes: basicStringAttributes)

        // keep the font size less-than-or-equal-to max
        let ps: CGFloat = min(maxPointSize, bestFittingFont.pointSize)

        font = UIFont(descriptor: currentFont.fontDescriptor, size: ps)
    }
    
}

现在我们可以用label.fitTextToBounds(maxPointSize: 60.0)调用它,结果是:

第二个问题是扩展不考虑多行标签...如果我们将字符串更改为“友谊is good”,我们会得到:

所以,让我们试着改变一下……我们将把字符串分割成单词,并找到所需的最小点大小,以便 * 最长的单个单词 * 适合宽度:

extension UILabel {
    
    func multilineFitTextToBounds(maxPointSize: CGFloat) {
        guard let text = text, let currentFont = font else { return }
        
        let a: [String] = text.components(separatedBy: " ")
        var minFS: CGFloat = currentFont.pointSize
        a.forEach { s in
            let bestFittingFont = UIFont.bestFittingFont(for: s, in: bounds, fontDescriptor: currentFont.fontDescriptor, additionalAttributes: basicStringAttributes)
            minFS = min(minFS, bestFittingFont.pointSize)
        }
        minFS = min(minFS, maxPointSize)
        
        font = UIFont(descriptor: currentFont.fontDescriptor, size: minFS)
    }
    
}

我们看起来更好了

不幸的是,如果我们使用这个文本-“友谊对世界上的每个人都有好处”-字符串将超过标签的高度:

所以,让我们*实现自动收缩:

label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.5

label.multilineFitTextToBounds(maxPointSize: 60.0)

然后...

除非我误解了你的任务,或者你有额外的要求,那可能是一个解决方案。
下面是一个使用上述扩展的控制器示例:

class LabelTestVC: UIViewController {
    
    let label = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        label.font = .systemFont(ofSize: 60.0, weight: .bold)
        label.numberOfLines = 0
        label.backgroundColor = .green
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            label.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            label.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            label.widthAnchor.constraint(equalToConstant: 300.0),
            label.heightAnchor.constraint(equalToConstant: 160.0),
            
        ])
        
        label.adjustsFontSizeToFitWidth = true
        label.minimumScaleFactor = 0.5
        
        label.text = "FRIENDSHIP is good for everyone in the world."
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        label.multilineFitTextToBounds(maxPointSize: 60.0)
    }
    
}

相关问题