シリーズ物のアプリにおいて機能はほとんど同じでパッケージ毎の違いは文言や色の違いだけの場合も多いかと思います。そのようなときのTipsを共有します。モジュールとしてクラスを切り出して分けることも可能です。
方針としては大きく以下の3つのステップを取ります。
- R.swiftの導入による文言や色の一元管理
- CocoaPodsのパッケージ化
- アプリごとに異なる部分の切り出し
R.swiftによる文言や色の一元管理
最初に共通部分を切り出す準備をします。シリーズによって文言や色が違う場合について取り上げます。
以下の3つの流れとなります。
- R.swiftの導入
- 色を管理するテキストファイルをclr拡張子へ変換ツールの導入
- ビルド時のR.swiftの実行のスクリプトを追加
R.swiftの導入
What's R.swift?
R.swiftとは文字列や色などのリソースを一元管理する他、リソース指定時のタイポを防ぐ他、予測変換や静的解析による事前のバグ防止をすることができるものです。Androidでの概念を持ち込んだものとなります。
利用方法
GitHubページでのREADMEでは以下のような例が取り上げられています。
R.swiftの使用前では以下のようなコードがあるとします。
let icon = UIImage(named: "settings-icon")
let font = UIFont(name: "San Francisco", size: 42)
let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale: NSLocale.current, "Arthur Dent")
R.swiftの使用後は以下のようになります。
let icon = R.image.settingsIcon()
let font = R.font.sanFrancisco(size: 42)
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")
リソースの指定方法
文字列について、こちらはAndroidでのstring.xmlに相当するものとなります。
プロジェクトで対象のグループを右クリックで[New File...]を選択。
Strings Fileを選択して[Next]を選択。
Save asにファイル名(ここではsample
)を入力して[Create]を選択。
sample.strings
が生成されるので、以下のように記述します。
"sampleTest1" = "hoge";
"sampleTest2" = "fuga";
例えば、"hoge"という文字列にアクセスするためには以下のように指定します。
R.string.sample.sampleTest1()
色については、こちらはAndroidでのcolor.xmlに相当するものとなります。
R.swiftでは、clr形式のバイナリファイルを利用します。
A0A0A0 primary_text
例えば、上記のようなテキストファイルからclrファイルを生成したときは以下のように指定してUIColorの値を取得します。
R.color.default.primary_text()
clrファイルの作成方法は後述する。
導入
Podfileに以下を追加する
pod 'R.swift', '~>3.2.0'
Terminalで以下のコマンドを実行します。
$ pod install
HexToClrの導入
R.swiftで色の読み込みをclrファイルから行うがこちらはバイナリファイルで扱いが難しいので、16進数で表記したRGBのテキストファイルで管理してビルド時にclrファイルに変換して管理します。
ColorToolsのHtml2Clrを使用すると上記の変換が可能である。しかし、Html2Clrでは指定した色と微妙に異なる色となることがある。これはキャリブレーションによる影響で、こちらの記事を書かれた方が公開しているHexToClrというツールを使用すると良い。
以下、Terminalで作業をする。
$ https://github.com/noppefoxwolf/HexToClr
$ cd HexToClr
Swift Package Managerによるビルドをします。
$ swift build
すると、.build/debug/
以下にHexToClr
の実行ファイルが生成されます。
Home直下にコピーして動作確認してみる。
$ cp .build/debug/HexToClr ~
色を16進数で定義するファイルを作成する。
$ vim sample.colors
例えば、以下のように作成する。
424242 primary_text
000000 primary_dark_text
717171 primary_light_text
$ ./HexToClor sample.colors
出力としてDone
と表示され、sample.clr
が生成されれば良い。
プロジェクトに実行ファイルを移動しておきます。
$ mkdir path/to/<プロジェクト名>/<プロジェクト名>/tools
$ mv ~/HexToClr path/to/<プロジェクト名>/<プロジェクト名>/tools/
プロジェクトから[Add Files to ...]を選択し、path/to/<プロジェクト名>/<プロジェクト名>/tools/HexToClr
を選択する。
ビルド時のR.swiftの適用
プロジェクトのサイドメニューの一番上の<プロジェクト名>を選択。
Build Phases中で[+]を選択し、[New Run Script]を選択。
現れる[Run Script]を展開し、Type a script or drag a script file from your workspace to insert its path
とプレースホルダーで表示されている箇所をクリックし、以下のスクリプトを入力する。
$SRCROOT/<プロジェクト名>/tools/HexToClr $SRCROOT/<プロジェクト名>/default.colors
Run Script
の表記をダブルクリックして、Color Converter
と変換しておく。
追加で[+]を選択し、[New Run Script]を選択。
以下のスクリプトを指定する。
$SRCROOT/Pods/R.swift/rswift $SRCROOT/<プロジェクト名>
Run Script
の表記をダブルクリックして、Build R.swift
に変えておく。
文字列と色を定義するファイルを用意しておく。
Terminalから以下のコマンドを実行する。
$ touch path/to/<プロジェクト名>/<プロジェクト名>/default.colors
ビルドするとR.generated.swiftが生成される。ただし、Xcodeのサイドメニューには現れないので、このファイルを置きたい階層で右クリックをして[Add Files to "<プロジェクト名>"]を選択。
ファイル選択画面が現れるので、生成された<プロジェクト名>/<プロジェクト名>/R.generated.swift
を選択。
default.clr
も生成されるので同様にプロジェクトに追加しておく。
以上で準備が完了です。
CocoaPodsのパッケージ化
シリーズアプリでアプリごとに異なる文字列や色、モジュール等を管理するためにCocoaPodsのパッケージ化をする。
文字列や色以外にもモジュールとして別途クラスを定義したファイルをライブラリに含めることも可能です。
CocoaPodsの自作ライブラリの作成
Xcodeで[File]から[New]->[Project]を選択。
Cocoa Touch Frameworkを選択した状態で[Next]を選択。
Product Name: PackageLibSample1として[Next]->[Create]を選択。
注意点として、異なるアプリでも共通にPackageLib.podspec
というようにPackageLib
を作成し、アプリごとに異なるプロジェクト名(ここではPackageLibSample1
)は、GitHubでのプロジェクト作成時の名前指定またはレポジトリのURLで使用します。
$ pod init
$ pod spec create PackageLib
PackageLib.podspec
が生成されるのでこちらを編集します。
vim PackageLib.podspec
#
# Be sure to run `pod spec lint PackageLib.podspec' to ensure this is a
# valid spec and to remove all comments including this before submitting the spec.
#
# To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
# To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
#
Pod::Spec.new do |s|
# ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# These will help people to find your library, and whilst it
# can feel like a chore to fill in it's definitely to your advantage. The
# summary should be tweet-length, and the description more in depth.
#
s.name = "PackageLib"
s.version = "0.0.1"
s.summary = "A short description of PackageLib."
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
DESC
s.homepage = "https://github.com/<GitHubユーザ名>/PackageLibSample1.git"
# s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
# ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Licensing your code is important. See http://choosealicense.com for more info.
# CocoaPods will detect a license file if there is a named LICENSE*
# Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
#
# s.license = "MIT (example)"
# s.license = { :type => "MIT", :file => "FILE_LICENSE" }
# ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Specify the authors of the library, with email addresses. Email addresses
# of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
# accepts just a name if you'd rather not provide an email address.
#
# Specify a social_media_url where others can refer to, for example a twitter
# profile URL.
#
s.author = { "xxxxx" => "xxxxxxxxxx@gmail.com" }
# Or just: s.author = "xxxxx"
# s.authors = { "xxxxx" => "xxxxxxxxxx@gmail.com" }
# s.social_media_url = "http://twitter.com/xxxxx"
# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# If this Pod runs only on iOS or OS X, then specify the platform and
# the deployment target. You can optionally include the target after the platform.
#
# s.platform = :ios
s.platform = :ios, "8.0"
# When using multiple platforms
# s.ios.deployment_target = "5.0"
# s.osx.deployment_target = "10.7"
# s.watchos.deployment_target = "2.0"
# s.tvos.deployment_target = "9.0"
# ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Specify the location from where the source should be retrieved.
# Supports git, hg, bzr, svn and HTTP.
#
s.source = { :git => "https://github.com/<GitHubユーザ名>/PackageLibSample1.git", :tag => "#{s.version}" }
# ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# CocoaPods is smart about how it includes source code. For source files
# giving a folder will include any swift, h, m, mm, c & cpp files.
# For header files it will include any header in the folder.
# Not including the public_header_files will make all headers public.
#
s.source_files = "Classes", "PackageLib/**/*.{h,m,swift,strings}"
s.exclude_files = "Classes/Exclude"
# s.public_header_files = "Classes/**/*.h"
# ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# A list of resources included with the Pod. These are copied into the
# target bundle with a build phase script. Anything else will be cleaned.
# You can preserve files from being cleaned, please don't preserve
# non-essential files like tests, examples and documentation.
#
# s.resource = "icon.png"
# s.resources = "Resources/*.png"
# s.preserve_paths = "FilesToSave", "MoreFilesToSave"
# ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# Link your library with frameworks, or libraries. Libraries do not include
# the lib prefix of their name.
#
# s.framework = "SomeFramework"
# s.frameworks = "SomeFramework", "AnotherFramework"
# s.library = "iconv"
# s.libraries = "iconv", "xml2"
# ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
#
# If your library depends on compiler flags you can set them in the xcconfig hash
# where they will only apply to your library. If you depend on other Podspecs
# you can include multiple dependencies to ensure it works.
# s.requires_arc = true
# s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
s.dependency "R.swift", "~> 3.2.0"
end
注意点
- s.descriptionをs.summaryより長くしないと注告。
- s.homepageとs.sourceのURLを先ほどpushしたgithubのURLに変更する。
- s.licenseは個人用に自作したものなのでコメントアウトしておく。
- s.platformは
:ios, "5.0"
となっている箇所のコメントアウトを外して、:ios, "8.0"
に修正する。 - s.source_filesは、
s.source_files = "Classes", "Classes/**/*.{h,m}"
となっているところをs.source_files = "Classes", "PackageLib/**/*.{h,m,swift,strings}"
のようにパスの変更と拡張子にswiftを追加する。 - s.dependencyに
R.swift
の依存を追加する。これをしないと本体からライブラリ呼び出し時にR.swiftを認識できない。
podspecファイルの検査をするにあたってGitHubでPackageLibSample1
というプロジェクトを作成しておきます。
$ pod lib lint PackageLib.podspec --allow-warnings
WARNはいくつか出るが、PackageLib passed validation.
と表示されればOK.
ライブラリのプロジェクトにR.swiftを追加する。Podfileにpod 'R.swift', '~>3.2.0'
を追加し、pod install
する。
ビルドの設定
ビルド時のR.swiftの適用と同様にBuild Phasesの設定を行う。
Build R.swift
でのスクリプトは--accessLevel public
を追加して、以下のようにしないとライブラリ中のリソースを認識できない。
$PODS_ROOT/R.swift/rswift --accessLevel public $SRCROOT/<プロジェクト名>
先程の本体のColor Converter
とBuild R.swift
は消去しておく。
アプリごとに異なる部分の切り出し
切り出し作業
アプリの切り替えはTargetで行う。
引き続き、CocoaPodsの自作ライブラリのプロジェクトで作業をする。
先程共通部分のアプリで作成したstringsファイルやcolorsファイルを新しく作成したCocoaPodsの自作ライブラリのプロジェクトに移動して追加しておく。
リソースファイルの追加後ビルドを行い全てgit管理下におく。clrファイルやR.generated.swiftファイルもgit管理下にするので注意。
ビルドが通ることを確認しておく。確認ができたら、自作ライブラリのレポジトリをGitHubにpushする。
$ git init .
$ git remote add origin https://github.com/<GitHubユーザ名>/PackageLibSample1.git
$ git add ./
$ git commit -m "add podspec file"
$ git push origin master
$ git tag 0.0.1
$ git push --tag
次に共通部分のプロジェクトで作業をする。
Podfile
に以下を追加する。
pod 'PackageLib', :git => 'https://github.com/xxxxx/PackageLibSample1.git'
以下のコマンドでPackageLib
をインストールする。
ただし、ライブラリのレポジトリはプライベートレポジトリでも良いが取り組みには、[Settings]の[Collatorators]からメンバーで追加されている必要があるので注意。
$ pod install
ここで、import PackageLib
のようにPackageLibSample1
のモジュールを読み込んで、PackageLib.R.string.sample.sampleTest1()
のように入力すると予測変換で候補が出るなど読み込まれることを確認しておきます。
Podfile中のTarget毎の定義
アプリごとのTargetの作成を行う。元のプロジェクト名をsample_app
とします。
Targets中のアプリ本体(sample_app
)を右クリックして[duplicate]を選択。
名前を変更して、sample_app2
とします。
Podfileの設定
次にPodfile中におけるTarget毎に読み込むパッケージの定義を行います。
共通に読み込むライブラリは関数を定義しておき、そこに定義します。
Target毎のパッケージは先程定義した関数の宣言に加えて独自に読み込むパッケージを宣言します。
def shared_module
pod 'xxxxxxxxxxxxxxxxx', '~>xxx.xxx.xxx'
pod 'xxxxxxxxxxxxxxxxx', '~>xxx.xxx.xxx'
pod 'xxxxxxxxxxxxxxxxx', '~>xxx.xxx.xxx'
end
target "sample_app" do
shared_module
pod 'PackageSampleLib1',:git => 'https://github.com/xxxxx/PackageSampleLib1.git', :tag => '0.0.1'
end
target "sample_app2" do
shared_module
#pod 'PackageLibSample2',:git => 'https://github.com/xxxxx/PackageLibSample2.git', :tag => '0.0.1'
end
Targets毎の定数の設定
Targets毎に定数の設定をします。
[Build Settings]のSwift Compiler - Custom Flags
のOther Swift Flags
のDebug
とRelease
に例えば、PackageSampleLib1
というTargetであれば、-D PackageSampleLib1
のように追加します。
ビルド時に読み込むファイルの切り替えの設定
AppTargets.swiftのようなファイルを作成し、以下のように定義します。
#if PackageSampleLib1
import PackageSampleLib1
let COLOR = PackageSampleLib1.R.color.default.self
let STRING = PackageSampleLib1.R.string.self
let SUtil = PackageSampleLib1.R.string.utility.self
#else
import PackageSampleLib2
let COLOR = PackageSampleLib2.R.color.default.self
let STRING = PackageSampleLib2.R.string.self
let SUtil = PackageSampleLib12.R.string.utility.self
#endif
ビルド
Target毎のビルドはXcode画面上部のStopボタンとDestinationの指定するところの間にTargetを選択できるのでそちらを選択した状態でビルドします。
参考
CocoaPodsによる、外部ライブラリの利用と作成
CocoaPods Private Specリポジトリの作成と利用方法
【iOS Apple Watch,watchOSプログラミング入門 #009】CocoaPodsで複数のターゲットで共通するライブラリがある場合のPodfileの定義方法
Swift Package Managerの使い方
R.swiftで色を管理する