🍳

Xcode Libraryに自作のSwiftUIのViewとModifierを追加して生産性を上げる

2020/10/03に公開

はじめに

Xcode12からXcode LibraryにカスタムViewとカスタムModifierを追加できるようになりました👏
以下のようにプレビューと組み合わせることで便利に使えます。

Xcode Previewsとかよくわからないよ😭って人は、この記事をご参照ください🙇‍♂️
Xcode Previews入門 〜コードを書かないで画面を作る〜

実際にサンプルアプリでどのようにLibraryに追加していくか見ていきましょう!

LibraryContentProvider

Xcode LibraryにViewやModifierを追加させるためには、LibraryContentProviderに準拠したstructを用意してあげる必要があります。

protocol LibraryContentProvider

 LibraryContentProvider

このプロトコルにはViewとModifierで使う2つの要件があります。

struct LibraryViewContent: LibraryContentProvider {
    var views: [LibraryItem] {
        LibraryItem(MyView())
    }
}
struct LibraryModifierContent: LibraryContentProvider {
    func modifiers(base: MyView) -> [LibraryItem] {
        LibraryItem(base.myModifier(value: MyValue()))
    }
}

注目すべきは、双方がLibraryItemの配列を返すことです。
では、LibraryItemの作り方を見ていきましょう!

LibraryItemの作成方法

LibraryItemは以下のようなイニシャライザーを持ちます。

init<SnippetExpressionType>(
  _ snippet: @autoclosure () -> SnippetExpressionType,
  visible: Bool = true, 
  title: String? = nil,
  category: LibraryItem.Category = .other,
  matchingSignature: String? = nil
)

結構引数ありますよね、しかしどれもデフォルト引数を保つため最小の実装は以下のようにできます。

LibraryItem(
  CostumView()
)

実際に使ってみる

以下のようなセルを用意しました☕️

これは、コーヒーの種類にごとの画像と説明があるセルです。
このCoffeeRowViewは今後他の画面でも使いたいので、簡単に使えるように、Xcode Libraryに追加していきたいと思います。

まず、CoffeeRowContentというLibraryContentProviderに準拠したstructを作ります。

struct CoffeeRowContent: LibraryContentProvider {
}

次に、今回はModifierではなくViewを追加するのでviewsを追加します💪
viewsLibraryItemの配列です。

LibraryItemに渡すViewは今回CoffeeRowView を渡します。

struct CoffeeRowContent: LibraryContentProvider {
    var views: [LibraryItem] {
        LibraryItem(
            CoffeeRowView(coffeeType: .american)
        )
    }
}

これだけの実装でCoffeeRowViewがLibraryに追加されます。

以下の場面で使ってみます。
コーヒーの種類の一覧を表示したいListです。

スクリーンショット 2020-09-29 0.58.25.png

Xcode Library を起動して、 CoffeeRowViewを探してみましょう。
Xcode LibraryViewModifierを追加するにはCanvasを起動しておく必要があるのでご注意ください。

スクリーンショット 2020-09-29 1.01.04.png

実際にコードにドラッグ&ドロップしてみます。

スクリーンショット 2020-09-29 1.01.25.png

即時反映されます🎉

スクリーンショット 2020-09-29 1.05.34.png

CoffeeTypeを渡してあげることで全種類のコーヒーを表示させることができました。
(コーヒータイプの実装: https://github.com/tsuzukihashi/Coffee/blob/master/Coffee/CoffeeType.swift)

スクリーンショット 2020-09-29 1.06.46.png

同一のViewを引数ごとにLibraryに追加する

ここでCoffeeRowViewの全貌を公開します。

struct CoffeeRowView: View {
    let coffeeType: CoffeeType
    let size: CGFloat = 88
    @State var showFavorite: Bool = false

    var body: some View {
        HStack {
            coffeeType.image
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: size, height: size)
                .background(Color.gray.opacity(0.3))
                .clipShape(RoundedRectangle(cornerRadius: 16))
            VStack(alignment: .leading) {
                Text(coffeeType.title)
                    .font(.headline)
                Text(coffeeType.description)
                    .font(.subheadline)

                if showFavorite {
                    FavoriteView(count: coffeeType.rating)
                }
            }

        }
    }
}

