17
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swiftによるシリーズアプリの共通部分を切り出して一括管理する

Last updated at Posted at 2017-02-07

シリーズ物のアプリにおいて機能はほとんど同じでパッケージ毎の違いは文言や色の違いだけの場合も多いかと思います。そのようなときの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 ConverterBuild 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 FlagsOther Swift FlagsDebugReleaseに例えば、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を選択できるのでそちらを選択した状態でビルドします。

スクリーンショット 2017-02-07 14.03.22.png

参考

CocoaPodsによる、外部ライブラリの利用と作成
CocoaPods Private Specリポジトリの作成と利用方法
【iOS Apple Watch,watchOSプログラミング入門 #009】CocoaPodsで複数のターゲットで共通するライブラリがある場合のPodfileの定義方法
Swift Package Managerの使い方
R.swiftで色を管理する

17
19
0

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
17
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?