Jak obliczyć szerokość ciągu tekstowego określonej czcionki i rozmiar czcionki?

82

Mam UILabel, który wyświetla kilka znaków. Na przykład „x”, „y” lub „rpm”. Jak mogę obliczyć szerokość tekstu na etykiecie (nie wykorzystuje całej dostępnej przestrzeni)? Służy do automatycznego układania, gdzie inny widok będzie miał większy prostokąt ramki, jeśli ten UILabel ma mniejszy tekst w środku. Czy istnieją metody obliczania tej szerokości tekstu, gdy określono UIFont i rozmiar czcionki? Nie ma też podziału wiersza i tylko jednej linii.


źródło
Nie wiem, jak byś to zrobił z dowolnym rodzajem czcionki, jednak jeśli używasz czcionki o stałej szerokości, możesz obliczyć, używając liczby znaków. Nie jestem do końca pewien wzoru.
jgallant

Odpowiedzi:

74

Możesz to zrobić dokładnie za pomocą różnych sizeWithFont:metod w NSString UIKit Additions . W twoim przypadku najprostszy wariant powinien wystarczyć (ponieważ nie masz etykiet wieloliniowych):

NSString *someString = @"Hello World";
UIFont *yourFont = // [UIFont ...]
CGSize stringBoundingBox = [someString sizeWithFont:yourFont];

Istnieje kilka odmian tej metody, np. niektórzy rozważają tryby łamania linii lub maksymalne rozmiary.

Daniel Rinser
źródło
1
Czy nie powinien to być UIFont * yourFont = // [UIFont ...]; chociaż?
PinkFloydRocks
11
„sizeWithFont:” jest przestarzałe. Odpowiedź przez wcochrana powinna być zaznaczona.
Ferran Maylinch
4
Ponieważ masz zielony znacznik wyboru, powinieneś zmienić odpowiedź, aby użyć sizeWithAttributes.
Glenn Howes,
80

Ponieważ sizeWithFontjest przestarzały, po prostu zaktualizuję moją pierwotną odpowiedź na używanie Swift 4 i.size

//: Playground - noun: a place where people can play

import UIKit

if let font = UIFont(name: "Helvetica", size: 24) {
   let fontAttributes = [NSAttributedStringKey.font: font]
   let myText = "Your Text Here"
   let size = (myText as NSString).size(withAttributes: fontAttributes)
}

Powinien to być rozmiar ekranu „Twój tekst tutaj” w punktach.

Glenn Howes
źródło
Dziękuję za wysłanie tego rozwiązania. Napisałem rozszerzenie na podstawie Twojej odpowiedzi. Jest zamieszczone poniżej.
Adrian
Jaka będzie odpowiednia funkcja w Swift 4?
Swayambhu
77

sizeWithFont:jest przestarzały, użyj sizeWithAttributes:zamiast tego:

UIFont *font = [UIFont fontWithName:@"Helvetica" size:30];
NSDictionary *userAttributes = @{NSFontAttributeName: font,
                                 NSForegroundColorAttributeName: [UIColor blackColor]};
NSString *text = @"hello";
...
const CGSize textSize = [text sizeWithAttributes: userAttributes];
wcochran
źródło
8
Uwaga: sizeWithAttributes:metoda zwraca rozmiary ułamkowe; aby użyć zwróconego rozmiaru do rozmiaru widoków, należy podnieść jego wartość do najbliższej większej liczby całkowitej za pomocą funkcji ceil.
Allen
55

Aktualizacja z września 2019 r

Ta odpowiedź jest dużo czystszym sposobem na zrobienie tego przy użyciu nowej składni.

Oryginalna odpowiedź

W oparciu o doskonałą odpowiedź Glenna Howesa stworzyłem rozszerzenie do obliczania szerokości ciągu. Jeśli robisz coś takiego, jak ustawienie szerokości a UISegmentedControl, może to ustawić szerokość na podstawie ciągu tytułu segmentu.

extension String {

    func widthOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.width
    }

    func heightOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.height
    }

    func sizeOfString(usingFont font: UIFont) -> CGSize {
        let fontAttributes = [NSAttributedString.Key.font: font]
        return self.size(withAttributes: fontAttributes)
    }
}

stosowanie:

    // Set width of segmentedControl
    let starString = "⭐️"
    let starWidth = starString.widthOfString(usingFont: UIFont.systemFont(ofSize: 14)) + 16
    segmentedController.setWidth(starWidth, forSegmentAt: 3)
Adrian
źródło
1
Jakie będzie rozwiązanie dla Swift 4?
Swayambhu
1
Dlaczego nie tylko jedna funkcja, która zwraca rozmiar (CGSize)? Dlaczego pracuję dwa razy?
wcochran
@wcochran Zaktualizowano. Świetna rozmowa.
Adrian
1
Nie wiem jak, ale to daje mi nieprawidłowe rozmiary.
Max
Nieważne, to działa, ale z moich testów wynika, że ​​musisz dodać dodatkowe wypełnienie co najmniej 15 , aby zapobiec obcięciu tekstu.
Max
33

