【独学】はじめてのiOSプログラミング – 10.5 グリッド表示

グリッド表示

Pixabaya APIから得られた素材検索の結果をグリッド表示する処理を作成します。

グリッドのセルを指定する

グリッド表示を行う際には、まず最初にグリッドのセルを作成する必要があります。
グリッドのセルは GridItem 構造体で行います。
https://developer.apple.com/documentation/swiftui/griditem
GridItem 構造体の init メソッドの書式は次のとおりです。

init(セルのサイズ [, spacing: スペースのピクセル数] [, alignment: 横に寄せる位置])

スペースのピクセル数、横に寄せる位置はオプショナルなので省略可能です。
セルのサイズは 列挙型の GridItem.Size オプジェクトの .adaptive で行います。
https://developer.apple.com/documentation/swiftui/griditem/size-swift.enum/adaptive(minimum:maximum:)
GridItem.Size オブジェクトの .adaptive は、最小と最大のピクセル数を指定して表示時に端末の画面に応じて自動的に最小〜最大の間で最適なサイズを利用できる、というものです。

GridItem.Size.adaptive(minimum: 最小ピクセル数, maximum: 最大ピクセル数)

サンプルでは、100 〜 150px のサイズで表示できるようにセルを定義します。

struct ContentView: View {
    
    @State var inputText = ""
    @State var buttonEnabled = false
    @StateObject var imageLoader = ImageLoader()
    
    let columns: [GridItem] = [GridItem(GridItem.Size.adaptive(minimum: 100, maximum: 150))]
# 略

サイズのみを指定して GridItem を配列で定義しています。配列で定義するのは、グリッド表示の際に複数のサイズを利用することがあるためです。サンプルでは1種類のみのセルを用います。

グリッド表示を定義する

グリッドの表示は、LazyVGrid 構造体で行います。
https://developer.apple.com/documentation/swiftui/lazyvgrid
LazyVGrid 構造体の書式は次のとおりです。

LazyVGrid(columns: セルの配列, spacing: セル間のピクセル数) {
     ForEach( グリッド表示する内容の配列 ) { 配列の要素 in
          // セル内の表示処理
    }
}

グリッドのセルの配列とセル間のピクセル数がインスタンス作成時の引数です。
その後に、グリッドに表示する内容の配列を ForEach 文で処理して、配列の要素 1つ1つに対してセルに表示する処理を行います。
スクロールのUIを表示する ScrollView 内で LazyVGrid を 配置することで、スクロールのあるグリッド表示を実装することができます。
サンプルでは、パースしたJSONの配列を ForEach 文で処理し、画像をグリッドで表示します。

画像を非同期に表示する

パースしたJSON内には、画像のURLが格納されています。画像のURLから画像を取得して表示するには AsyncImage 構造体を利用します。
https://developer.apple.com/documentation/swiftui/asyncimage/

AsyncImage(url: 画像のURLオブジェクト) { 画像オブジェクト in
     // 画像オブジェクトに対する処理
} placeholder: {
     // 画像が表示されるまでに表示するUI
}

AsyncImage 構造体は、URLからの画像の取得、取得した後の画像のリサイズなどの処理、取得するまでに別のUIを表示する、といった3つの処理をインスタンス作成時に行う非常に便利な構造体です。
グリッドのセル内に AsyncImage を配置することで、セル単位で非同期に画像を表示することができます。

検索結果をグリッドで表示する

こでまでに説明したグリッド表示と画像の表示を実装します。ContentView.swift のScrollView 内を次のように編集してください。

struct ContentView: View {
    
    @State var inputText = ""
    @State var buttonEnabled = false
    @StateObject var imageLoader = ImageLoader()
    
    let columns: [GridItem] = [GridItem(GridItem.Size.adaptive(minimum: 100, maximum: 150))]  // --------(1)
    
    var body: some View {
        VStack {
            HStack {
                TextField("検索キーワード", text: $inputText)
                    .onChange(of: self.inputText) {
                        // 3文字以上でボタン押下可能
                        if ($0.count >= 3) {
                            self.buttonEnabled = true
                        } else {
                            self.buttonEnabled = false
                        }
                    }
                    .textFieldStyle(.roundedBorder)
                    .frame(height: 32)
                    .padding([.leading, .trailing], 8)

                Button("検索") {
                    // キーボードを下げる
                    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                    Task {
                        do {
                            // 画像検索
                            try await self.imageLoader.searchImages(keyword: self.inputText)
                        } catch {
                            print(error)
                        }
                    }
                }.disabled(!self.buttonEnabled)
            }
            .frame(height: 64)
            .padding([.leading, .trailing], 16)
            
            ScrollView {
                LazyVGrid(columns: columns, spacing: 20) {   // --------(2)
                    ForEach(self.imageLoader.imageDatas) { item in
                            AsyncImage(url: URL(string: item.largeImageURL)) { image in   // --------(3)
                                image.resizable()
                                     .frame(width: 100, height: 100)
                                     .aspectRatio(contentMode: ContentMode.fit)
                                     .clipShape(Circle())
                            } placeholder: {
                                ProgressView()    // --------(4)
                            }
                    }
                }
                .padding()
            }
        }
    }
}

(1)グリッドで表示するセルを GridItem 構造体で定義します。LazyVGrid のインスタンスで配列の形式で引数として利用するので、配列で定義しています。
(2)ScrollView内に LazyVGrid 構造体を配置します。インスタンスの引数は(1)で定義したセルとセル間のスペース 20px です。ForEach 文ではパースしたJSONの素材情報の配列が格納された 「self.imageLoader.imageDatas」です。
(3)「self.imageLoader.imageDatas」内の素材情報である ImageData 構造体の largeImageURLで素材の画層URLを参照して AsyncImage 構造体で画像を非同期に表示します。画像情報を取得した後に、得られた画像オブジェクトをリサイズして円形で表示しています。
ここで利用しているメソッドと引数は次のとおりです。

メソッド概要
resizable画像をリサイズ可能にする
frameサイズを指定 height:縦ピクセル数 width:横ピクセル数 
aspectRatioアスペクト比を指定 ContentMode.fit:アスペクト比の維持を指定
clipShape形状を指定 Circle():円形に表示

(4)画像が表示されるまでは進捗状態をローディングで表示する ProgressView を表示します。
https://developer.apple.com/documentation/swiftui/progressview

動作確認

シミュレーターを起動してアプリを実行して実装した機能を確認します。

UIの配置とボタンの有効化

検索実行直後にローディング表示

画像を取得できたものから非同期に表示

画像を全部取得できたところ

別のキーワードで検索するとグリッド表示をクリアして同様に新しい検索結果を表示

サンプルを作成してSwiftがオブジェクト指向で高い機能を持ったプログラム言語であることが実感できたと思います。
Appleのアプリ開発者向けページ でも多数のサンプルが公開されていますので、ぜひ楽しみながらアプリの作成を進めてみてください。

タイトルとURLをコピーしました