【part 6】AppApp のリファクタリング過程紹介【R.swift導入編】

どうも。Reoです。3日目です。今日はいっぱい寝てしまった。

AppApp のリファクタリング記事も今回で6記事目になりました。

いよいよ荒らしみたいになってきたなって思っています。Twitterも呟いてないからほぼ同じサムネイルが並んでいるし。

まぁ気にせずどんどんやっていきます。

 

環境

  • Xcode11.3.1
  • Swift 5.1.3
  • iOS13.3
  1.  AppApp のリファクタリングを始めます!【SwiftLint 導入編】
  2. 【part 2】AppApp のリファクタリング過程紹介【SwiftLint導入後のError解消編】
  3. 【part 3】AppApp のリファクタリング過程紹介【SwiftLint導入後のWarning解消編】
  4. 【part 4】AppApp のリファクタリング過程紹介【コーディング規約編】
  5. 【part 5】AppApp のリファクタリング過程紹介【ディレクトリ構成編】

 

リポジトリ→ uruly/AppApp

 

今回の目標

R.swift を導入します。

多分それだけで結構長くなっちゃうので画像管理の話とかは次回に回すと思います。

 

R.swift とは

R.swift は、リソースを使いやすくしてくれるライブラリです。

Get strong typed, autocompleted resources like images, fonts and segues in Swift projects

https://github.com/mac-cain13/R.swift

R.swiftを使うと、以下のような書き方ができるようになります。(README.mdから引用)

// 通常
let icon = UIImage(named: "settings-icon")
let font = UIFont(name: "San Francisco", size: 42)
let color = UIColor(named: "indicator highlight")
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 color = R.color.indicatorHighlight()
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")

タイポも減りますし、自動補完もしてくれるので便利なのです!(Xcodeが仕事放棄しなければ)

 

R.swift vs SwiftGen

今回のシリーズでは気になったことは調べて書いておくようにしたいと思っているので。

R.swift と同様に、リソースに型付けを行なってくれるSwiftGenというものがあります。リソース管理を楽にしたい場合には、どちらかを選んで入れることになると思います。

 

SwiftGenを使うメリットは、Qiitaより引用させていただくと、以下のようです。

メリット
* 開発の活発頻度が高い
* 定期的なアップデートが望めそう
* ライブラリのインポートが必要ない
* 生成するリソース種別を取捨選択できる
* R.Swiftのように再ビルドが走らない

「SwiftGenを導入して無駄なビルド負担を低減する – Qiita 」2. R.Swiftと何が違うか

 

恐らく大規模で R.swift ちょっとしんどいと思い始めたら、SwiftGenを使うのを考えると良いんじゃないかな?と思います。

私は R.swift すら最近知った勢なので、SwiftGenは使ったことがないんですけどね…。

今のところは、R.swift サイコーと思っているので、R.swiftの方を入れていこうと思います。

 

Mint で R.swift 導入

初回でSwiftLintを導入するときにMintを利用したので、今回もMintを使っていきます。

mac-cain13/R.swift の Installation の Mint 部分を見て、順にやっていきましょう。

 

Mintでインストール

Mintfile に以下のように記述します。

vi Mintfile

/* Mintfile内 */
realm/SwiftLint@0.39.1
// 以下を追記
mac-cain13/R.swift@v5.1.0

追記したら


mint bootstrap

で入れます。

mint list で確認。わーい。


んで。

Run Script を書く

Xcode でプロジェクトを開き、TARGETS > 対象のターゲットを選択 > Build Phases で New Run Script Phases を選択します。

スクリプトを書きます。

if mint list | grep -q 'R.swift'; then
  mint run R.swift rswift generate "$SRCROOT/AppApp/Scripts/R.generated.swift"
else
  echo "error: R.swift not installed; run 'mint bootstrap' to install"
  return -1
fi

Input Files に以下を追記します。


$TEMP_DIR/rswift-lastrun

Output Files に以下を追記します。


$SRCROOT/AppApp/Scripts/R.generated.swift

R.generated.swift っていうのが Output Files で指定した場所に生成されます。せっかくディレクトリ整理したので、Scripts以下に配置されてほしいということで、上記のように指定しました。スクリプト部分の generate のところにも忘れずに指定します。(忘れてたからエラー出ちゃった)

以下のようになりました。

 

R.generated.swift をプロジェクトに配置

ここまでできたらビルドを行います。

ビルドを行なったら R.generated.swift が指定した場所に生成されました。

まだプロジェクトに認識されてないので、Xcode上の同じ場所にドラッグして追加します。

 

追加するときは、Copy items if needed のチェックを外してください。

 

Swift Package Manager で R.swift.library を入れる

今の状態だと、R.generated.swift の import Rswift をしているところでエラーが出てビルドできません。

R.generated.swift:7:8: No such module ‘Rswift’

 

なので、Swift Package Manager で R.swift.library をいれます。今更だけど、このやり方だとXcode11以上じゃないとできないですね。

 

https://github.com/mac-cain13/R.swift.Library を探します。

みつけたので 次へ。

いえーい Finish!

ぽちぽちするだけで簡単に入りました。

 

くっそービルドできないorz

 

エラー解消

