開発者向け
SwiftUI の行の高さ:iOS 26 の lineHeight を解説
iOS 26 の SwiftUI で行の高さを指定する方法。.lineHeight のプリセット、multiple(factor:)、lineSpacing との違いを iPhone と iPhone Duo 向けに解説。
AppleDuo 編集部公開 12分で読めます
このページの内容
デザイナーは「17 pt、行の高さ 1.5」のような仕様を渡してきますが、長い間 SwiftUI にはそれを直接表現する方法がありませんでした。行と行の間にポイントを加える .lineSpacing や、控えめな3段階を選べるフォントの .leading で近似するしかなかったのです。iOS 26 では、本物の SwiftUI の行の高さのモディファイアでこれが解決されます。
このガイドでは、lineHeight(_:) モディファイア、受け付けるすべての値、従来の API との比較、そして多くの人が驚く1つの動作、つまり増えたスペースがどこに置かれるかを解説します。内容は Apple のドキュメント、Nil Coalescing の Natalia Panferova による解説、iOS デベロッパ @sarunw の投稿に基づいています。このモディファイアは iOS 26.0 の新機能で、リリース間で詳細が変わる可能性があります。
SwiftUI で行の高さを指定するには?
Text、またはその上位にある任意のビューに .lineHeight(_:) を適用します。Apple はこれを「ビュー階層におけるデフォルトの行の高さのためのモディファイア」と説明しており、.font と同じく環境を通じて機能します。
Text(article.body)
.font(.body)
.lineHeight(.loose)
パラメータはオプショナルの AttributedString.LineHeight で、Apple が連続する2行のベースライン間の距離と定義している Foundation の型です。デフォルトは nil で、その場合、Apple によると SwiftUI は「文脈ごとに適切な行の高さの設定を自動的に選びます」。nil を渡すと、そのサブツリーを自動の動作に戻せます。
このモディファイアと、対応する環境値 lineHeight は、iOS 26.0、iPadOS 26.0、macOS 26.0、Mac Catalyst 26.0、tvOS 26.0、visionOS 26.0、watchOS 26.0 から利用できます。バックデプロイはないので、古いバージョンに対応する場合は #available(iOS 26.0, *) で分岐させてください。
.tight、.normal、.loose の意味は?
AttributedString.LineHeight には、4つの静的プロパティと3つのファクトリメソッドがあります。Apple のドキュメントでのそれぞれの説明は次のとおりです。
| 値 | Apple の説明 | Dynamic Type に合わせて拡大縮小 |
|---|---|---|
.tight | 狭いと感じられるポイントサイズの倍数 | する |
.normal | 標準と感じられるポイントサイズの倍数 | する |
.loose | 広いと感じられるポイントサイズの倍数 | する |
.variable | 各行のフォントのアセントとディセントに基づく | する |
.multiple(factor:) | 最大のポイントサイズに factor を掛けたもの | する |
.leading(increase:) | 最大のポイントサイズに increase ポイントを加えたもの | 一部:フォントは大きくなるが、加算分は変わらない |
.exact(points:) | ポイント単位の固定の合計 | しない |
Apple は3つのプリセットの具体的な数値を公開していません。@sarunw によると、これらは .multiple(factor:) の近道で、.tight は 1.0、.normal は 1.2、.loose は 1.5 です。特定の値が必要な場合は、コードがこの対応関係に依存しないよう、倍率を自分で書きましょう。
Nil Coalescing の比較では、.normal と .variable はデフォルトとほとんど区別がつきませんでした。違いは定義にあります。.variable は各行のフォントのメトリクスに従いますが、その他の値は、Apple の言葉では「使用中のフォントの実効的なアセントとディセント」にかかわらず一定です。
.multiple(factor:) は CSS の line-height とどう違う?
Web の経験があるなら、使うべきは .multiple(factor:) です。@sarunw の言葉を借りれば、.lineHeight(.multiple(factor: 1.5)) は CSS の line-height: 1.5 と同じように機能し、行の高さはフォントサイズに倍率を掛けたものになります。
Apple の定義も同じですが、1つ補足があります。倍率が掛けられるのは、その行の最大のポイントサイズです。17 pt の本文に 22 pt の語句が混ざった行では、22 pt が使われます。
Text(article.body)
.font(.body)
.lineHeight(.multiple(factor: 1.5))
.body はテキストスタイルなので、ポイントサイズはユーザーの Dynamic Type の設定に応じて変わり、行の高さもそれに合わせて変わります。Nil Coalescing によると、multiple(factor:) は Dynamic Type のサイズが変わっても間隔の比率を保ちます。
残りの2つのメソッドは、その柔軟性と引き換えに制御しやすさを得ます。
Text(caption)
.lineHeight(.leading(increase: 6))
Text(ticketNumber)
.lineHeight(.exact(points: 24))
.leading(increase:) は最大のポイントサイズに固定のポイント数を加えたものなので、フォントが大きくなっても間隔は変わりません。.exact(points:) はフォントをまったく考慮しません。Nil Coalescing は、固定の値では大きなテキストサイズで行が重なったり切れたりすることがあると注意しているので、使うとしても短い固定サイズのテキストに限りましょう。
lineHeight を使うと1行のラベルが上に寄って見えるのはなぜ?
ここが落とし穴です。ブラウザは CSS の行の高さで増えたスペースを半分に分け、テキストの上下に置きます。CSS の仕様ではこれを half-leading と呼んでいます。@sarunw によると、SwiftUI の .lineHeight は増えたスペースをすべて各行の下に置き、それは「1行しかない場合でも」同じです。そのため、.multiple(factor: 2) を指定した1行のラベルは「上に寄りすぎているように見えます」。
段落では、これは問題になりません。スペースは行と行の間に入り、それこそが望んでいたことだからです。しかし、ボタン、チップ、中央揃えの行の中にある1行のラベルでは、テキストが高くなったフレームの上部に寄り、フレームを中央揃えにしてもテキストは中央になりません。
対処方法はいくつかあります。
- モディファイアの範囲を絞る。
.lineHeightは環境を通じて伝わるので、画面全体ではなく段落のTextに適用しましょう。親が設定している場合は、ラベルを.lineHeight(nil)でリセットします。 - ベースラインで揃える。 行の中では
HStack(alignment: .firstTextBaseline)や.lastTextBaselineを使い、ラベルをフレームではなくテキストで揃えましょう。そうすれば、行の下に増えたスペースで位置がずれることはありません。 - 短いテキストの倍率は控えめに。 大きな倍率が役立つのは主に長文を読む場面です。見出しやラベルでは、デフォルトより大きくする必要はほとんどありません。
HStack(alignment: .firstTextBaseline) {
Text(item.title)
.font(.headline)
Spacer()
Text(item.price, format: .currency(code: "USD"))
.font(.subheadline)
}
.lineHeight(nil)
ここでは、上位で設定された行の高さをこの行は無視し、2つのラベルは最初のベースラインで揃います。間隔の問題が最初に現れるのは大きな Dynamic Type のサイズなので、どちらもそのサイズでテストしてください。
lineHeight、lineSpacing、leading の違いは?
SwiftUI には行と行の間のスペースを変える方法が3つあり、それぞれ測っているものが異なります。
| API | 導入 | 設定するもの | 単位 |
|---|---|---|---|
.lineHeight(_:) | iOS 26 | ベースラインからベースラインまで | 倍率またはポイント |
.lineSpacing(_:) | iOS 13 | ある行の下端から次の行の上端まで | ポイント |
Font.leading(_:) | iOS 14 | フォント自体の行間 | .standard、.loose、.tight |
lineSpacing(_:) は、行と行の間に固定の間隔を加えます。便利ではありますが、行の高さを倍数で指定するデザイン仕様には対応せず、間隔も Dynamic Type に合わせて大きくなりません。Apple の例では .lineSpacing(10) が使われています。
Font.leading(_:) は、.font(.title.leading(.loose)) のようにフォントに適用します。Apple によると「行間の調整が使えるかどうかはフォントによって異なり」、一部のフォントではこのモディファイアは効果がありません。Nil Coalescing は、その効果をごくわずかだと説明しています。
iOS 26 以降の新しいコードでは、デザイナーがテキストを指定する方法に合うのは .lineHeight です。古いデプロイメントターゲット向けには .lineSpacing を残しておきましょう。
contentTransition(.numericText()) で数字を回転させるには?
関連するテキストの効果で、スコア、カウンター、価格でよく求められるものがあります。@sarunw が指摘しているように、.contentTransition(.numericText()) は「数字を新しい値へ回転させます」。フェードではなく、変わる桁がそれぞれ新しい値へスライドします。
struct ScoreView: View {
@State private var score = 0
var body: some View {
VStack {
Text(score, format: .number)
.font(.largeTitle.monospacedDigit())
.contentTransition(.numericText(value: Double(score)))
Button("Add 10") {
withAnimation { score += 10 }
}
}
}
}
これを機能させるポイントが2つあります。1つ目は、contentTransition(_:) についての Apple のヒントにあるとおり、アニメーションの中でしか効果がないことです。値は withAnimation の中で変更してください。2つ目は、iOS 17 の numericText(value:) が、古い値と新しい値の差から方向を選ぶことです。iOS 16 の古い numericText(countsDown:) は、代わりに方向を Bool で受け取ります。
.monospacedDigit() のフォントを使うと、桁が変わる間も数字の幅が一定に保たれます。数字の隣にアニメーションするシンボルを置く場合は、SF Symbols のアニメーションとレンダリングモードのガイドをご覧ください。
iPhone Duo では行の高さがより重要になる?
iPhone Duo では、内側ディスプレイは iPad のように regular の幅を返し、外側ディスプレイは他の iPhone と同じように振る舞います(iPhone Duo のサイズクラスのガイドを参照)。内側ディスプレイいっぱいに広がる読み物のビューでは、外側ディスプレイよりもずっと行が長くなることがあります。
行が長いほど、通常は行の高さを少し大きくしたほうが読みやすくなります。外側ディスプレイでちょうどよく見える値も、内側ディスプレイ全体では窮屈に感じることがあります。.lineHeight は環境値なので、サイズクラスに連動させられます。
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
Text(article.body)
.font(.body)
.lineHeight(.multiple(factor: horizontalSizeClass == .regular ? 1.5 : 1.3))
.frame(maxWidth: 680)
}
ここでの倍率は例であり、Apple のガイダンスではありません。広いディスプレイでも行の長さを読みやすく保てるので、frame(maxWidth:) で幅を制限するほうが、行の高さよりも効果的なことがよくあります。
まとめ
- iOS 26 では
.lineHeight(_:)が加わりました。AttributedString.LineHeightを受け取り、環境を通じて伝わります。nilは自動を意味します。 .multiple(factor: 1.5)は CSS のline-height: 1.5と同じように機能し、Dynamic Type に合わせて拡大縮小します。@sarunw によると、.tight、.normal、.looseは倍率 1.0、1.2、1.5 です。.leading(increase:)は固定のポイントを加算します。.exact(points:)は拡大縮小せず、大きなサイズでは切れることがあります。- 増えたスペースはすべて各行の下に置かれるので、1行のラベルは上に寄って見えます。モディファイアの範囲を絞り、行はテキストのベースラインで揃えましょう。
.lineHeightはベースラインからベースラインまでを測り、.lineSpacingは行と行の間に間隔を加え、Font.leadingは控えめな3段階を提供します。- 数字を回転させるには、
withAnimationの中で.contentTransition(.numericText(value:))を使います。
次は、iOS 27 での SwiftUI のドラッグによる並べ替えとスワイプアクションを確認するか、折りたたみのために作られたゲームで、iPhone Duo の2つのディスプレイにすでに対応しているアプリをご覧ください。
よくある質問
SwiftUI で行の高さを指定するには?
iOS 26 以降では、Text または任意の親ビューに .lineHeight(_:) を適用します。たとえば .lineHeight(.multiple(factor: 1.5)) です。値は環境を通じて下位に伝わります。nil を渡すと SwiftUI の自動の行の高さに戻ります。
.tight、.normal、.loose はどんな値を使いますか?
Apple のドキュメントでは、それぞれ狭い、標準、広いと感じられるポイントサイズの倍数に基づく一定の行の高さと説明されています。デベロッパの @sarunw によると、これらは .multiple(factor:) の 1.0、1.2、1.5 と同じです。
SwiftUI の lineHeight と lineSpacing の違いは?
lineSpacing(_:) は、ある行の下端と次の行の上端の間にポイント単位で間隔を加えます。lineHeight(_:) は、あるベースラインから次のベースラインまでの距離を、フォントサイズの倍数またはポイント値で指定します。
SwiftUI の lineHeight は Dynamic Type に合わせて拡大縮小しますか?
multiple(factor:) はポイントサイズに掛け算するので拡大縮小します。leading(increase:) はフォントに合わせて大きくなりますが、加算するポイント数は固定です。exact(points:) は合計が固定なので拡大縮小せず、大きなサイズではテキストが切れたり重なったりすることがあります。
lineHeight を使うと1行のラベルが上に寄って見えるのはなぜ?
@sarunw が指摘しているように、.lineHeight は行が1行しかない場合でも、増えたスペースをすべて各行の下に置きます。大きな倍率は複数行のテキストにだけ適用し、1行のテキストはフレームを中央揃えにするのではなく、テキストのベースラインで揃えましょう。
出典
- lineHeight(_:) · Apple Developer Documentation
- AttributedString.LineHeight · Apple Developer Documentation
- multiple(factor:) · Apple Developer Documentation
- exact(points:) · Apple Developer Documentation
- leading(increase:) · Apple Developer Documentation
- lineSpacing(_:) · Apple Developer Documentation
- Font.Leading · Apple Developer Documentation
- numericText(value:) · Apple Developer Documentation
- contentTransition(_:) · Apple Developer Documentation
- iOS 26 adds a .lineHeight modifier to SwiftUI · @sarunw on X
- .lineHeight presets .tight, .normal, .loose = .multiple(factor:) 1.0, 1.2, 1.5 · @sarunw on X
- .lineHeight(.multiple(factor: 1.5)) works like CSS line-height: 1.5 · @sarunw on X
- .lineHeight puts all extra space below each line · @sarunw on X
- .contentTransition(.numericText()) makes numbers roll to their new value · @sarunw on X
- Adjusting line height in SwiftUI on iOS 26 · Nil Coalescing
- CSS 2.2: Visual formatting model details (line-height) · W3C
折りたたみを、アプリの一部に。
AppleDuo は iPhone Duo を本当に活かしたアプリとゲームを紹介しています。App Store へのリンクを添えて送ってください。ほかの開発者の作品も見られます。