Swift-5

Użyj intrinsicContentSize, aby znaleźć wysokość i szerokość tekstu.

yourLabel.intrinsicContentSize.width

To zadziała, nawet jeśli masz niestandardowe odstępy między ciągami, takie jak „TEX T”

Alok
źródło
28

Oneliner w Swift 4.2 🔸

let size = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:18.0)])
eonista
źródło
6

To proste rozszerzenie w Swift działa dobrze.

extension String {
    func size(OfFont font: UIFont) -> CGSize {
        return (self as NSString).size(attributes: [NSFontAttributeName: font])
    }
}

Stosowanie:

let string = "hello world!"
let font = UIFont.systemFont(ofSize: 12)
let width = string.size(OfFont: font).width // size: {w: 98.912 h: 14.32}
JsW
źródło
3

Szybki 4

extension String {
    func SizeOf(_ font: UIFont) -> CGSize {
        return self.size(withAttributes: [NSAttributedStringKey.font: font])
    }
}
WM
źródło
2

To jest dla szybkiej wersji 2.3. Możesz uzyskać szerokość sznurka.

var sizeOfString = CGSize()
if let font = UIFont(name: "Helvetica", size: 14.0)
    {
        let finalDate = "Your Text Here"
        let fontAttributes = [NSFontAttributeName: font] // it says name, but a UIFont works
        sizeOfString = (finalDate as NSString).sizeWithAttributes(fontAttributes)
    }
Mandeep Singh
źródło
2

Jeśli masz problemy z uzyskaniem szerokości tekstu z obsługą wielu wierszy , możesz użyć następnego kodu ( Swift 5 ):

func width(text: String, height: CGFloat) -> CGFloat {
    let attributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.systemFont(ofSize: 17)
    ]
    let attributedText = NSAttributedString(string: text, attributes: attributes)
    let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height)
    let textWidth = attributedText.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil).width.rounded(.up)

    return textWidth
}

I w ten sam sposób możesz znaleźć wysokość tekstu, jeśli musisz (po prostu przełącz implementację constraintBox):

let constraintBox = CGSize(width: maxWidth, height: .greatestFiniteMagnitude)

Lub tutaj jest ujednolicona funkcja do uzyskiwania rozmiaru tekstu z obsługą wielu wierszy:

func labelSize(for text: String, maxWidth: CGFloat, maxHeight: CGFloat) -> CGSize {
    let attributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.systemFont(ofSize: 17)
    ]

    let attributedText = NSAttributedString(string: text, attributes: attributes)

    let constraintBox = CGSize(width: maxWidth, height: maxHeight)
    let rect = attributedText.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil).integral

    return rect.size
}

Stosowanie:

let textSize = labelSize(for: "SomeText", maxWidth: contentView.bounds.width, maxHeight: .greatestFiniteMagnitude)
let textHeight = textSize.height.rounded(.up)
let textWidth = textSize.width.rounded(.up)
atereshkov
źródło
1

Dla Swift 3.0+

extension String {
    func SizeOf_String( font: UIFont) -> CGSize {
        let fontAttribute = [NSFontAttributeName: font]
        let size = self.size(attributes: fontAttribute)  // for Single Line
       return size;
   }
}

Używaj go jak ...

        let Str = "ABCDEF"
        let Font =  UIFont.systemFontOfSize(19.0)
        let SizeOfString = Str.SizeOfString(font: Font!)
Lakhdeep Singh
źródło
1

Nie jestem pewien, jak wydajne jest to, ale napisałem tę funkcję, która zwraca rozmiar w punktach, który dopasuje ciąg do określonej szerokości:

func fontSizeThatFits(targetWidth: CGFloat, maxFontSize: CGFloat, font: UIFont) -> CGFloat {
    var variableFont = font.withSize(maxFontSize)
    var currentWidth = self.size(withAttributes: [NSAttributedString.Key.font:variableFont]).width

    while currentWidth > targetWidth {
        variableFont = variableFont.withSize(variableFont.pointSize - 1)
        currentWidth = self.size(withAttributes: [NSAttributedString.Key.font:variableFont]).width
    }

    return variableFont.pointSize
}

I zostałby użyty w ten sposób:

textView.font = textView.font!.withSize(textView.text!.fontSizeThatFits(targetWidth: view.frame.width, maxFontSize: 50, font: textView.font!))

David Chopin
źródło
0

Mój kod polega na utworzeniu rozszerzenia UIFont: (To jest Swift 4.1)

extension UIFont {


    public func textWidth(s: String) -> CGFloat
    {
        return s.size(withAttributes: [NSAttributedString.Key.font: self]).width
    }

} // extension UIFont
MarkAurelius
źródło