ご覧の通りCoffeeRowViewshowFavoriteというBool値がありこれを切り替えることでFavoriteViewを表示・非表示を切り替えることができるようになっています。
ですので、Xcode LibraryにもFavoriteViewを表示するLibraryItemを先ほどと同様に追加したいと思います。

struct CoffeeRowContent: LibraryContentProvider {
    @LibraryContentBuilder
    var views: [LibraryItem] {
        LibraryItem(
            CoffeeRowView(coffeeType: .american)
        )
        LibraryItem(
            CoffeeRowView(coffeeType: .american, showFavorite: true)
        )
    }
}

LibraryContentBuilderはiOS14から使える機能で、本来ならviewsLibraryItemの配列として返す必要がありますが、配列として返さずとも初期化することができる便利attributesです。

以上のようにLibraryItemを追加し、Libraryを確認しますと一つのライブラリしか見えていません。

これはLibrary上では引数だけが異なるViewを区別することができていないからです。
ためにしにタイトルを追加してみましょう。

        LibraryItem(
            CoffeeRowView(coffeeType: .american),
            title: "Coffee Row View"
        )
        LibraryItem(
            CoffeeRowView(coffeeType: .american, showFavorite: true),
            title: "Coffee Row View with Favorite"
        )

このようにしてあげると、、、

無事二つのライブラリが追加されることが確認できました!

ModifierのLibrary対応

次にModifierをLibraryに追加していきます。
まず追加するModifierはCoffeeRowViewの画像のアスペクト比率を保ちつつ特定のサイズにするメソッドチェーンをModifierにしましょう!

まずImageを拡張してresizedToFillというメソッドにまとめます。

extension Image {
    func resizedToFill(width: CGFloat, height: CGFloat) -> some View {
        self
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: width, height: height)
    }
}

そうしましたら、先ほど作成したCoffeeRowContentmodifiersメソッドを実装していきます。
まず、modifiersの定義を見てみましょう。

func modifiers(base: Self.ModifierBase) -> [LibraryItem]

 modifiers(base:)

modifierはViewに対して働きかけるものですので、元となるViewが必要です。
そこで元となるViewを指定することができるのが引数のbaseの箇所です。

デフォルトですとAnyViewとなっておりますので、今回はImageに対してのmodifierなので以下のように定義しましょう。

    @LibraryContentBuilder
    func modifiers(base: Image) -> [LibraryItem] {
        LibraryItem(
            base.resizedToFill(width: 100, height: 100),
            title: "resizedToFill effect"
        )
    }

viewsのときと同様にtitleを設定しました。
早速使ってみましょう。


Libraryを起動して、viewsの隣のmodifiersを選択してresizedと検索すると無事表示されることが確認できました!


この3行を削除して、先ほど追加したmodifierを選択してエンターもしくはドラッグ&ドロップで適応させることができます。

            coffeeType.image
//                .resizable()
//                .aspectRatio(contentMode: .fill)
//                .frame(width: size, height: size)
                .resizedToFill(width: size, height: size)
                .background(Color.gray.opacity(0.3))
                .clipShape(RoundedRectangle(cornerRadius: 16))

View Libraryのグループ

LibraryItemの引数であるcategoryはView Libraryのグループを指定することができます。
デフォルトは.otherです。

LibraryItemcategoryにそれぞれの値をセットしてみた結果が以下のとおりです。
effectはViewに設定してもグルーピングできていないようでした。

Modifierにセットしてみた結果は以下の通りです。
effectを含めた全てのカテゴリが適応されていることが確認できました。

活用方法

Xcode LibraryはSPMのLibraryContetnProviderも範囲内であり、パッケージが分けられているLibraryItemも参照して使うことができます。

自分が開発しているSwiftUIでNeumorphismというデザインを簡単に表示させることができるOSSがあります。
こちらにLibraryItemを追加しました。
https://github.com/tsuzukihashi/NeumorphismUI/blob/develop/Sources/NeumorphismContent.swift

これをSPMで追加し、追加したプロジェクトでXcode Libraryを開くとNeumorphismUIのLibraryItemが表示されていることを確認することができます。

Canvasにドラッグ&ドロップするだけで簡単に自作のAPIを利用することができました。

LibraryItemを使いこなして爆速でUIを構築して行きたいですね!

参考

Discussion