iOSエンジニアにとって高さの計算は常に大きな関心の対象でした🔍
AutoLayoutの出現によって高さの自動計算ができる手段が提供されましたが、パフォーマンスの問題や宗教的な理由でAutoLayoutを避ける場合もあります
そんな時のために、AutoLayoutを使わないUILabelの高さの計算方法を紹介します💡
文字列の高さの計算
タイトルはUILabelの高さとしましたが、実際に計算するのは文字列の高さになります。
String
(NSString
)にはboundingRect
という文字列の描画領域を計算するメソッドが存在します。
ドキュメント
func boundingRect(with size: CGSize,
options: NSStringDrawingOptions = [],
attributes: [NSAttributedString.Key : Any]? = nil,
context: NSStringDrawingContext?) -> CGRect
表示させたい幅は決まっていると思うので、第一引数size
には幅を指定したサイズを渡します。高さはとりあえずとても大きい数値にします。
第二引数options
には文字描画オプションを渡します。複数行の高さの計算を正確にするには.usesLineFragmentOrigin
を渡せとドキュメントに書いてあります。フォントによっては.usesFontLeading
も渡さないと高さが小さく計算されてしまうので、指定しておくのが無難です。
第三引数attributes
には文字列の属性を渡します。フォントや行間を指定します。
第四引数context
には文字列描画の環境のようなものを渡します。nil
を渡せばデフォルトのものが使われます。
戻り値は描画領域がですが、小数値になっているので、ceil()
を使って整数にしてやります。
今回の目的はUILabelの高さを計算することなので、幅と文字列の属性はUILabelから取得します。文字列のサイズ計算の際は、外から渡ってくる値を使用できるようにします。
以上をまとめて、Stringのextensionを作ります。
extension String {
func makeSize(width: CGFloat, attributes: [NSAttributedString.Key: Any]) -> CGSize {
let bounds = CGSize(width: width, height: .greatestFiniteMagnitude)
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
let rect = self.boundingRect(with: bounds, options: options, attributes: attributes, context: nil)
let size = CGSize(width: rect.size.width, height: ceil(rect.size.height))
return size
}
}
UILabelの高さの計算
こちらは自身の幅と属性を上記のメソッドに渡してやるだけです。
プレーンテキスト用と属性付きテキスト用のメソッドをそれぞれ用意します。
それらをUILabelのextensionとして定義します。
extension UILabel {
func getTextHeight() -> CGFloat {
guard let text = self.text else {
return 0.0
}
let attributes: [NSAttributedString.Key: Any] = [.font : self.font]
let size = text.makeSize(width: self.bounds.width, attributes: attributes)
return size.height
}
func getAttributedTextHeight() -> CGFloat {
guard let attributedText = self.attributedText else {
return 0.0
}
var attributes: [NSAttributedString.Key: Any] = attributedText.attributes(at: 0, effectiveRange: nil)
attributes[.font] = self.font
let size = attributedText.string.makeSize(width: self.bounds.width, attributes: attributes)
return size.height
}
}
これでUILabelの高さの計算ができるようになりました!
終わりに
ここまで書いて思い出したんですが、sizeThatFits()
というメソッドがありましたね・・・🙀
まあそっちはNSStringDrawingOptions
の指定とかできないし、もしうまくいかないという場合は上記の方法を試してみてください👊