ヒラギノフォントが切れる問題 SwiftUI編
検証環境 Xcode 11.4.1
iOS+ヒラギノ+UILabel とか UIButton でググると過去の UIKit での問題が参照できます。
この問題は SwiftUI でも発生します。
まずサンプルとしてヒラギノ角ゴのW3を指定して Text
を作成。
(デバッグのため青色の枠線も表示)
Japanese font without Japanese character causes th ...
上の日本語を含まない状態だとpとyの下が切れている。
下の日本語を含んだ状態だと切れずに表示されている。
試しに frame を設定して Text
の表示領域を大きくしてみる。*1
var body: some View { VStack(spacing: 10) { Text("Copy") .font(font) .frame(height: 60) .border(borderColor) Text("Copy") .font(font) .border(borderColor) .frame(height: 60) .border(borderColor) } }
表示領域は大きくなっても文字は切れたまま。
次は Text
の baselineOffset
を試してみる。*2
let font = Font.custom("HiraginoSans-W3", size: 50) let uiFont = UIFont(name: "HiraginoSans-W3", size: 50)! let borderColor = Color(.systemBlue) var body: some View { HStack(spacing: 10) { Text("Copy") .font(font) .border(borderColor) Text("Copy") .font(font) .baselineOffset(-uiFont.descender) .border(borderColor) } }
文字が切れなくなった。
SwiftUI の Font
からはフォント情報の descender
を取得する事ができないため別途 UIFont
を使っている。これを CTFont
を使う様にリファクタリング。
UIFont
の descender
は正の値が入っているが CTFont
の descender
は負の値が入っているので baselineOffset
で使う際の符号も修正している。
let ctFont = CTFontCreateWithName("HiraginoSans-W3" as CFString, 50, nil) var ctFontDescender: CGFloat { CTFontGetDescent(ctFont) } let borderColor = Color(.systemBlue) var body: some View { HStack(spacing: 10) { Text("Copy") .font(.init(ctFont)) .border(borderColor) Text("Copy") .font(.init(ctFont)) .baselineOffset(ctFontDescender) .border(borderColor) } }
見た目の結果は1つ前の実装と同じ。
でも今回の実装の場合 Text
の箇所で Font
を都度 CTFont
から init
するため、表示箇所が多い場合はFont
と UIFont
を1つずつ作成する前回の実装の方が動作としては効率的な気もする。
コードの構造的には今回の CTFont
を使った実装の方が整頓されていると思う。
現在の実装では baselineOffset
で調整した分テキスト全体が上に移動するため、表示文字や周りのUIとの組み合わせによっては見た目が気になってくる。
(↑文字全体が上にシフトしているのが気になる。日本語の場合特に。)
SwiftUI には便利な offset
や padding
が存在するので、これらで baselineOffset
による移動を再調整してやる。
offset
Text("Copy") .font(.init(ctFont)) .baselineOffset(ctFontDescender) .border(Color(.systemRed)) .offset(y: ctFontDescender / 2) .border(borderColor)
padding
Text("Copy") .font(.init(ctFont)) .baselineOffset(ctFontDescender) .border(Color(.systemRed)) .padding(.top, ctFontDescender) .border(borderColor)
これで見た目の問題も解消された。
最後にお好みでExtension化。
毎回の font
baselineOffset
offset or padding
の操作を Extension にしてまとめてやることもできる。*3
extension Text { public func ctFont(_ ctFont: CTFont) -> some View { let descent = CTFontGetDescent(ctFont) return self.font(.init(ctFont)) .baselineOffset(descent) .offset(y: descent / 2) // .padding(.top, ctFontDescender) } }
この Extension の中で条件分岐して日本語フォント(もしくは問題のある特定のフォント)の場合のみ処理を入れて他のフォント利用時にはレイアウトに影響を与えない様にする事も検討できる。
extension CTFont { // quick example var isJapaneseFont1: Bool { let encoding = CTFontGetStringEncoding(self) return encoding == CFStringEncodings.macJapanese.rawValue } // quick example var isJapaneseFont2: Bool { let languages = CTFontCopySupportedLanguages(self) as? [String] return languages?.contains("ja") ?? false } }
上記の日本語フォントの判定実装はちゃんと調査はしてないので取り急ぎの例として。
最終実装
Japanese font without Japanese character causes th ...