SwiftUIでLazyなGridとStackの使い方を学ぶよ

おはよう:relaxed:
今日はLazyVGrid,LazyVStack,LazyHGridLazyHStackの使い方を勉強するよ。


###Lazyってなにかざくっと勉強
Stackは画面を開いたときにその中の要素をすべて読み込み・描写します。
一方Lazy〜は必要に応じてViewを読み込み・描写をするという違いがあります。

例えば、表示したいViewのサイズが大きかったり、個数が多い場合、Stackで画面を作ってしまうと、パフォーマンスが低下する危険性があります。
Lazy〜を使うことで、必要に応じて読み込みと描写をさせ、処理を低下させずに画面描写を行うことができるようです。

Lazy〜はそれぞれiOS14以上から使用できる比較的新しい構造体です。

[公式：Creating Performant Scrollable Stacks
](https://developer.apple.com/documentation/swiftui/creating-performant-scrollable-stacks)


###LazyVGridとLazyVStackの使い方

[公式：LazyVGrid](https://developer.apple.com/documentation/swiftui/lazyvgrid/)
[公式：LazyVStack](https://developer.apple.com/documentation/swiftui/lazyvgrid/)


【サンプルPGM】

```swift:LazyVGrid.swift
import SwiftUI


//２. GridItem
let columns: [GridItem]=[GridItem(),GridItem()]

struct LazyView: View {
    var body: some View {

        NavigationView {

            //３.ScrollView            
            ScrollView(.vertical) {

                /*---LazyVGridの表示---*/
                // １.LazyVGrid

                LazyVGrid(columns: columns, pinnedViews: [.sectionHeaders]) {
                    
                    //　header, footerで呼び出しているLazyPinnedViewは下のPGMで定義しています。
                    Section(header: LazyPinnedView(title: "GridHeader", color: Color.blue),
                            footer: LazyPinnedView(title: "GridFooter", color: Color.green)) {
                        
                        ForEach(0..<20) { i in
                            Text("Grid: \(i)")
                                .frame(width: 100, height: 50)
                                .background(Color(red: Double(i * 10) / 255, green: 1
                                                  ,blue: Double(i * 30) / 255 ))
                        }
                    }
                }
                



                /*---LazyVStackの表示---*/
                // 5.LazyVStack
                LazyVStack(spacing: 3, pinnedViews: [.sectionHeaders]) {

                    Section(header: LazyPinnedView(title: "ListHeader", color: Color.yellow),
                            footer: LazyPinnedView(title: "ListFooter", color: Color.red)) {
                        ForEach(0..<15) { i in
                            HStack {
                                Spacer()
                                Text("List: \(i)")
                                Spacer()
                            }.padding(.all, 30)
                            .background(Color(red: 1, green: Double(i * 30) / 255
                                              ,blue: Double(i * 10) / 255))
                        }
                    }
                    
                }
            }
            .navigationTitle("Navigation Header")
        }
        
    }
}

struct LazyView_Previews: PreviewProvider {
    static var previews: some View {
        LazyView()
    }
}


```

```swift:LazyPinnedView.swift
import SwiftUI

struct LazyPinnedView: View {
    private var title: String
    private var color: Color
    
    init(title: String, color : Color){
        self.title = title
        self.color = color
    }
    
    var body: some View {
        
        HStack {
            Spacer()
            Text("\(title)")
            Spacer()
        }.padding(.all, 10).background(color)
    }
}

struct LLazyPinnedView_Previews: PreviewProvider {
    static var previews: some View {
        LazyPinnedView(title:"previewHeader", color :Color.blue)
    }
}


```

【挙動】
![demo1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/aaad0bb2-cd10-0264-a353-10d7a4c93674.gif)


####1. LazyVGrid
LazyVGridは下記のような形で使用するよ。

```swift

LazyVGrid (columns: XXXX, PinnedScrollableViews : YYYY)

```

**columns:XXXX**
GridItem型に則って、１行のGridの個数や、１要素のサイズ・行間を指定する。
今回は１行のカラム数=GridItem２個で定義しているね。
これを3つにするとこのように変わります。

<img width="1５０" alt="GridItem3" src ="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/7833760c-8957-8861-8ff8-94c25e0e4d69.png">

下記のような形で定義もできます。

```swift
let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 4)
///Array(repeating: X-リピートしたい文字-X, count: Y-回数-Y)
```


**PinnedScrollableViews:YYYY**
[sectionHeaders][sectionFooters]のどちらかを指定。
リストをスクロールした際に固定される要素を決めているよ。
最初のサンプルはsectionHeadersを使用しています。
sectionFootersを選択した場合の挙動は下記のとおりです。

【挙動】
![demo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/0b457fa8-c5b3-b164-375b-71951a799043.gif)


####2.GridItem
[公式：GridItem](https://developer.apple.com/documentation/swiftui/griditem)
１個１個のGridItemを定義するのに使うよ。
すべてのGridItemは個別にサイズや間隔を設定することができます。
サイズの設定の仕方としては、fixed, flexible,adaptiveの３つのやり方があります。

#####fixed
固定値としてサイズを設定します。
例えば下記のように、１行2カラムをそれぞれ固定値と間隔・寄せを設定した場合

```swift
let columns: [GridItem] = [
    GridItem(.fixed(150), spacing: 16, alignment: .top),
    GridItem(.fixed(200), spacing: 20, alignment: .leading)
]
```


<img width="２５０" alt="GridItem2fixed" src ="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/efc48265-3989-1793-b550-227bcd266822.png">

１行３カラムで、Frameサイズより小さくGridを設定した場合は、画像のように要素同士が重なって表示されるよ。

```swift
let columns: [GridItem] = [
    GridItem(.fixed(50),spacing: 16),
    GridItem(.fixed(70),spacing: 16),
    GridItem(.fixed(100),spacing: 16)
]
```

<img width="２５０" alt="GridItem3fixed" src ="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/47c74149-9336-9bc4-b686-a0486e6391ef.png">
[公式：fixed(_:)](https://developer.apple.com/documentation/swiftui/griditem/size-swift.enum/fixed(_:))


#####flexible
空いているスペースに合わせて拡張したり縮小したりしてカラムサイズが変わる設定の仕方で、最小値と最大値を設定することができます。
デフォルト値は、minimum(最小)が10、maximum（最大）がinfinityのようです


1行２アイテムの場合

```swift
private var columns: [GridItem] = [
    GridItem(.flexible(maximum: 150)),
    GridItem(.flexible())
]
```

<img width="２５０" alt="GridItem2flexible" src ="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/8ca2dfd2-5923-5429-7f40-443edc950266.png">

1行３アイテムの場合

```swift
private var columns: [GridItem] = [
    GridItem(.flexible(minimum: 70, maximum: 150)),
    GridItem(.flexible(minimum: 70, maximum: 150)),
    GridItem(.flexible())
]
```

<img width="２５０" alt="GridItem2flexible" src ="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/3ea054bb-30b6-f2e2-ad0a-888204dd6aaa.png">

[公式：flexible(minimum:maximum:)](https://developer.apple.com/documentation/swiftui/griditem/size-swift.enum/flexible(minimum:maximum:))

#####adaptive
flexibleは１行のなかで柔軟にアイテムを配置していましたが、adaptiveはその行にまだItemを入れるスペースがある場合は、行を超えて配置がされます。
なので、例えば下記のように設定しても１行３アイテムで詰めて表示がされます。


```swift
private var columns: [GridItem] = [
    GridItem(.adaptive(minimum: 100, maximum: 100)),
    GridItem(.adaptive(minimum: 100))
]
```
<img width="２５０" alt="GridItem2flexible" src ="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/3b50a248-0e94-9232-d049-a10ef3d6e5be.png">

[公式：adaptive(minimum:maximum:)](https://developer.apple.com/documentation/swiftui/griditem/size-swift.enum/adaptive(minimum:maximum:))



####３.ScrollView
ScrollView(.vertical) ・・　縦方向のスクロール
ScrollView(.horizontal) ・・　横方向のスクロール
LazyVGrid, LazyVStack, LazyHStackは必要なアイテムだけ読み込むので、
ScrollViewの中に配置しないと、すべての要素が画面上に表示されなくなってしまうよ。



####4.LazyVStack
LazyVStackは下記のように使うよ。
alignmentやspacingなどの要素はすべて任意なので、不要であればなしでOKだよ。
pinnedViewsはLazyVGridと同じ使い方をするよ。headerとfooterでの固定され方の違いは前のgifをみてね。

```swift
LazyVStack(alignment: XXXX, spacing:XXXX, pinnedViews: XXXX,content: XXXX) {
```

###LazyHGrid, LazyHStackの使い方

[公式：LazyHGrid](https://developer.apple.com/documentation/swiftui/lazyhgrid)
[公式：LazyHStack](https://developer.apple.com/documentation/swiftui/lazyhstack/)

基本的な使い方はLazyVGrid, LazyVStackと同じです。
今回は比較のため、最初のPGMをLazyH~用に最低限に書き換えているので、
すこしレイアウトがおかしいですが、ご了承ください。


```swift:LazyView.swift
let rows: [GridItem] = [GridItem(),GridItem()]

struct LazyView: View {
    var body: some View {
        NavigationView {

            // 変更点1
            ScrollView(.horizontal) {
                VStack {

                    // 変更点2
                    LazyHGrid(rows: rows, alignment: .top, pinnedViews: [.sectionHeaders]) {
                        
                        Section(header: LazyPinnedView(title: "GridHeader", color: Color.blue),
                                footer: LazyPinnedView(title: "GridFooter", color: Color.green)) {
                            
                            ForEach(0..<20) { i in
                                Text("Grid: \(i)")
                                    .frame(width: 100, height: 50)
                                    .background(Color(red: Double(i * 10) / 255, green: 1
                                                      ,blue: Double(i * 30) / 255 ))
                            }
                        }
                    }
                    //変更点3
                    LazyHStack(spacing: 3, pinnedViews: [.sectionHeaders]) {
                        Section(header: LazyPinnedView(title: "ListHeader", color: Color.yellow),
                                footer: LazyPinnedView(title: "ListFooter", color: Color.red)) {
                            ForEach(0..<10) { i in
                                HStack {
                                    Spacer()
                                    Text("List: \(i)")
                                    Spacer()
                                }.padding(.all, 30)
                                .background(Color(red: 1, green: Double(i * 30) / 255
                                                  ,blue: Double(i * 10) / 255))
                            }
                        }
                        
                    }
                }
            }
            .navigationTitle("Navigation Header")
        }
        
    }
}

```

Header固定
【挙動】
![demo4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/ede32192-1e64-a0be-05ae-5b58a03ca31d.gif)


Footer固定
【挙動】
![demo5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482103/b49b3844-e04a-4aff-5693-73b89c20786b.gif)





####変更点１ ScrollView
LazyV〜の場合は縦方向のスクロールverticalを設定していましたが、
LazyH〜の場合は横方向のスクロールhorizontalに変更。


####変更点２ LazyHGrid
LazyVGridのときはcolumnsで１行あたりのカラム数を設定していましたが、
LazyHGridのときは、行数を設定しています。
どちらもGridItemを必要な個数配置して、設定しています。

####変更点3 LazyHStack
単にLazyVStack→LazyHStackで置き換えただけです。同様な設定で使用できます。


###まとめ
今回はLazy系の機能を使ってみて、動きや作り方を学んだよ。
headerが固定されます！とか、Gridの個数を設定できます！とか、実際動かしてみると、
理解が深まった気がしました。

理解に誤りがあれば、ご指摘いただけると幸いです:relaxed: