2017-07-15 76 views
0

我想創建一個方法來計算多行標籤的最佳寬度,以在固定高度的水平行中附加多個標籤。如何計算swift中多行文本的最佳標籤寬度

隨着一行文字沒有問題:

let textAttributes: [String : Any] = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.title2)] 

let maximalWidth: CGFloat = text!.boundingRect(
     with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: height), 
     options: [NSStringDrawingOptions.usesLineFragmentOrigin], 
     attributes: textAttributes, 
     context: nil).size.width 

據我瞭解,沒有選擇在這裏表示,我有幾行。當我們計算具有固定寬度的文本的高度時,此方法在其他方向上工作良好。但我有相反的目標。作爲變體,我可以基於最長的單詞(更精確地說,基於最寬的單詞,因爲我們可以有幾個單詞具有相同的字符數,但呈現的寬度不同):

var sizeToReturn = CGSize() 

    let maxWordsCharacterCount = text?.maxWord.characters.count 
    let allLongWords: [String] = text!.wordList.filter {$0.characters.count == maxWordsCharacterCount} 
    var sizes: [CGFloat] = [] 
    allLongWords.forEach {sizes.append($0.size(attributes: attributes).width)} 
    let minimalWidth = (sizes.max()! + constantElementsWidth) 

我在這裏用了兩個字符串擴展創建單詞列表,找到所有最長:

extension String { 
    var wordList: [String] { 
    return Array(Set(components(separatedBy: .punctuationCharacters).joined(separator: "").components(separatedBy: " "))).filter {$0.characters.count > 0} 
    } 
} 

extension String { 
    var maxWord: String { 
     if let max = self.wordList.max(by: {$1.characters.count > $0.characters.count}) { 
     return max 
    } else {return ""} 
} 

}

不是一個糟糕的選擇,但它看起來醜陋的,如果我們甲肝e不能用三行代替的文本,最後有幾個簡短的單詞和一個長的單詞。這個長字,確定了寬度,將被截斷。而更多的,它不是用3分喜歡短的話不太好:

好了,我最小寬度,我有最大寬度。也許,我可以 從最大值到最小值,並在標籤開始被截斷時捕獲。 所以我覺得可以有一個優雅的解決方案,但我卡住了。

+0

您應該使用自動佈局約束來根據所需內容根據需要調整標籤的大小。在故事板中很容易做到這一點,但如果需要的話,也可以通過編程來完成。我會做一些自動佈局的研究:https://www.raywenderlich.com/115440/auto-layout-tutorial-in-ios-9-part-1-getting-started-2 – user3353890

+0

我不明白自動佈局可以幫助在這種情況下。 –

+0

我的意思是,如果我知道寬度,改變標籤尺寸不會有問題。但我無法計算這個寬度。 –

回答

0

Hooray,我找到了一種可能的解決方案。您可以在操場上使用以下代碼:

import UIKit 
import PlaygroundSupport 

//: Just a view to launch playground timeline preview 
let hostView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) 
hostView.backgroundColor = .lightGray 
PlaygroundPage.current.liveView = hostView 

// MARK: - Extensions 
extension String { 
    var wordList: [String] { 
     return Array(Set(components(separatedBy: .punctuationCharacters).joined(separator: "").components(separatedBy: " "))).filter {$0.characters.count > 0} 
    } 
} 

extension String { 
    var longestWord: String { 
     if let max = self.wordList.max(by: {$1.characters.count > $0.characters.count}) { 
      return max 
     } else {return ""} 
    } 
} 

// MARK: - Mathod 

func createLabelWithOptimalLabelWidth (
        requestedHeight: CGFloat, 
       constantElementsWidth: CGFloat, 
    acceptableWidthForTextOfOneLine: CGFloat, //When we don't want the text to be shrinked 
           text: String, 
         attributes: [String:Any] 
    ) -> UILabel { 

    let label = UILabel(frame: .zero) 

    label.attributedText = NSAttributedString(string: text, attributes: attributes) 

    let maximalLabelWidth = label.intrinsicContentSize.width 

    if maximalLabelWidth < acceptableWidthForTextOfOneLine { 

     label.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: maximalLabelWidth, height: requestedHeight)) 
     return label // We can go with this width 
    } 

    // Minimal width, calculated based on the longest word 

    let maxWordsCharacterCount = label.text!.longestWord.characters.count 
    let allLongWords: [String] = label.text!.wordList.filter {$0.characters.count == maxWordsCharacterCount} 
    var sizes: [CGFloat] = [] 
    allLongWords.forEach {sizes.append($0.size(attributes: attributes).width)} 
    let minimalWidth = (sizes.max()! + constantElementsWidth) 


    // Height calculation 
    var flexibleWidth = maximalLabelWidth 
    var flexibleHeight = CGFloat() 

    var optimalWidth = CGFloat() 
    var optimalHeight = CGFloat() 

    while (flexibleHeight <= requestedHeight && flexibleWidth >= minimalWidth) { 

     optimalWidth = flexibleWidth 
     optimalHeight = flexibleHeight 

     flexibleWidth -= 1 

     flexibleHeight = label.attributedText!.boundingRect(
     with: CGSize(width: flexibleWidth, height: CGFloat.greatestFiniteMagnitude), 
     options: [NSStringDrawingOptions.usesLineFragmentOrigin], 
     context: nil).size.height 

     print("Width: \(flexibleWidth)") 
     print("Height: \(flexibleHeight)") 
     print("_______________________") 
    } 

    print("Final Width: \(optimalWidth)") 
    print("Final Height: \(optimalHeight)") 

    label.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: optimalWidth+constantElementsWidth, height: requestedHeight)) 

    return label 
} 

// MARK: - Inputs 

let text: String? = "Determine the fair price"//nil//"Select the appropriate payment method"//"Finalize the order" //"Sell the car"//"Check the payment method" 
let font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.callout) 
let paragraphStyle = NSMutableParagraphStyle() 
paragraphStyle.lineBreakMode = .byWordWrapping 
paragraphStyle.allowsDefaultTighteningForTruncation = true 


let attributes: [String:Any] = [ 
    NSFontAttributeName: font, 
    NSParagraphStyleAttributeName: paragraphStyle, 
    NSBaselineOffsetAttributeName: 0 
] 

if text != nil { 
    let label = createLabelWithOptimalLabelWidth(requestedHeight: 70, constantElementsWidth: 0, acceptableWidthForTextOfOneLine: 120, text: text!, attributes: attributes) 

    label.frame.width 
    label.frame.height 

    label.backgroundColor = .white 
    label.lineBreakMode = .byWordWrapping 
    label.numberOfLines = 3 

    hostView.addSubview(label) 
}