Swift Academy · レッスン

Flow Layoutの構築

アイテムを動的に複数行へ折り返します。

レッスン 3/413 ステップ

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

フローレイアウトとは

フローレイアウトは、アイテムを左から右へ配置し、次のアイテムが利用可能な幅を超える場合は新しい行に折り返します。タグチップや単語の折り返しのような動作です。

計画

共有ヘルパーで行の区切りを計算し、sizeThatFitsから合計の高さを返し、placeSubviewsでも同じ計算を再利用します。ロジックを共有すると、2つのメソッドの一貫性を保てます。

配置のモデル化

小さな構造体で各サブビューの配置先を記録します。すべての配置をあらかじめ計算しておくと、両方のプロトコルメソッドを単純にできます。

struct Placement {
    let index: Int
    let point: CGPoint
    let size: CGSize
}

折り返しアルゴリズム

サブビューを順に処理し、現在の x を追跡します。子が最大幅を超える場合は x をリセットし、行の高さと間隔の分だけ下へ移動します。

func layout(_ subviews: Subviews, maxWidth: CGFloat) -> (placements: [Placement], height: CGFloat) {
    var placements: [Placement] = []
    var x: CGFloat = 0, y: CGFloat = 0, rowHeight: CGFloat = 0
    let spacing: CGFloat = 8
    for (i, s) in subviews.enumerated() {
        let size = s.sizeThatFits(.unspecified)
        if x + size.width > maxWidth, x > 0 {
            x = 0; y += rowHeight + spacing; rowHeight = 0
        }
        placements.append(Placement(index: i, point: CGPoint(x: x, y: y), size: size))
        x += size.width + spacing
        rowHeight = max(rowHeight, size.height)
    }
    return (placements, y + rowHeight)
}

sizeThatFitsの実装

sizeThatFitsは、提案された幅に対してアルゴリズムを実行し、提案された幅と計算された合計の高さからなるサイズを返します。

func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
    let maxWidth = proposal.width ?? .infinity
    let result = layout(subviews, maxWidth: maxWidth)
    return CGSize(width: maxWidth == .infinity ? result.placements.map { $0.point.x + $0.size.width }.max() ?? 0 : maxWidth,
                  height: result.height)
}

placeSubviewsの実装

placeSubviewsは同じアルゴリズムを実行してから、各配置をbounds.originでオフセットし、placeを呼び出します。

func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
    let result = layout(subviews, maxWidth: bounds.width)
    for p in result.placements {
        let pt = CGPoint(x: bounds.minX + p.point.x, y: bounds.minY + p.point.y)
        subviews[p.index].place(at: pt, anchor: .topLeading,
                                proposal: ProposedViewSize(p.size))
    }
}

完全なFlowLayout型

各要素を組み合わせると、完全で再利用可能なフローレイアウトになります。共有するlayoutヘルパーにより、サイズと配置の一貫性が保証されます。

struct FlowLayout: Layout {
    var spacing: CGFloat = 8
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let r = layout(subviews, maxWidth: proposal.width ?? .infinity)
        return CGSize(width: proposal.width ?? 0, height: r.height)
    }
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        let r = layout(subviews, maxWidth: bounds.width)
        for p in r.placements {
            subviews[p.index].place(at: CGPoint(x: bounds.minX + p.point.x, y: bounds.minY + p.point.y),
                                    anchor: .topLeading, proposal: ProposedViewSize(p.size))
        }
    }
}

フローレイアウトの使用

通常のコンテナと同じように配置できます。幅が狭くなると、タグは自動的に折り返されます。

var body: some View {
    FlowLayout {
        ForEach(tags, id: \.self) { tag in
            Text(tag)
                .padding(8)
                .background(.blue.opacity(0.2))
                .clipShape(Capsule())
        }
    }
}

各行の最初のアイテムを処理する

x > 0というガードにより、行の最初のアイテムを配置する前に折り返すことを防ぎます。これがないと、コンテナより幅の広いアイテムによって無限ループが発生したり、不要に空の行へ移動したりします。

if x + size.width > maxWidth, x > 0 {
    // wrap only if the row already has content
}

間隔の選択

ここでは、間隔をデフォルト値付きの保存プロパティにしています。実際のプロダクションコードでは、ハードコードした定数ではなく、各サブビューのspacing設定を読み取ってシステムのメトリクスを尊重してください。

var spacing: CGFloat = 8 // configurable per instance

エッジケースのテスト

アイテムが0個の場合、非常に大きなアイテムが1個の場合、多数の小さなアイテムがある場合をテストしてください。正しいフローレイアウトは、コンテンツが空なら高さ0を返し、サイズが大きすぎる単一の子を決してクリップしません。

確認テスト:フローレイアウト

折り返しアルゴリズムについて理解度を確認します。

まとめ:フローレイアウトの構築

フローレイアウトは、現在の x を追跡し、次の子があふれる場合に新しい行へリセットすることでアイテムを折り返します。共有ヘルパーで全配置と合計の高さを計算するため、sizeThatFitsとplaceSubviewsの一貫性を保てます。

配置をbounds.originでオフセットし、各行の最初のアイテムをガードして、空・特大・多数のアイテムのケースをテストしてください。その結果、再利用可能な折り返しコンテナが完成します。

無料で開始

AI チューターと学ぶ Swift — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
122
レッスン
409

よくある質問

「Flow Layoutの構築」レッスンは無料ですか?

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

「Flow Layoutの構築」で何を学びますか?

アイテムを動的に複数行へ折り返します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「Flow Layoutの構築」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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