SwiftUI - ScrollViewとLazyとGridレイアウト

1. ScrollView

alt

ScrollViewはページの内容がディスプレイサイズを超えた場合、スクロールできるようにしてくれるものです。これを入れないと自動でスクロールしてくれません

struct ContentView: View {
  var body: some View {
    ScrollView {
      VStack(alignment: .leading) {
        ForEach(0..<100) {
          Text("Row \($0)")
        }
      }
    }
  }
}

2. LazyVStack

alt

Lazy系はコンテンツが多い場合、使用することでレンダリングのパフォーマンスを上げてくれるものです。詳しい内容は、公式ドキュメントのページに書いてあります。

LazyVStackは縦スクロール

struct Item: View {
  var body: some View {
    RoundedRectangle(cornerRadius: 25.0)
      .fill(Color.gray)
      .frame(width: 200, height: 200)
  }
}

struct ContentView: View {
  var body: some View {
    ScrollView {
      LazyVStack {
        ForEach(0..<20) { item in
          Item()
        }
      }
    }
  }
}

3. LazyHStack

alt

LazyHStackは縦スクロールで、ScrollView(.horizontal)を追記します。

struct Item: View {
  var body: some View {
    RoundedRectangle(cornerRadius: 25.0)
      .fill(Color.gray)
      .frame(width: 200, height: 200)
  }
}

struct ContentView: View {
  var body: some View {
    ScrollView(.horizontal) {
      LazyHStack {
        ForEach(0..<20) { item in
          Item()
        }
      }
    }
  }
}

デフォルトでも多少余白がついてますが、余白を指定したい場合は下記のようにします。

LazyVStack(spacing: 20)

LazyHStack(spacing: 20)

//他の設定
//LazyVStack(alignment:配置, spacing: 余白, pinnedViews: 固定, content: コンテンツ)

4. グループ化したLazyStack

alt

上記のコメントアウトで書いた。pinnedViewsというものは、Gif画像のようにスクロールしたときに部分的に固定できるというものです。

LazyVStack(spacing: 5, pinnedViews: [.sectionHeaders])

下記のコードは、公式ドキュメントに書いてあったコードを少しだけ変えたものですが、Gif画像のようなものが出力できます。

struct SectionHeaderView: View {
  var colorData: ColorData
  
  var body: some View {
    HStack {
      Text(colorData.name)
        .font(.title2)
        .fontWeight(.bold)
        .foregroundColor(colorData.color)
      Spacer()
    }
    .padding()
    .background(Color.primary
                  .colorInvert()
                  .opacity(0.75))
  }
}

struct ColorData: Identifiable {
  let id = UUID()
  let name: String
  let color: Color
  let variations: [ShadeData]
  
  struct ShadeData: Identifiable {
    let id = UUID()
    var brightness: Double
  }
  
  init(color: Color, name: String) {
    self.name = name
    self.color = color
    self.variations = stride(from: 0.0, to: 0.5, by: 0.1)
      .map { ShadeData(brightness: $0) }
  }
}

struct ContentView: View {
  let sections = [
    ColorData(color: .red, name: "Reds"),
    ColorData(color: .green, name: "Greens"),
    ColorData(color: .blue, name: "Blues")
  ]
  
  var body: some View {
    ScrollView {
      LazyVStack(spacing: 5, pinnedViews: [.sectionHeaders]) {
        ForEach(sections) { section in
          Section(header: SectionHeaderView(colorData: section)) {
            ForEach(section.variations) { variation in
              section.color
                .brightness(variation.brightness)
                .frame(height: 200)
            }
          }
        }
      }
    }
  }
}

variations - フォントのバリエーション仕様辞書を返す
brightness - 明るさを指定(Double)
colorInvert() - 色の反転
pinnedViews - スクロールビューの境界に固定できる
stride(from:to:by:) - 開始値から終了値までのシーケンスを返し、指定量だけステップする

stride(from: 開始値, to: 終了値, by: ステップ量)

5. Gridレイアウト

LazyVGrid

縦グリッド

struct Item: View {
  var body: some View {
    Rectangle()
      .fill(Color.gray)
  }
}

struct ContentView: View {
  var body: some View {
    ScrollView {
      LazyVGrid(columns: Array(repeating: GridItem(), count: 4)) {
        ForEach(0..<50) { item in
          Item()
        }
      }
    }
  }
}

LazyHGrid

横グリッド。ScrollViewと併用する場合は、LazyHStackと同じように(.horizontal)を追記。LazyVGrid(columns:)ですがLazyHGrid(rows:)になります。

struct ContentView: View {
  var body: some View {
    ScrollView(.horizontal) {
      LazyHGrid(rows: Array(repeating: GridItem(), count: 4)) {
        ForEach(0..<50) { item in
          Item()
        }
      }
    }
  }
}

6. GridItemのサイズや書き方

LazyVGrid(columns: Array(repeating: GridItem(.fixed(10)), count: 4))

.fixed(100)
//指定された固定サイズの単一アイテム

.flexible() | .flexible(minimum: 50, maximum: 100)
//単一の柔軟なアイテム

.adaptive(minimum: 50) | .adaptive(minimum: 50, maximum: 100)
//1つの柔軟なアイテムのスペースに複数のアイテム。

.fixed

alt

アイテムサイズ固定

LazyVGrid(...)内に、[GridItem]の設定を書かずに、下記のように書くこともできます。下記のように書く場合、var(変数)ではなくlet(定数)にしないとエラーが出て、.init()が必要になります。

let items: [GridItem] = Array(repeating: .init(.fixed(40)), count: 4)

LazyVGrid(columns: items) {
  ...
}

.flexible

alt

カラム数に合わせてアイテムが可変

let items: [GridItem] = Array(repeating: .init(.flexible(minimum: 40, maximum: 200)), count: 4)

LazyVGrid(columns: items) {
  ...
}

.adaptive

alt

アイテムサイズの最小値を設定し、そのアイテムができるだけ多く並ぶように配置します。

let items: [GridItem] = Array(repeating: .init(.adaptive(minimum: 40, maximum: 200)), count: 4)

LazyVGrid(columns: items) {
  ...
}

7. サイズ以外の設定や書き方

alt

countを使用せずに、下記のようにGridItemを複数書く書き方。下記の場合だと固定と可変の組み合わせ

let items = [GridItem(.fixed(100)), GridItem(.flexible())]

LazyVGrid(columns: items) {
  ...
}

他の設定

LazyVGrid(columns: グリッドアイテム, alignment: 配置, spacing: 余白, pinnedViews: 固定,content: コンテンツ) {
  ...
}

LazyHGrid(rows: グリッドアイテム, alignment: 配置, spacing: 余白, pinnedViews: 固定,content: コンテンツ) {
  ...
}

コンテンツを使うとき

LazyVGrid(columns: items, content: {...})

ScrollView | Apple Developer Documentation
LazyVStack | Apple Developer Documentation
PinnedScrollableViews | Apple Developer Documentation
LazyVGrid | Apple Developer Documentation
LazyHGrid | Apple Developer Documentation
GridItem.Size | Apple Developer Documentation