LoginSignup
11
5

More than 1 year has passed since last update.

Swift Package内のリソースにアクセスする便利な方法

Last updated at Posted at 2022-01-20

はじめに

本記事は Swift/Kotlin愛好会 Advent Calendar 2021 の14日目の記事です。
空いていたので埋めました。

Swift Package内のリソースにアクセスするには bundle:Bundle.main でなく Bundle.module を指定する必要があります。
毎回指定するのが手間なので、私は便利メソッドを実装し、さらに リソースへのアクセスを1つのSwiftファイルにまとめています
その方法を紹介します。

環境

  • OS:macOS Big Sur 11.6
  • Swift:5.5.2
  • Xcode:13.2.1 (13C100)

リソースの格納

Swift Packageにおいて、リソースのほとんどは Sources/{target name}/Resources/ フォルダに格納すれば自動的にリソースと認識します。

リソースにアクセスする便利な方法

私は R.swift というファイルを作成し、Androidに合わせて R.静的にリソースへアクセスできる ようにしています。

アクセス修飾子は基本的に internal (何も付けない)にしておき、 target外に公開するリソースのみpublic にする のがいいと思います。

NSLocalizedString

Localizable.strings からローカライズ文字列を取得します。

createNSLocalizedString(_:) メソッドを用意して、簡単にローカライズ文字列を生成できるようにしています。
NSLocalizedString の実態は String なのに注意です。

ローカライズ文字列は static let で静的に保持しています。
変数名はできる限りリソース名に合わせるとわかりやすい です。

R.swift
enum R {
    enum LocalizedString {

        // MARK: Internal Stored Type Properties

        // !!!: ここにローカライズ文字列へのアクセスを追加する
        static let contactUs = nsLocalizedString("Contact us")
        static let version = nsLocalizedString("Version")

        // MARK: Other Private Type Methods

        private static func nsLocalizedString(_ key: String) -> String {
            NSLocalizedString(key, bundle: .module, comment: key)
        }
    }
}

呼び出し方は以下の通りです。

label.text = R.LocalizedString.contactUs

UIColor

*.xcassets から UIColor を取得します。

NSLocalizedString とほぼ同様なので、詳細は省略します。

R.swift
enum R {
    enum Color {

        // MARK: Internal Stored Type Properties

        // !!!: ここに色へのアクセスを追加する
        static let labelBackground = uiColor(named: "LabelBackground")

        // MARK: Other Private Type Methods

        private static func uiColor(named name: String) -> UIColor {
            guard let color = UIColor(named: name, in: .module, compatibleWith: nil) else {
                fatalError("Fail to load '\(name)' color.")
            }
            return color
        }
    }
}

呼び出し方は以下の通りです。

label.backgroundColor = R.Color.labelBackground

UIImage

*.xcassets から UIImage を取得します。

UIColor とほぼ同様なので、詳細は省略します。

R.swift
enum R {
    enum Image {

        // MARK: Internal Stored Type Properties

        // !!!: ここに画像へのアクセスを追加する
        static let uhooiIcon = uiImage(named: "Uhooi")

        // MARK: Other Private Type Methods

        private static func uiImage(named name: String) -> UIImage {
            guard let image = UIImage(named: name, in: .module, with: nil) else {
                fatalError("Fail to load '\(name)' image.")
            }
            return image
        }
    }
}

呼び出し方は以下の通りです。

imageView.image = R.Image.uhooiIcon

UIStoryboard

*.storyboard から対象の UIViewController を取得します。

私は UIStoryboard を直接使うことがないので、 instantiateInitialViewController(_:) メソッドを用意して対象の UIViewController を取得するようにしています。

R.swift
enum R {
    enum Storyboard {

        // MARK: Enums

        // !!!: ここにストーリーボードへのアクセスを追加する
        enum MonsterList {
            static func instantiateInitialViewController() -> MonsterListViewController {
                Storyboard.instantiateInitialViewController(named: "MonsterList")
            }
        }
        enum MonsterDetail {
            static func instantiateInitialViewController() -> MonsterDetailViewController {
                Storyboard.instantiateInitialViewController(named: "MonsterDetail")
            }
        }

        // MARK: Other Private Type Methods

        private static func instantiateInitialViewController<T: UIViewController>(named name: String) -> T {
            guard let vc = uiStoryboard(name: name).instantiateInitialViewController() as? T else {
                fatalError("Fail to load \(T.self) from '\(name)' Storyboard.")
            }
            return vc
        }

        private static func uiStoryboard(name: String) -> UIStoryboard {
            UIStoryboard(name: name, bundle: .module)
        }
    }
}

呼び出し方は以下の通りです。

let vc = R.Storyboard.MonsterList.instantiateInitialViewController()

UINib

*.xib から UINib を取得します。

他とほぼ同様なので、詳細は省略します。

R.swift
enum R {
    enum Nib {

        // MARK: Internal Stored Type Properties

        // !!!: ここにジブへのアクセスを追加する
        static let monsterCollectionViewCell = uiNib(name: "MonsterCollectionViewCell")

        // MARK: Other Private Type Methods

        private static func uiNib(name: String) -> UINib {
            UINib(nibName: name, bundle: .module)
        }
    }
}

呼び出し方は以下の通りです。

let monsterCellRegistration = UICollectionView.CellRegistration<MonsterCollectionViewCell, MonsterItem>(
    cellNib: R.Nib.monsterCollectionViewCell) { cell, _, monster in
    cell.setupWith(name: monster.name, iconUrl: monster.iconUrl, elevation: 1.0)
}

おわりに

これでSwift Package内のリソースへ簡単にアクセスできます :relaxed:

ただ手動で実装するのは手間なので、 ツールを使って自動で生成したい です。
R.swift ではできなさそうでした。
試したことないですが、 SwiftGen だとテンプレートを自作できるのでできそうです。

また、みなさんのSwift Package内のリソースへのアクセス方法も教えていただけると嬉しいです。

以上 Swift/Kotlin愛好会 Advent Calendar 2021 の14日目の記事でした。

参考リンク

11
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5