Cycle inside AppApp; building could produce unreliable results. This usually can be resolved by moving the target’s Headers build phase before Compile Sources.
Cycle details:
→ Target ‘AppApp’ has compile command with input ‘/Users/XXX/XXX/AppApp/AppApp/Scripts/Components/UICollectionViewCell/App/AppCollectionViewCell.xib’
○ Target ‘AppApp’ has compile command for Swift source files
○ That command depends on command in Target ‘AppApp’: script phase “R.swift”
○ Target ‘AppApp’ has copy command from ‘/Users/XXX/Library/Developer/Xcode/DerivedData/AppApp-gtqynoqiknesfqbdlqwtrvkdilud/Build/Products/Release-iphoneos/ShareFromAppStoreExtension.appex’ to ‘/Users/XXX/Library/Developer/Xcode/DerivedData/AppApp-gtqynoqiknesfqbdlqwtrvkdilud/Build/Products/Release-iphoneos/AppApp.app/PlugIns/ShareFromAppStoreExtension.appex’
○ Target ‘AppApp’ has compile command with input ‘/Users/XXX/XXX/AppApp/AppApp/Scripts/Components/UICollectionViewCell/App/AppCollectionViewCell.xib’

はい。

はいはいはい。

くそーできれば変えたくないんだけど、ちょっと Legacy Build System にして様子見。File > Project Settings (Workspace Settings) で変更。

これでいけるんだけど、こうしたくはないなぁ。

 

お。これこれこれこれこれ。New Build Systemに戻します。戻しました。

 

んで、R.swift の Run Script Phase を追加したところの順番をドラッグして変更します。

Compile Sources より前に配置しましょう。

再度ビルドして、いけました。ここまでしてダメなら⌘+Shift+K でクリーンするか、DerivedDataの削除でいけるはず。

rm -rf ~/Library/Developer/Xcode/DerivedData
rm -rf ~/Library/Caches/com.apple.dt.Xcode

SwiftPM 使ってるときは、この後Xcode再起動すると良いです。

3回くらいハマっている人なんだけど、毎度同じことやってますね。記事書いてるときも出てきてくれてむしろよかった。

今回は解決に5分もかかってないのでハマってはないぞ…

 

.swiftlint.yml を編集する

さて。ビルドできてないです。

.swiftlint.yml で R.generated.swift を無視するようにしましょう。

excluded:
  - AppApp/Scripts/R.generated.swift
  - Carthage

保存して再度ビルドします。できたできた。

 

.gitignore に追加する

.gitignore に R.generated.swift を追記します。

追記する前にコミットプッシュしちゃったよ…。

あとからまとめて.gitignoreする方法 – Qiita

はい…。

git rm --cached `git ls-files --full-name -i --exclude-standard
git commit

よかろう…。

 

実際に使ってみよう。

なんやかんやで既に5000字を突破してしまってるんですが、あれだよね、エラー文の引用とかしてるからだよね?htmlタグとか含んでるんだよね?

画像や色の話は次回します。

今回はそれ以外の気に入っている部分の紹介をします。

 

UITableViewCell, UICollectionViewCell

個人的にR.swiftの一番好きなところです。
UICollectionViewも同様の手順ですが、今回はUITableViewで。

まず、cellにidentifierをつけてあげます。

tableViewにcellを登録する部分を変更します。

// 修正前
tableView.register(UINib(nibName: "InfoTableViewCell", bundle: nil), forCellReuseIdentifier: "common")

// 修正後
tableView.register(R.nib.infoTableViewCell)

ReuseIdentifier の元々の付け方よ…。

tableView(_:cellForRowAt:)は以下のように変更しました。

// 修正前
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: InfoTableViewCell = tableView.dequeueReusableCell(withIdentifier: "common", for: indexPath) as! InfoTableViewCell
    return cell
}

// 修正後
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.infoTableViewCell, for: indexPath)!
    return cell
}

これがあるからR.swiftを好きになったと言っても過言ではない…。

 

ローカライズ

こちらはOKAIMOたんから。

Localizable.strings にローカライズの内容を書きます。

// TEMPLATE: "<key>" = "<localized value>";

// Label Setting
"Cancel" = "キャンセル";
"Create Label" = "ラベルを作成";

// Keyboard
"Done" = "完了";

んで、使いたいところでキーを指定して使います。

let cancelButton = UIButton()
cancelButton.setTitle(R.string.localizable.cancel(), for: self)

enならCancel, jaならキャンセルと表示できます。これだけでローカライズができるわけではないですが、大分楽になるはず。

ローカライズした話もそのうちまとめて書きたいですね。

 

次回

次回は画像と色の管理について書いていきます。

今回導入したR.swiftを用います。

お持ち帰り作業として、次回までにUITableViewCellやUICollectionViewCell部分をR.swiftを使うように変更しておこうと思います。

 

おわりに

今までで一番長くなってしまったかもしれないです。R.swift導入しただけなのに。

普段は作っているアプリは出来上がるまで見せたくない派なので、記事を書いても隠すところが多いんですが、その点 AppApp は記事書くのがめっちゃ楽ですね。いやほんとに無限にかけちゃう。実作業が全然進んでいかないけれど…。

R.swift は導入が簡単っていうけれど、個人的にはわりと難しいなって思います。何回もやってようやく学んだけれど、最初は本当にハマってました。

 

ブログもちょっと手入れしないと読みづらさを感じてきています。とりあえずprism.jsが1行でなぜか作動しないのをどうにかしないと…。1行でいいところに空行が入っているのはわざとなんでする…。

 

今日はもう一本書くのは時間的に無理なので、明日書けるように夜に少し作業しようと思います。

それではでは。

 

AppApp ,

Comments...

コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS
more