Динамически установите выравнивание текста UILabel между .левый и .оправданный

В моем приложении у меня есть UILabelс двумя линиями предустановки. Я могу установить выравнивание текста либо .leftили .justified.

Если я установлю его.left, нет никакой проблемы макета, если есть достаточное пространство между последним словом в строке и максимальной позицией x метки. Тем не менее, когда есть не так много пространства, так что последнее слово очень близко к максимальной позиции x, это выглядит немного странно, потому что он не совсем выровнен (как это было бы .justified.

Если я установлю его.justified, он всегда выровнен хорошо, но иногда расстояние между отдельными персонажами выглядит странно.

Я ищу способ динамической настройки выравнивания текста в зависимости от расстояния между последним словом в первой строке до максимального положения X метки. Скажем, если позиция последнего символа последнего слова меньше 50 , я хочу иметь выравнивание текста.left, иначе я хотел бы иметь .justified. Есть ли способ, как это сделать?

1 ответ

  1. Я взял довольно банальный подход, который требует некоторой вычислительной мощности, но, кажется, работает.


    Прежде всего, я получаю строку в первой строке метки с помощью этого расширения:

    import CoreText
    
    extension UILabel {
    
       /// Returns the String displayed in the first line of the UILabel or "" if text or font is missing
       var firstLineString: String {
    
        guard let text = self.text else { return "" }
        guard let font = self.font else { return "" }
        let rect = self.frame
    
        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(String(kCTFontAttributeName), value: CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil), range: NSMakeRange(0, attStr.length))
    
        let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: rect.size.width + 7, height: 100))
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
    
        guard let line = (CTFrameGetLines(frame) as! [CTLine]).first else { return "" }
        let lineString = text[text.startIndex...text.index(text.startIndex, offsetBy: CTLineGetStringRange(line).length-2)]
    
        return lineString
      }
    }
    

    После этого я вычисляю ширину, метка с номером строки 1 и фиксированной высотой потребуется для этой строки с помощью этого расширения:

    extension UILabel {
    
       /// Get required width for a UILabel depending on its text content and font configuration
       class func calculateWidth(text: String, height: CGFloat, font: UIFont) -> CGFloat {
    
           let label = UILabel(frame: CGRect(x: 0, y: 0, width: CGFloat.greatestFiniteMagnitude, height: height))
           label.numberOfLines = 1
           label.font = font
           label.text = text
           label.sizeToFit()
    
           return label.frame.size.width
       }
    }
    

    Исходя из этого, я могу вычислить расстояние справа и решить, выбрать ли выравнивание текста .leftили.justified, Поэтому основной код выглядит следующим образом:

    // Set text
    myLabel.text = someString
    
    // Change text alignment depending on distance to right
    let firstLineString = myLabel.firstLineString
    let distanceToRight = myLabel.frame.size.width - UILabel.calculateWidth(text: firstLineString, height: myLabel.frame.size.height, font: myLabel.font)
    myLabel.textAlignment = distanceToRight < 20 ? .justified : .left