【SwiftUI】LazyVGridを使って簡単なSF Symbolsのカタログアプリを作る

2022.03.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

SwiftUIでUICollectionView的な表現の仕方を学ぶついでに簡単なSF Symbolsのカタログアプリを作ってみることにしました。

はじめに

今回は縦方向にスクロール出来るUICollectionView的な表現として、ScrollViewLazyVGridを使っています。

作ったもの

sfsymbols_catalog

環境

  • Xcode 13.2.1
  • iOS 15.0
  • SFUserFriendlySymbols 0.2.2

SF Symbolsライブラリ

今回、SF Symbolsのライブラリとして自分で作ったユーザーに優しいSF Symbolsライブラリを使用します。

ユーザーに優しいSF Symbolsライブラリをリリースしました

パッケージの追加

  1. Xcode上で FileAdd Packages... を押します。
  2. 開かれたページの右上にある検索窓に https://github.com/littleossa/SFUserFriendlySymbols.gitを入力します。

    searchbox

  3. SFUserFriendlySymbolsのパッケージが現れたら、Add Packageボタンを押します。

  4. 完了すると、packageが追加されます

Grid内で使用するViewを作成する

CatalogItem

import SwiftUI
import SFUserFriendlySymbols

struct CatalogItem: View {

    private let symbol: SFSymbols
    private let color: Color
    private let backgroundColor: Color
    private let baseLength: CGFloat
    private let imageWidth: CGFloat

    init(symbol: SFSymbols, color: Color, backgroundColor: Color) {
        self.symbol = symbol
        self.color = color
        self.backgroundColor = backgroundColor
        baseLength = UIScreen.main.bounds.width * 0.25
        imageWidth = baseLength * 0.5
    }

    var body: some View {

        VStack(alignment: .center) {

            ZStack {
                                RoundedRectangle(cornerRadius: 16)
                    .foregroundColor(backgroundColor)

                RoundedRectangle(cornerRadius: 16)
                    .stroke(.gray, lineWidth: 0.5)

                Image(symbol: symbol)
                    .resizable()
                    .scaledToFit()
                    .frame(width: imageWidth)
                    .foregroundColor(color)
            }
            .frame(width: baseLength, height: baseLength)

            HStack {
                Spacer()

                Text(symbol.rawValue)
                    .frame(height: 35, alignment: .top)
                    .multilineTextAlignment(.center)
                    .font(.caption)

                Spacer()
            }
        }
    }
}

プレビュー

シンボルイメージ部分

ZStack {
    // 背景            
    RoundedRectangle(cornerRadius: 16)
        .foregroundColor(backgroundColor)
    // 枠線
    RoundedRectangle(cornerRadius: 16)
        .stroke(.gray, lineWidth: 0.5)
    // シンボルイメージ       
    Image(symbol: symbol)
        .resizable()
        .scaledToFit()
        .frame(width: imageWidth)
        .foregroundColor(color)
}
.frame(width: baseLength, height: baseLength)

今回はUIScreen.main.bounds.width * 0.25したものを基準の長さにしており、imageWidthはその基準の長さの半分の値にしています。

baseLength = UIScreen.main.bounds.width * 0.25
imageWidth = baseLength * 0.5

シンボルネーム部分

HStack {
    Spacer()

    Text(symbol.rawValue)
        .frame(height: 35, alignment: .top)
        .multilineTextAlignment(.center)
        .font(.caption)

    Spacer()

シンボルネームが中心にきて、尚且つサイドにスペースが出来て欲しかったのでHStack内でTextを記述して両サイドにSpacer()を配置しています。

LazyVGridを使用して縦方向にGridを組む

LazyVGridとは

子ビューを垂直方向に拡大するグリッドに配置し、必要な場合にのみアイテムを作成するコンテナビュー

引用: LazyVGrid

このLazyとは、グリッドビューが必要になるまで生成されないことを意味しています。

CatalogView

import SwiftUI
import SFUserFriendlySymbols

struct SymbolsCatalogView: View {

    @State private var selectedColor = Color.black
    @State private var backgroundColor = Color.white

    var body: some View {

        let columns: [GridItem] = Array(repeating: .init(.flexible()),
                                        count: 3)

        ScrollView {

            LazyVGrid(columns: columns) {
                ForEach(SFSymbols.allCases, id: \.self) {

                    CatalogItem(symbol: $0,
                                color: selectedColor,
                                backgroundColor: backgroundColor)
                }
            }
            .padding(.top)
            .padding(.horizontal)
        }
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                ColorPicker("背景", selection: $backgroundColor)
            }

            ToolbarItem(placement: .bottomBar) {
                ColorPicker("カラー", selection: $selectedColor)
            }
        }
    }
}

プレビュー

グリッド部分

let columns: [GridItem] = Array(repeating: .init(.flexible()),
                                count: 3)
ScrollView {

    LazyVGrid(columns: columns) {
        ForEach(SFSymbols.allCases, id: \.self) {

            CatalogItem(symbol: $0,
                        color: selectedColor,
                        backgroundColor: backgroundColor)
        }
    }
    .padding(.top)
    .padding(.horizontal)
}

GridItem

LazyGridを使用する為には、GridItemが必要です。

let columns: [GridItem] = Array(repeating: .init(.flexible()),
                                count: 3)

repeatingには、GridItem.Sizeを設定します。今回は特に固定させないので.flexibleに対応するようにしました。

countには、今回は垂直方向のグリッドなので、水平方向のアイテムの個数になります。

LazyVGrid

ScrollView {

    LazyVGrid(columns: columns) {
        ForEach(SFSymbols.allCases, id: \.self) {

            CatalogItem(symbol: $0,
                        color: selectedColor,
                        backgroundColor: backgroundColor)
        }
    }
}

ScrollViewが必要な理由は、LazyVGridだけを記述すると、スクロールすることが出来ず、グリッド内のアイテムが画面以上ある場合は生成することが出来なくなってしまうからです。

あとは、LazyVGrid内でForEachを使って、SFSymbols.allCasesを呼んであげてCatalogItemを生成します。

ちなみにSFUserFriendlySymbolsallCasesは、使用しているOSで使用可能な全てのシンボルを返してくれます。

おまけ ToolBar

ここまででSF Symbolsをカタログ風に表現することが出来たのですが、せっかくなので色を変更出来る様にツールバーを作成してみました。

.toolbar {
    ToolbarItem(placement: .bottomBar) {
        ColorPicker("背景", selection: $backgroundColor)
    }

    ToolbarItem(placement: .bottomBar) {
        ColorPicker("カラー", selection: $selectedColor)
    }
}

背景色を設定できるColorPickerとシンボルの色を設定できるColorPickerを画面下部のバーに設置しました。

これで簡易なSF Symbolsのカタログの完成です。

おわりに

UICollectionViewだと使用するまでに色々と記述しないといけなかったですが、少ないコードで同じような表現が出来る様になった気がしました。

LazyVStackを学ぶという趣旨で最初は進めたのですが、カタログを作る行為が途中から楽しくなってしまいました、、

今回は単色しか色を設定出来ない仕様なので、階層カラーやパレットカラー、マルチカラーにも対応していきたいと思います。またカテゴリーや名前で検索できるようになると更に良さそうですね。

SF Symbolsに幸あれ

参考