[SwiftUI] 作ったビューを SwiftPackage にしてみる

SwiftPackageManagerEyeCatch

サイコロビューを SwiftPackage で利用しやすくする方法を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.2
  • Xcode 12.4
  • iOS 14.4

作成したビューの Swift Package 化

作成したサイコロビュー(DiceView) を Swift Package 化して、再利用しやすくしてみます。

Swift Package の作成

Xcode の "File" メニューから "New" - "Swift Package..." を選択します。
ここでは、"SDSDiceView” という名称にしました。

ポイントとしては、同じダイアログ下部にある "Add to" には、現在のプロジェクトを ”Group”には、SwiftPackage を登録したいグループを選択する必要があります。

この設定をすることで、Swift Package を clone してきた フォルダを プロジェクト内部に D&D で登録した状態となります。

View の public 化

Swift Package 化するとデフォルトのアクセス制限では、モジュール内部に遮蔽されてしまいます。
必要な要素を Public にすることが必要となります。

具体的には、以下の要素を public 指定する必要があります。

  • SDSDiceView struct そのもの
  • SDSDiceView#init
  • SDSDiceView#body

# SwiftPackage 化の際に、View 名を DiceView から SDSDiceView に変更しています。

public 化された SDSDiceView

//
//  SDSDiceView.swift
//
//  Created by : Tomoaki Yagishita on 2021/02/05
//  © 2021  SmallDeskSoftware
//
import SwiftUI
import Combine

public struct SDSDiceView: View {
    @Binding var dice:Int
    let publisher:AnyPublisher
    @State private var animate = false
    // (1) public 化
    public init(_ dice:Binding, _ requester:AnyPublisher) {
        self._dice = dice
        self.publisher = requester
    }
    
    // (2) public 化
    public var body: some View {
        VStack {
            // (3) リソースは、Bundle.module 経由でアクセス
            Image("\(dice)", bundle: .module)
                .resizable()
                .scaledToFit()
                .rotation3DEffect(
                    Angle.degrees(animate ? 360 * 20 : 0),
                    axis: (x:1, y:1, z:1))
        }
        .onReceive(publisher) { () in
            self.roll()
        }
    }
    public func roll() {
        withAnimation(Animation.default.repeatForever()) {
            animate.toggle()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            withAnimation {
                self.dice = Int.random(in: 1...6)
                animate.toggle()
            }
        }
        return
    }
}

struct SDSDiceView_Previews: PreviewProvider {
    static var previews: some View {
        SDSDiceView(.constant(3), PassthroughSubject().eraseToAnyPublisher())
    }
}

リソースアクセスの変更

SwiftPackage 内のリソースには、Bundle.module 経由でアクセスすることになります。
# Bundle.module は、Package 内部から参照できる static 定義になっています。

上記の Public 化と合わせて修正します。

なお、この変更を行わないと、リソースを main の Bundle に探しに行き、アプリ実行時にリソースが見つけられずエラーとなります。

注意
Package.swift で Target に resources 設定を行わないと、Bundle.module は定義されません

リモートリポジトリの作成

SourceControl ナビゲータに移動して、右クリックから New "SDSDiceView" Remote... を選択して、リポジトリを GitHub に作成します。

MEMO
SwiftPackage としては、GitHub 以外でも問題ありません。参照できるリポジトリであれば OK です。

SwiftPackage として参照する

使う側のビューでは、import 宣言が必要となります。

モジュール名を SDSDiceView にしましたので、"import SDSDiceView" が必要となります。

SDSDiceView と使う側のビュー

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/02/03
//  © 2021  SmallDeskSoftware
//
//

import SwiftUI
import Combine
import SDSDiceView   // <- 追加

struct ContentView: View {
    @State private var dice1:Int = Int.random(in: 1...6)
    @State private var dice2:Int = Int.random(in: 1...6)
    @State private var dice3:Int = Int.random(in: 1...6)
    var requester = PassthroughSubject()
    var body: some View {
        VStack {
            HStack {
                SDSDiceView($dice1, requester.eraseToAnyPublisher())
                SDSDiceView($dice2, requester.eraseToAnyPublisher())
                SDSDiceView($dice3, requester.eraseToAnyPublisher())
            }
            Text("\(dice1) - \(dice2) - \(dice3)")
            Button(action: {
                self.requester.send()
            }, label: {
                Text("Roll")
            })
        }
        .padding()
    }
}

ここで作成した SDSDiceView は、こちら にあります。

まとめ

これで、サイコロを振るようなアプリでは簡単に DiceView が使えるようになりました。

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

# SwiftUI2.0 が登場したことで少し古くなってしまいましたが、いまでも 定番本です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です