0Pricing
Swift Academy · レッスン

Layoutプロトコルの基礎

sizeThatFitsとplaceSubviewsを実装します。

「Layoutプロトコルの基礎」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

カスタムレイアウトが必要な理由

SwiftUIにはHStack、VStack、ZStackが用意されていますが、放射状メニュー、フローレイアウト、メイソングリッドなど、これらでは表現できない配置ロジックが必要になることがあります。

Layoutプロトコルを使えば、そのロジックを記述しながら、通常のSwiftUIビューと組み合わせられます。

Layoutプロトコル

Layoutには2つのメソッドが必要です。sizeThatFits(自分のサイズはどれくらいか)とplaceSubviews(各子要素をどこに配置するか)です。

値型を適合させ、組み込みのスタックと同じように使います。

struct MyLayout: Layout {
    func sizeThatFits(proposal: ProposedViewSize,
                      subviews: Subviews,
                      cache: inout ()) -> CGSize { .zero }
    func placeSubviews(in bounds: CGRect,
                       proposal: ProposedViewSize,
                       subviews: Subviews,
                       cache: inout ()) { }
}

カスタムレイアウトの使用

Layoutの値はコンテナとして機能します。スタックとまったく同じように、末尾クロージャで子ビューを渡します。

var body: some View {
    MyLayout {
        Text("One")
        Text("Two")
        Text("Three")
    }
}

ProposedViewSize

親はProposedViewSizeを通じてレイアウトにサイズを提案します。そのwidthとheightはオプショナルです。nilは「未指定なので、理想のサイズを選ぶ」という意味です。

特殊な値である.zero、.infinity、.unspecifiedは、それぞれ最小サイズ、最大サイズ、理想サイズを調べるために使います。

func sizeThatFits(proposal: ProposedViewSize,
                  subviews: Subviews,
                  cache: inout ()) -> CGSize {
    let width = proposal.width ?? 0
    return CGSize(width: width, height: 44)
}

sizeThatFitsの詳細

sizeThatFitsは、提案されたサイズに応じてレイアウトが必要とするサイズを返します。SwiftUIは柔軟性を把握するため、異なる提案でこのメソッドを複数回呼び出すことがあります。

func sizeThatFits(proposal: ProposedViewSize,
                  subviews: Subviews,
                  cache: inout ()) -> CGSize {
    // Sum child ideal sizes for a simple horizontal stack
    let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
    let w = sizes.reduce(0) { $0 + $1.width }
    let h = sizes.map(\.height).max() ?? 0
    return CGSize(width: w, height: h)
}

Subviewsコレクション

Subviewsは各子要素のプロキシを格納するコレクションです。各プロキシはsizeThatFitsに応答し、レイアウト優先度やカスタム値を公開します。

実際のビューを見ることはなく、これらの測定用プロキシだけを扱います。

for subview in subviews {
    let size = subview.sizeThatFits(.unspecified)
    print(size)
}

placeSubviews

placeSubviewsは、渡されたbounds矩形内の各子要素の位置を決めます。

各サブビューのプロキシに対してplace(at:anchor:proposal:)を呼び出します。

func placeSubviews(in bounds: CGRect,
                   proposal: ProposedViewSize,
                   subviews: Subviews,
                   cache: inout ()) {
    var x = bounds.minX
    for subview in subviews {
        let size = subview.sizeThatFits(.unspecified)
        subview.place(at: CGPoint(x: x, y: bounds.midY),
                      anchor: .leading,
                      proposal: .unspecified)
        x += size.width
    }
}

配置時のアンカー

anchorパラメーターは、指定した位置に子要素のどの点を配置するかを示します。.topLeadingは左上の角を基準に配置し、.centerは指定した点を子要素の中央にします。

subview.place(at: CGPoint(x: 0, y: 0),
              anchor: .topLeading,
              proposal: .unspecified)

boundsは常に原点から始まるとは限らない

bounds矩形は(0,0)から始まるとは限りません。常にbounds.minXとbounds.minYを基準に配置し、絶対的なゼロを基準にしないでください。

let startX = bounds.minX // not 0
let startY = bounds.minY

最小限の等間隔レイアウト

ここまでの内容を組み合わせると、子要素を間隔なしで左から右へ並べるレイアウトになります。2つのメソッドは一貫しており、報告するサイズと配置が一致します。

struct SimpleHStack: Layout {
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
        return CGSize(width: sizes.reduce(0){ $0 + $1.width },
                      height: sizes.map(\.height).max() ?? 0)
    }
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        var x = bounds.minX
        for s in subviews {
            let size = s.sizeThatFits(.unspecified)
            s.place(at: CGPoint(x: x, y: bounds.minY), anchor: .topLeading, proposal: .unspecified)
            x += size.width
        }
    }
}

一貫性は自分の責任

SwiftUIは、2つのメソッドが一致することを前提にします。placeSubviewsがsizeThatFitsの報告したサイズより広い領域を使うと、子要素が切り取られたり重なったりする可能性があります。

両方のメソッドでジオメトリの計算を共有し、一貫性を保ってください。

理解度チェック:レイアウトの基本

Layoutプロトコルについての理解度を確認しましょう。

まとめ:Layoutプロトコルの基本

Layoutプロトコルを使うと、カスタムの配置ロジックを再利用可能なコンテナにできます。sizeThatFitsはProposedViewSizeに対するレイアウトのサイズを報告し、placeSubviewsはplace(at:anchor:proposal:)を使ってbounds内に各子要素のプロキシを配置します。

提案にはオプショナルの寸法が含まれ、boundsは原点からずれている場合があります。また、2つのメソッドはジオメトリ上の一貫性を保つ必要があります。次はサブビューを詳しく測定します。

よくある質問

「Layoutプロトコルの基礎」レッスンは無料ですか?

はい。「Layoutプロトコルの基礎」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「Layoutプロトコルの基礎」で何を学びますか?

sizeThatFitsとplaceSubviewsを実装します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「Layoutプロトコルの基礎」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Layoutプロトコルの基礎
  2. サブビューの測定
  3. Flow Layoutの構築
  4. レイアウトキャッシュとパフォーマンス
← Swift Academyに戻る