能登 要

札幌在住のiOSアプリ開発者。SwiftUI により分割されたデバイス間を縦横にやりとりできる考え方に転換しています。

iOSアプリ開発者。2009年のiPhoneアプリ開発開始時期から活動。開発言語のアップデートの中でSwiftUIおよび周辺技術に着目中。

Multiplatform(マルチプラットフォーム)での画面サイズ情報入手手段3種 @ScaledMetric とEnvironment 、GeometryProxy を使い分ける - SwiftUI

画面などのサイズ情報のヒントはUIを構築する上で重要なヒントとなる。プラットフォームに依存したUI構築用フレームワークごとにサイズ情報が提供されていた。

マルチプラットフォームでのUI構築用フレームワークとしてSwiftUIはプラットフォームに依存せずにサイズ情報を取得するサポートが用意されている。

SwiftUIのサイズ情報のサポートはビュー構築機能であったり、新設された定義であったり、組み込み環境変数であったりと提供方法がそれぞれ異なる。

Blogでは提供方法がそれぞれ異なる3点のサイズ情報提供方法について説明する。

SwiftUIのサイズ情報の提供方法

SwiftUIは開発者を宣言的プログラミングへ向かうように促す形でサイズ情報が提供されている。プラットフォーム毎のUIフレームワークは手続き型プログラミングで扱う事を前提としてサイズ情報が提供されていのと対照的となっている。

サイズ情報についてのサポートは把握している限り3種類用意されている。

  • 画面サイズを提供するもの
  • フォントサイズと連動するもの
  • 画面モードを提供するもの

3種類はそれぞれの機能に対応している。

  • GeometryReader/GeometryProxy (ジオメトリーリーダー/ジオメトリープロキシー)
  • @ScaledMetric (スケールメトリック)
  • Size class (サイズ クラス)

SwiftUI Size Catalog Dark

1) 画面サイズを提供するもの(GeometryReader/GeometryProxy)

親レイアウトサイズを提供する。SwiftUI ではGeometryReader/GeometryProxy として提供される。

GeometryReader はViewを構築するタイミングでGeometryProxyを渡す。GeometryReaderはコンテント(content) を1つ含んだ関数で親レイアウトの変化があるたびにViewの再構築を要求する。

親レイアウトの変化は初期構築時はもちろん、画面回転(縦持ち: portrait,横持ち: landscape)によっても変更が通知される。GeometryReader を使う側はGeometryProxy で渡されたサイズに基づいてViewを構築することだけを考えておけば良い。

配置に関して、SwiftUI 1st major releaseではGeometryReader で生成されたViewはSwiftUIの配置特性が反映されていた。SwiftUI 2nd major releaseではGeometryReader独自に配置特性が反映される。具体的にはGeometryReader でText() を作成した時、SwiftUI 1st major releaseだと中央に配置されるのに対して、SwiftUI 2nd major releaseでは左上に配置される。

SwiftUI 1st major releaseのタイミングで作ったアプリをSwiftUI 2nd major releaseに適合するには注意が必要な挙動だがSwiftUI 2nd major release以降から始めるぶんには気にしなくても良い話だろう。

2) フォントサイズと連動するもの(@ScaledMetric)

デバイス(iOS, iPad, macOS)で使用されるフォントサイズと不動小数点型の値を連動させる定義がSwiftUI 2nd major releaseから用意されている。

SwiftUI 1st major release時はSwiftUIで提供された標準フォントサイズ定義を渡すか、GeometryReader/GeometryProxy でフォントサイズを計算する必要があった。それでも画面サイズだけの話でDynamicFont によるフォントサイズ変更までは考慮できなかった。

SwiftUI 2nd major releaseでは@ScaledMetric 定義を新しく用意して問題を解消している。@ScaledMetric 定義は@Bindingや@Stateと同様にSwiftUIレンダリングためのソースとなるので利用者によるフォントサイズ変更があった場合に変更した値を画面上に反映することが期待できる。

フォントサイズとの連動はView定義内で@ScaledMetric 定義を付加した変数を追加する。

@ScaledMetric var \[変数名\]: \[型\] = \[値\]

@ScaledMetric で適用可能な型は不動小数点として定義されている。型を指定しない場合はデフォルトの不動小数点型(2020年の段階だとDouble)となるが、Fontにサイズを指定すると型が合わないという警告が出てしまう。Fontの定義はCoreGraphics由来のCGFloat定義なのでFontのサイズとして使用するのであればCGFloat型として定義しておく(CGFloat型がiOS,macOSのUI向け定義なのでコード上で用途を明示できる)。

連動する標準フォントサイズ定義を指定しない場合はデフォルトで .bodyのサイズ定義が適用されている。.title, .largeTitle などを指定する場合は@ScaledMetric 定義にrelativeTo:パラメーターを渡す。

@ScaledMetric (relativeTo: [フォント定義]) var [変数名]: [] = []

@ScaledMetric に渡す値はwrappedValue: パラメーターとしても渡すことができる。wrappedValue:, relativeTo:を組み合わせと省略を組み合わせると4通りの指定方法がある。

@ScaledMetric var fontSize: CGFloat = 10.0
@ScaledMetric (relativeTo: .body) var fontSize2: CGFloat = 10.0
@ScaledMetric(wrappedValue: 10) var fontSize3: CGFloat
@ScaledMetric(wrappedValue: 10, relativeTo: .body) var fontSize4: CGFloat

@ScaledMetric のサンプルコードを示す。システムフォントをサイズで指定する際に@ScaledMetric で定義された変数を渡している。

/// TestView.swift
import SwiftUI

struct TestView: View {
    @ScaledMetric var fontSize: CGFloat = 10.0
    
    var body: some View {
        Text("Hello, world!")
            .font(.system(size: self.fontSize))
            .padding()
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

3) Size class (@Emvironment)

AppleはSwiftUI 以前からデバイスのサイズを分類方法としてSize class を提供している。SwifyUI で扱いやすさが向上している。

3-1) Size class概要

Size classは、

  • Xcode上でのUIアセットがiPhoneとiPadでそれぞれ別途用意する問題への解決策
  • 問題や画面サイズのバリエーションが増える将来に向けての対策
  • アプリ画面のメイン/サブ切り替えの柔軟化(Split View機能)
  • を解決するために導入されたところがある。

初出は画面誠意を含んだUIアセットを作成するStoryboardで導入された。Storyboardで作成したUIアセットはアクションとアウトレットをソースコードに接続する形で実装するが、UIアセットだけでは記述しきれないため補助的にプログラミングコードを記述する必要があった。

3-2) Size classの分類

Size classはサイズをCompact, Regularの大枠で分類したうえで縦横方向ごとに用意されている。

Size classのRegularは幅が十分にある場合、Compactは幅を狭めた場合の挙動として振る舞うものと考えればわかりやすい。

WebでいうとCollapse design(コラプス デザイン)が良い例になる。Collapse designは幅が十分にある内は左側にメニューが表示され、幅を狭めるとメニューがドロワーメニューに切り替わる。

Size classの場合は縦方向にも適用されるのがWbeと異なる点となる。

3-3) Size class使い方

SwiftUI でのSize classesは@Emvironmentの要素として提供されているためViewを構築する際にSize classesの値を考慮してコードを記述するとのアプリ画面用途に応じたレイアウトを利用者に提供できる。

@Environment(\.verticalSizeClass) var verticalSizeClass
@Environment(\.horizontalSizeClass) var horizontalSizeClass

.verticalSizeClass、.horizontalSizeClassが返す型UIUserInterfaceSizeClassで、unspecified、compact、regularを返す。

まとめ

SwiftUI のサイズん移管する情報は大きく3種提供されているが、SwiftUIの記述の流儀には沿っているがそれぞれ記述方法に違いと異なった経緯をもっている。サイズ情報の提供方法の違いはAppleが意図したところなのであまり曲解せず適用するのがSwiftUI ではベストは選択だろう。

参考