- 投稿日:2019-12-24T23:50:45+09:00
Awesome CoreNFC in iOS 【iPhone でピッと IC カードを読む】
はじめに
CoreNFC は、iPhone でピッと IC カードを読みこめる機能です。
利用できるICカードは幅広く、交通系IC、各種電子マネー、大学生協の電子マネー、運転免許証、パスポート、マイナンバーカードなどあります。
今年9月19日に利用可能になった CoreNFC はまだ日本語の学習記事があまりありません。この記事では、CoreNFC を日本の開発者にもっと活用して頂く目的で、情報をまとめさせて頂きます。将来的に初学者に良いまとめになれば幸いです。
改善コメントや編集リクエストをどんどん募集しております。どうぞよろしくお願い申し上げます?♂️
キータ
- NFCの種類(type A, B, F)
- 交通系ICの残高読み取り
- 運転免許証
- 電子マネーの残高
- SwiftUI を用いて交通系IC
- ライブラリを使わずに運転免許証、読み取りコードの解説 - 日本の CoreNFC オープンソース開発の第一人者 @treastrain 氏の記事です。type B の読み取りをマスターできます。
- NFC アプリ一覧(type B) - @simphon氏。最新の情報がバランスよくまとまっております。
- Core NFC Advent Calendar 2019
サンプル・アプリケーション
- TRETJapanNFCReader/Examples - 交通系ICと運転免許証の読み込み例がのっております。
- TransitPal - アメリカのクリッパーカードの読み込みですが、とても良いサンプルアプリケーションです。Swift UI でシンプルながら、下記画像のようなかなり綺麗な UI を学べます。
![]()
Screenshots from TransitPalオープン・ソース・ライブラリ
- TRETJapanNFCReader - 交通系IC、各種電子マネー、大学生協の電子マネー、運転免許証の読み込みを手軽に扱えます。
記事を良くするコメントや編集リクエスト
この記事は、CoreNFC について学ぶ際に、興味のある各記事に進むのにご利用いただければと思います。皆様の、記事を良くするコメントや編集リクエストをお待ちしております。お願い申し上げます?♂️
- 投稿日:2019-12-24T23:50:27+09:00
Apple pencilとiPadでとった「手書きメモ」が自動でブログになる勉強方法が捗りすぎるので方法を公開します ( Evernote postach.io )
勉強でとったメモがそのままブログに!!
東京グルメ紀行
https://qiita.postach.io/この記事と全く同じ内容+お絵かき入りのブログ
このブログのように、evernoteでとった手書きメモやお絵かき入りのノートを直接いい感じのブログにできます。
iPadと Apple pencilがあれば、簡単な表も手書きでパッと書いて公開できるので、アルゴリズムの解説記事はevernoteで書いて公開した方が楽です。
(Qiitaはお絵かきが挿入できないのがだるい)手順(全部無料です)
- Evernoteをインストール
Evernoteはテキストと手書きメモでノートを取れるアプリです。
使い方はググってください
- postach.ioに登録する
以下のリンクにアクセスし、登録します
- postach.ioノートブック(登録したら勝手にできている)内にノートを作る。
このとき、publishedというタグを付けると公開されます。
これで準備は完了です
postach.ioというノートブック内にノートを取り、publishedタグを付けてノートを取ると、ブログが投稿されます。
ノートを更新すると自動でブログに反映されます。
超便利ですよね?自分の勉強がそのままブログになるので、モチベーション維持に欠かせないものになりました!!
FAQ
- markdown記法はつかえるの?
markdownタグを付けるとできます。以下の記事もマークダウンで書かれてます。
この記事と全く同じ内容+お絵かき入りのブログ
- アフィリエイトはできるの?
Google Adsense やAmazonのアフィリエイトは挿入可能です。
https://postach.io/blog/post/the-ultimate-guide-to-choosing-a-profitable-niche-for-your-blog
- twitterへの自動投稿はできるの?
Postachの設定→Edit addonsで設定出来ます。
- Google analytics はできるの?
上と同様の操作でUAを入力できる画面が出ます。
- 以上ですが何かあればコメント欄で聞いて下さい。
- 投稿日:2019-12-24T23:38:15+09:00
【iOS】算数で横スクロールのCollectionViewの高さを求める
スクローラブルと認識できるデザイン
まず初めに、iOSに限らずですがスクロール可能と認識できるデザインがあります。
こんな感じです。2つのセルが完全に表示されていて、残り1つは半分ほど出ているデザインです。
これはユーザーが見た時に「あ、横にスクロールできるんだな」と直感的に思わせるためですね。これをみた時にCollectionViewの高さとセルのサイズってどうするんだろうと思ってました
![]()
よく考えてみるとある程度条件を絞って算数の力を使えば導き出せることに気づいたので実装してみます(正確には中学生で習う1次方程式ですが、ほぼ算数なので許してください)。計算してみる
まずは手元で計算してみます。
条件を整理して実際に数式を出すところまでやります。条件の整理
今回は事前に計算をして計算結果を実装に当てはめる、ということが必要なのである程度の条件が必要となります。
以下が条件です。
- 各セルの縦横比が確定している
- 各セル同士のスペースが確定している
- CollectionViewのwidthが確定している
図にするとこんな感じです。
そこまで条件が多いわけではないので、大体の場合で使えるかなーという感じはしてます。
今回は計算を楽にするためにパディングは上下と一番左を同じ値にしてセル間をその2倍としています。数式を出す
条件が出たのでそれを数式に落とし込んでみましょう。
セルの比
まずは1つ目の条件
各セルの縦横比が確定している
です。
これは比をそのまま計算式にするだけです。このように定義すると
\begin{aligned} a:b = w:h => h = \frac{b}{a}w \end{aligned}$h$を$w$で表せました。
CollectionViewのwidth
次に2つ目と3つ目の条件を使うとCollectionViewの横幅を表すことができます。
先ほどの図を見るとわかりやすいですが、$W$はセルの横幅の合計とパディングの合計を足した値になるのがわかると思います。まずセルの横幅の合計は単純に
セルの横幅
×個数
なので$N \times w$ですね。次にパディングの合計です。図の場合であると$p + 2p + 2p$で$5p$ですね。
これを一般的なセルの個数で表現すると、
最初のパディング(p)
+セルの個数の小数点を切り捨てた整数
× $2p$になります。
数式にするとこのようになります。\begin{aligned} (2\lfloor N \rfloor + 1)p \end{aligned}ここでの $\lfloor N \rfloor$ はガウス記号です。これは小数点を切り下げするだけなので例えば3.5や3.8であれば3になります。
これで出揃ったので、先ほどのセルの横幅の合計を足してあげると$W$は
\begin{aligned} W = (2\lfloor N \rfloor + 1)p + Nw \end{aligned}と表せます。
CollectionViewのheight
最後にCollectionViewのセルの高さも出してあげます。
これは上下のパディングとセルの高さを足してあげればいいだけなので、\begin{aligned} H = 2p + h \end{aligned}と表せます。
これで全ての数式が揃いましたので、まとめてみます。
\begin{aligned} h & = \frac{b}{a}w \\ W & = (2\lfloor N \rfloor + 1)p + Nw \\ H & = 2p + h \\ \end{aligned}改めて確認すると現時点で判明していない値は$H$, $h$, $w$の3つです。
それでいて式が3つです。つまり3変数の1次方程式なのでそれぞれの値は確定します。細かい途中式は省きますが、まず簡単なところから求めると2番目の式から$w$が求まります。
\begin{equation} w = \frac{W - (2\lfloor N \rfloor + 1)p}{N} \end{equation}次に一番上の式と$w$を使えば$h$が求まります。
\begin{equation} h = \frac{b}{a}\frac{W - (2\lfloor N \rfloor + 1)p}{N} \end{equation}最後に3番目の式と$h$を使えば$H$が求まります。
\begin{equation} H = 2p + \frac{b}{a}\frac{W - (2\lfloor N \rfloor + 1)p}{N} \end{equation}これでCollectionViewの高さとセルのサイズが求まりました!
最後に実際に実装してみてどんな感じになるのか見てみます。実装
準備
まずは実装の前に条件を満たしておきます。
今回はそれぞれの値を以下のようにしておきます(計算が簡単になるように調整してます)。$a:b = 8:5$
$p = 16$
$W = 画面の横幅$
$N = 2.5$すると、$H$, $h$, $w$が以下のように出ます。
\begin{aligned} H & = \frac{W}{4} + 12 \\ h & = \frac{W - 5×16}{4} \\ w & = \frac{W - 5×16}{2.5} \\ \end{aligned}これで準備は終わりです。
CollectionViewのheight
準備も終わったのでいよいよ実装です。ここではストーリーボードを使って実装していきます。
まずはCollectionViewの高さから設定します。
CollectionViewをストーリーボード上に配置し、top, right, leftを親に合わせていきます。
これで$W$を画面の横幅と一致させています。親のViewのheightとCollectionViewのheightをアスペクト比で合わせます。
1つ前で親のheightを指定していることに違和感があったかもしれないですが、さっきの段階でheightをwidthに合わせる、みたいなことができないみたいようです。しょうがなく一旦親のViewのheightに合わせていました(もし知っている方がいたら教えて欲しいです
)。
そういう理由もありここでheightからwidthに変更します。最後に数式の出番です。
ここでは $H = W/4 + 12$を使います。数式の意味としては$W$を0.25倍して12を足す、です。
なので、Multipler
には0.25、Constant
には12を入れます。これでCollectionViewのheightの設定は完了です。
セルサイズの設定
続いてセルサイズの設定もやります。
色々と省略しますが、viewDidLoad()
でUICollectionViewFlowLayout
を設定してあげます。ここで使用するのは $h = (W - 5×16)/4$ と $w = (W - 5×16)/2.5$です
override func viewDidLoad() { super.viewDidLoad() let flowLayout = UICollectionViewFlowLayout() flowLayout.scrollDirection = .horizontal let padding: CGFloat = 16.0 let screenWidth = UIScreen.main.bounds.width // セルのwidthの計算式: w = (W - 5×16)/2.5 let cellHeight = (screenWidth - 5.0 * padding) / 4 // セルのheightの計算式: h = (W - 5×16)/4 let cellWidth = (screenWidth - 5.0 * padding) / 2.5 flowLayout.itemSize = CGSize(width: cellWidth, height: cellHeight) // セル間のパディング flowLayout.minimumLineSpacing = padding * 2 // 上下左右のパディング flowLayout.sectionInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) collectionView.collectionViewLayout = flowLayout }ビルドするとこんな感じになります。
ちゃんとセルが2.5個でそれぞれのパディングが取れていますね。
https://github.com/hiromu21/CalcCollectionView
GitHubにソースは載せてあるので良かったらそちらも見てください。まとめ
ちゃんと計算したかったので、ガウス記号とかを取り入れました。それっぽい数式になって良かったです笑
ちなみにもっと簡単な方法を知っている方がいたら教えてください![]()
最後に注意事項としてこのままのやり方だと問題点があります。それは画面回転した時の挙動です。
実際に実装してみて回転させると分かるのですが、セルが2行で表示されたりセルの大きさが変わったりしてしまいます。
そもそも縦固定が想定されてる実装になっているのでしょうがないのですが![]()
もし機会があれば画面回転にも対応した実装をして記事にしてみたいです!
- 投稿日:2019-12-24T23:17:59+09:00
Apple Developer Program の管理者が最低限意識すべきこと
受託開発企業の
Apple Developer Program
の管理者として一年間仕事をしてきたので、
最低限これだけは意識しておいたほうが良いと思ったことをまとめます。受託開発企業というところもあり、ある程度領域が狭まってしまっていることもあるかと思いますので、ご了承ください。
プログラムの種類
Apple Developer Program (ADP)
開発及びAppStoreでの配布で利用する
AppStore配布が出来る
Ad Hoc配布が出来る
カスタムB2B配布が出来る
TestFlight(テスト配布)が利用出来るApple Developer Enterprise Program (ADEP)
開発及びIn-Houseでの配布で利用する
Ad Hoc配布が出来る
In-House配布が出来る配布方法
AppStore 配布
- AppStoreを利用した最も一般的な配布方法
- ADPでしか使えない
- 配布前に審査に出す必要がある
Ad Hoc 配布
- App Storeを利用しない配布方法
- ADP(ADEP)に登録されたデバイス(最大100台)にのみ配布可能
Provisioning Profile
の有効期限(1年)が切れるとアプリが起動できなくなる- 配布用証明書が無効になるとアプリが起動できなくなる
- 新たにデバイスを追加する場合、下記の手順が必要
- ADPにデバイスを登録
- 追加したデバイスを
Provisioning Profile
に追加ipa
ファイルの作成カスタムB2B 配布
- VPP(Volume Purchase Program)を利用した配布方法
- ADPでしか使えない
- 配布前に審査に出す必要がある
- 配布先企業は
Apple Business Manager
に登録しなければならないApple Business Manager
のVolume Purchase Program
を利用して配布するIn-House 配布
- App Storeを利用しない配布方法
- ADEPでしか使えない
Provisioning Profile
の有効期限(1年)が切れるとアプリが起動できなくなる- 配布用証明書が無効になるとアプリが起動できなくなる
- デバイスの追加で作業が発生しない
- アプリが第三書にインストールされない様に配布方法を工夫する必要がある
管理対象
- People
- Certificates
- Identifiers
- Devices
- Profiles
- Keys
People
メンバーの管理
よく使う:
- Account Holder
- Admin
- App Manager
- Developer
あまり使わない:
- Finance
- Marketing
- Sales
- Customer Support
詳細
Apple Developer Programにおける役割 - サポート - Apple DeveloperCertificates
証明書の管理
開発用証明書
主に使うもの:
Development
iOSアプリの開発時に必要
Xcode11以降ではこちらを利用する
※ Apple Developer Webサイト ではApple Development
という記述になっている
※ Developer権限でも作れるiOS Development
iOSアプリの開発時に必要
Xcode11未満はこちらを利用する
※ Developer権限でも作れる配布用証明書
※ 最大数が決まっている(現状 3つまで)
主に使うもの:
Distribution
iOSアプリの配布時に必要
Xcode11以降ではこちらを利用する
※ Apple Developer Webサイト ではApple Distribution
という記述になっているiOS Distribution
iOSアプリの配布時に必要
Xcode11未満はこちらを利用するIdentifiers
アプリを識別するためのID
Bundle ID
の管理意識的に管理する必要はない認識。
(ですが、上限が決まっているなどあった場合、後で面倒な事になる可能性はあります。)Devices
端末の管理
僕の場合は、社内端末にはプレフィックスをつけて社外端末と区別できるようにしています。
Profiles
Provisioning Profile
の管理
Provisioning Profile
は証明書
,App ID
,UDID
を紐付けて「誰が何のアプリをどの端末に入れられるか」をまとめるファイル開発用プロビジョニングプロファイル
主に使うもの:
- iOS App Development iOSアプリの開発時に利用する
※ Developer権限でも作れる配布用プロビジョニングプロファイル
主に使うもの:
App Store
AppStoreでの配布やTestFlightでのテスト配布時に利用するAd Hoc
Ad Hocでの配布時に利用するKeys
鍵ファイルの管理
主に使うもの:
- Apple Push Notifications service (APNs)
プッシュ通知の開発時に利用する
※ 最大数が決まっている(現状 2つまで)今後使いそうなもの:
- Sign In with Apple Apple IDを使用して認証できるようにする時に利用する
あとがき
間違ってるところなどあればご指摘いただけると幸いです。
- 投稿日:2019-12-24T23:09:53+09:00
Bitriseで2FA認証後に複数アカウントのSandboxユーザーを扱う上で気をつけること
Bitriseの2FA
BitriseではAppleIDに2FA(二段階認証)がかかっていたとしてもApp Store Connect 2FA solved on Bitriseのように設定をすることで、セッションが有効な30日間は2FAをスキップすることが可能です。
ただ以下のようなSandboxユーザーを扱うケースでハマりました。
ハマったこと
以下2つのアカウントがあるとします。
2FAの設定をしていないAppleID:
apple_id_not_2fa@test.com
2FAの設定をしているAppleID:apple_id_2fa@test.com
Bitrise上で紐付いているアカウントは
apple_id_2fa@test.com
とします。以下のような、2つのアカウントのSandboxユーザーを削除するfastlane actionを実行します。
# Fastfile require 'spaceship' lane :remove_sandbox_users do _remove_sandbox_users(username: "apple_id_not_2fa@test.com", password: "password", sandbox_user: "sandbox_not_2fa@test.com") _remove_sandbox_users(username: "apple_id_2fa@test.com", password: "password", sandbox_user: "sandbox_2fa@test.com") end private lane :_remove_sandbox_users do |options| Spaceship::Tunes.login(options[:username], options[:password]) Spaceship::Tunes::SandboxTester.delete!([options[:sandbox_user]]) end上記の
remove_sandbox_users
をBitrise上で実行すると、sandbox_2fa@test.com
は削除されますが、2FAを有効にしていないアカウント側のsandbox_not_2fa@test.com
は削除されません。原因
Bitrise上で
apple_id_2fa@test.com
を紐付けた際に、おそらくBitriseの内部でFASTLANE_SESSION
の設定とcookieが作成されています。
(ref: https://discuss.bitrise.io/t/two-factor-authentication-for-apple-id-itunes-connect-testflight-deploy/1180/25)そのため2FAが設定されている
apple_id_2fa@test.com
のFASTLANE_SESSION
が使用されたため、常にapple_id_2fa@test.com
のログイン状態になり、apple_id_not_2fa@test.com
上に作成したSandboxユーザーは削除されません。
(FASTLANE_SESSION
が設定されている場合、https://github.com/fastlane/fastlane/blob/c821aa892157ad499b03dc7693877b978db9a64c/spaceship/lib/spaceship/client.rb#L431 で読み込まれます)いくつか挙動に疑問を感じたため、以下のようなケースをローカルで実験しました。
実験
以下2つのファイルを利用して、Bitrise上ではなく、ローカル上で確認をしました。
またcookieは、~/.fastlane/spaceship/**/cookie
のことを示しています。# Fastfile require 'spaceship' lane: test_login do Spaceship::Tunes.login("apple_id_2fa@test.com", "password") end# fastlane/.env FASTLANE_SESSION="fastlane_session value of apple_id_2fa@test.com"1以外は、2FAをスキップしてログインが成功していることがわかります。
表にまとめると以下のようになります。
ケース 2FA Skip FASTLANE_SESSION="", cookieがない場合 ✗ FASTLANE_SESSION=""=, 有効なcookieが存在する場合 ◯ FASTLANE_SESSION="fastlane_session value of apple_id_2fa@test.com", cookieがない場合 ◯ 改善案
今回のケースの改善案として、Bitriseの
[Workflow]
→[Env]
に移動して、対象のworkflowのEnvironment Variables
に
ENV["FASTLANE_SESSION"] = ""
を設定します。これにより、Bitrise上で
2. FASTLANE_SESSION="", 有効なcookieが存在する場合
と同じ状態になるため
apple_id_not_2fa@test.com
の場合:
apple_id_not_2fa@test.com
で通常ログイン
apple_id_2fa@test.com
の場合:
apple_id_not_2fa@test.com
でcookieを利用してログインどちらのアカウントも問題なくログインすることができ、Sandboxユーザーの作成・削除を行えます。
まとめ
複数のAppleID(2FAありなし)でSandboxユーザーを操作することはなかなかないと思いますが、もしこのようなことが起こった場合、
FASTLANE_SESSION
とcookieを確認してみることをおすすめします。関連URL
- 投稿日:2019-12-24T19:10:49+09:00
8ヶ月かけてTODOアプリを作った話
はじめに
今年の4月頃から開発していたTODOアプリが完成したのでリリースした。
今回はなぜアプリの開発に8ヶ月もかかってしまったのかを自戒も含めて公開したいと思う。どんなアプリ
リリースしたアプリはこちら
https://apps.apple.com/us/app/tickettask/id1491648458?l=ja&ls=1使用技術
開発にあたって使用した技術は以下
・Swift
・Xcode
・Firebase(Analytics集計のため)
・Realmアプリ概要
アプリの概要としては、根幹としてはよくあるTODOアプリ。
それ以外に設定した期間でのリセット機能、定期的なリマインド機能などがある。ここまで見ていただいて、ある程度のアプリ開発の経験がある人はこう思ったんじゃないでしょうか。
「ん?これくらいの用件のアプリだったら8ヶ月もかからなくないか?下手したら1ヶ月で作れるくらいの規模じゃないか?」
はい。こういったご意見も含めてなぜこんなに期間がかかってしまったのかを書いていく。
開発した経緯
まずなぜこのアプリを開発したかというところだが、当初作りはじめていたときこのアプリは公開などするということを全く考えていなかった。勉強のために様々なデザインのアプリを見ていたときに、すごく好みなUIのアプリがあったため自分でも作ってみたいと思いダラダラと作りはじめていった。
行き当たりばったりの開発
表現したいUIができてきた頃にこんな思いになっていった
「この土台のアプリを使えばストアに公開できるくらいのアプリが作れるんじゃないか?」
こうして本格的に中身を作りはじめた。
もともと開発していたアプリは実験的な感覚で作っていたので画面仕様書だったりを全く作っていなかったし、ましてや公開する気もなかったので中身の部分も仕様なんて全く考えていなかった。
とりあえずTODOアプリを作ってみようと思い、仕様を考えはじめた。そしてざっくりと作りたいアプリのイメージを固めた。犯した2つの失敗
こうして開発を進める中で自分は2つの失敗を犯してしまった。これらが開発が伸びて行ってしまった原因だと考えている。
・仕様の曖昧さ
一つ目が仕様の曖昧さ。アプリを作り出しがUI経緯なのに対して画面仕様書の一つも作っていなかった。これによってアプリ全体のスケールが想像できずに内部仕様を考えるときに足枷となっていた。
そして開発中にアイデアが浮かんだ時も作り出している構造だと実現できないような物もあったりしたのUIの構造を丸々作り変えたりしていた。これによって開発スケジュールが膨れたりしていた。・怠惰により開発の先延ばし
二つ目が怠惰による開発の先延ばし。途中本業が忙しくなる時期があったりして個人プロジェクトを1、2ヶ月放置した期間があったりした。しかしこれも一つ目の仕様の曖昧さが生み出してしまった「スケールが想像できない」ことでモチベーションが保てなかったことが原因でもあると思う。
学びのある開発を
開発を行ってきた中で確かに失敗はあったが次につながる失敗であったと思う。
次からはざっとでいいから画面一覧を作ろうと思ったし、ちゃんと仕様を決めてから開発に取り掛かろうと思った。エンジニアをはじめたての頃Qiitaである記事を見てすごく衝撃を受けた記事があった。
ジャバ・ザ・ハットリさんのこの記事。
開設後3週間で収益10万円を得た個人開発サイトでやったことの全部を公開するこの記事の中で以下のようなことが書いてある
個人開発で3大やってはいけないこと
・「マネタイズは後で考える」と言ってとりあえず作る
・「コンテンツ(ユーザー投稿数確保)は後で考える」と言ってとりあえず作る
・「ウケるかどうか出してみないと分からない」と言ってとりあえず作る
上記の3つとも推奨されている方もいる。ただ少なくとも私にとってはこの3つが最大の失敗要因だった。
映画「ソーシャル・ネットワーク」のジャスティン・ティンバーレイクのように「広告なんてクールじゃねーよ」と気取ってもいいが、そんなのが上手くいった試しが無い。
だいたいにおいて 考えることを放棄した時点で負けが決定している。じっくり考えるところが個人開発の一番おもしろいところだし「後で考える」ぐらいならその場でとことん知恵を
絞った方が楽しいだろう。当時この記事を見ながら「自分が個人プロダクトを作るときはとことん思考を張り巡らさせて作りるぞ!!」なんて考えていたがいざアプリを作りおえてみるとアプリを完成させるということだけに拘って他のところか完全にお座なりになっていた。
もちろんこの考えが間違っているというわけではないが、結果アプリのコンセプトも開発途中でふわふわになっていろんな機能を追加したり削除したりして完成が遅くなってしまった。
「コードを書く」ことはすごく楽しいし刺激的だけど、それと同じくらい「準備する期間」という物が大事なのだとわかった。最後に
今回作ったアプリが受けるか受けないかに関わらずこれからもアプリを作り続けると思います。
やはり作ってきたアプリが公開される瞬間というのはすごく刺激的だしワクワクします。失敗を繰り返しながらより素敵なアプリが自分の中から生み出されていくのを夢見て明日からも開発に勤しんで行きます。
次はAndroid界にも出したいからFlutterで作ろうかなんて考えてます。もちろん技術だけではなく自分なりに「これは受ける!」ってアプリを作っていきます。宣伝にはなっちゃいますが自分が作ったチケットタスクというアプリ、親バカですがすごく可愛いアプリなので是非インストールしてみてください。
- 投稿日:2019-12-24T11:58:31+09:00
Bitriseからアプリをチームにテスト配布するための設定
この記事はBitriseアドベントカレンダーの23日目の記事です。
どうも!ハムカツおじさんという名前でtwitterやってます(@hmktsu)?
自分でだったり弊社でだったりなどでBitriseを使ってサービスをビルドしています。ちなみに先日Bitrise User Group Meetup #3にて以下のスライドを発表させていただいたので、興味ある人はご覧になってください!
XcodeやAndroid Studioを弄らないアイコン管理はじめに
Bitriseはアプリ開発をするにあたって外せないサービスになっていると思っています。
対抗馬としてCircleCIなどの他サービスもありますが、iOSやAndroidのビルドをするためにブラウザ上でぽちぽちするだけでworkflowを組め、さらにはBetaみたいにBitriseを使ってのAdhoc配信や各種Appストアへのアップロードも行ってくれます。そんなBitriseを使っていてチーム内でアプリのAdhoc配布をしたいということは多々あります。
なので今日はチームに配布するために色々と設定するべき項目、やってもらうべき項目についてを書きたいと思います。なお諸々の細かい設定に関しては、ほぼほぼiOS向けとなっております。
配布されたい人がアカウントを作成
- アカウントを作成して管理者にアカウント名を伝える
- Account Settings -> Test devicesをiOSのSafariで開いてデバイスを登録
Bitriseのアカウントを作成しましょう。
次にBitriseのチームを管理している人に自分のアカウント名をお知らせしてください。その後iOSをBitriseに登録する必要があります。
SafariでBitriseを開き、Account Settings -> Test devicesを開いてください。
プロファイルをダウンロード -> iOSの設定 -> プロファイル -> インストール -> 登録完了となります。Safariをプライベートモードで開いているとうまくいかないので注意
配布するための設定
- Organization -> Groupの作成
- Groupにアカウントを追加
- アプリのダッシュボード -> Teamで先ほど作ったGroupをTESTERS / QAに追加
- Workflowに「iOS Auto Provision」と「Deploy to Bitrise.io - Apps, Logs, Artifacts」を追加
- ビルド
BitriseのOrganizationにおいて、Groupを作成しましょう。
初期状態でいくつかGroupが作成されていますが、配布したいアプリ用のGroupを作っておくと個人的には管理が楽だなぁと思います。
Groupを作ったら先ほど教えてもらったアカウント名を作ったGroupに追加してください。
次にアプリのダッシュボード -> Teamに先ほどのGroupをTESTERS / QAに追加しましょう。
「iOS Auto Provision」と「Deploy to Bitrise.io - Apps, Logs, Artifacts」をWorkflowに追加し、諸々を設定してください。以上の設定をしてビルドをすると、自動的にGroupに追加したユーザ宛てにダウンロードURLがメールで飛びます。
ビルドページからインストール
- Click hereというリンクをクリックしてプロファイルをダウンロード
- 設定 -> プロファイル -> インストール
- Safariが開くのでインストールボタンを押してインストール
Download anywayというボタンを押すとipaをインストールするけど、これはシミュレータ用とか他の用途なので、プロファイル経由でないといけないので注意
まとめ
このような感じでGroupを作成して追加/削除をするだけで配布されるかどうかということが決まるのでとても簡単です。
特にBetaに渡すとかそういうこともせずにBitriseだけでDEV環境の確認ができ、App StoreからTest FlightでPROD環境の確認をするという流れできます。
できる限り自分が管理するサービスを少なくするための選択肢としてBitriseはいいんじゃないかと思います。
- 投稿日:2019-12-24T10:08:31+09:00
iPhoneでの現在Apple Storeに公開されている一覧+ 今後の展望とか
長い長いカレンダーになりましたが終盤戦ということで、もう少しお付き合いいただければと思います。
今回の記事では、現在自分が確認できている(2019/12/20現在)でType-Bの読み取りを行っているアプリケーションの紹介をしたいと思います。
ぶっちゃけ、自分は仕事以外でmac関連を触っていない。そもそも触って数か月くらいしか…ということもあるので、開発を試したい。ないしは開発したくないけどとりあえず触ってみたい層向けです。触ってみたくなりましたらこのカレンダーを見ていただければばと思います。
ちなみに筆者はFelicaには詳しくないのでType-Bばかり触っていますのであしからず…
Japan NFC Reader
URL: https://apps.apple.com/jp/app/id1480265213
作成者: Ryoga Tanaka氏(treastrain)カレンダーを見たことある人はわかると思いますが、Core NFC Advent Calendar 2019の作成者様です。紹介しないと私が〇〇されてしまいます。
Felica
を主に取り扱っていて、残高や履歴などを確認することができるものとなっています。
https://japannfcreader.tret.jp/index.html 気になった方はこちらのURLへ。IC免許証 本籍リーダ
URL: https://apps.apple.com/jp/app/%E5%85%8D%E8%A8%B1%E8%A8%BC%E6%9C%AC%E7%B1%8D%E3%83%AA%E3%83%BC%E3%83%80/id1487791296
作成者: 株式会社aBiteこちらも本カレンダー参加のkewa4氏が所属している株式会社aBiteのリーダになります。
色々と機能を実装するのではなく本籍
取得に特化しており、まごったい処理時間等は少ない為、狙った情報を読み取りたい場合に便利だと思われます。ReadID
URL: https://apps.apple.com/nl/app/readid-nfc/id1463949991
作成者: InnoValor(オランダの会社)個人的な一言感想を述べると黒船です。パスポートの読み取りと(おそらく?)国際運転免許証を読み取る機能を持ちます。これ、空港とかのパスポート認証とかに使えてしまいそうなくらい…
パスポートもカードタイプと券面タイプ両方を読み取ることができる優れものです(筆者は日本発行の電子旅券のみ確認)
外国のアプリなので全て英語ですが、UIの通りに動かしたところ問題なく動作したので、パスポートを持っていらっしゃる方はぜひお試しください…!
JPKIMobile
作成者:地方公共団体情報システム機構(JBIS)
マイナンバーカードの読み取りに利用されています。マイナンバーカードは仕様を知らないのですが、以下の2点が読めるようです
- 署名用電子証明書
- 主に電子申請(WEBでの確定申告など?)に利用される署名用の証明書
- 利用者証明用電子証明書
- これは本人が利用する証明になります。
マイナンバーカードの鍵や仕様はたしか非公開だったので政府等の依頼で作成したりしたのでしょうかね…?
LibJeID
URL: https://apps.apple.com/jp/app/id1480652022
作成者: オープンソース・ソリューション・テクノロジ株式会社
上記会社が公開しているライブラリの試用版。もともとandroidでは日本の本人確認書類4種に対応していたので、現在iOS用に調整中?だと思われる。
現在は運転免許証とマイナンバーカードの読み取りがされている。上記のJPKIMobileとは読み取りの種類が違うが、入力補助と券面と2種類がある…
今後の展望
会社で初めてiOSの開発環境に触って。鍵だったり規格だったり全く知らないことを詰め込んで頭がパンクしていますが、自分が知ってる限りだけでもここまで広い活用をされていると思うと驚きでなりません。
今回紹介しなかった
Felica
やmifare
規格などの読み込みも盛んになっていけばと思うのと同時に、データを悪用するようなことがないよう開発者としては、正しい知識などを得ていければと思います。
こういった記事内や小さな勉強会などでもあればいいな…(本記事、手元にiPhoneが無い状態で作成したので画像が無くて申し訳ないです。手に取れるようになり次第画像を添付して再更新したいと思います!!)
- 投稿日:2019-12-24T07:11:29+09:00
SwiftUIを簡易的にUserDefaultsを使う方法
はじめに
SwiftUIでViewModelを使ったUserDefaultsを使う方法はよく見かけるのですが、簡易的にUserDefaultsを使う方法がありましたので紹介します。
あくまでの簡易的な使い方です。今回のサンプルコード
TextFieldに文字入力が終わるとTextに入力した結果が表示されます。
今回は、文字入力が終わったタイミングでUserDefaultsに保持して、アプリ起動時にUserDefaultsから読み出します。コード
今回は、文字入力が終わると
onCommit
内が実行されるのでそこでUserDefaultsに保存してします。
VStack{}
の後に.onAppear
が表示前に呼ばれるのでそこでUserDefaultsから値を読み出しています。ContentView.swiftimport SwiftUI struct ContentView: View { @State var inputText:String = "" @State var displayText:String = "" var body: some View { VStack { Text("Hello, \(self.displayText)!") TextField("Your name", text: $inputText , onCommit: { UserDefaults.standard.set(self.inputText, forKey: "name") self.displayText = self.inputText self.inputText = "" }) .padding() } .onAppear { self.displayText = "World" guard let userdefaultText = UserDefaults.standard.value(forKey: "name") as? String else { return } self.displayText = userdefaultText } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }参考サイト
- 投稿日:2019-12-24T07:07:25+09:00
[Swift]GitHub検索プログラムをprotocol, associatedTypesで改良する
先日、Swiftの基本的な内容を記した本「改訂新版 Swift実践入門(石川洋資・西山勇世、技術評論社、2018)」に従って、GitHubのリポジトリの検索プログラムを実装しました。
GitHub検索用APIには豊富な検索オプションが用意されているのですが、この本ではユーザーとリポジトリの検索のみ記載されていたのでとりあえずそれに従って実装しました。
検索結果を表すために、ジェネリクスを利用したSearchResponse構造体を実装しています。
SearchResponse.swiftstruct SearchResponse<Item: Decodable>: Decodable { let totalCount: Int let items: [Item]//構造体User, 構造体Repositoryを収められる //中略 }この構造体は、
SearchRepositories
,SearchUsers
という、APIへのリクエストを表す構造体にそれぞれassociatedTypesを用いて関連づけられています。GitHubAPI.swiftfinal class GitHubAPI { struct SearchRepositories: GitHubRequest { //中略 //リクエストに、対応するレスポンスを関連付けしている typealias Response = SearchResponse<Repository> // The word you would like to search with. let keyword: String } struct SearchUsers: GitHubRequest { //中略 //リクエストに、対応するレスポンスを関連付けしている typealias Response = SearchResponse<User> let keyWord: String } }GitHubRequest.swiftprotocol GitHubRequest { associatedtype Response: Decodable //後略 }associatedTypesとは、swiftのprotocolに任意の型を関連づけられるというものです。protocol版のジェネリクスのようなものと大雑把に捉えています(認識がおかしかったら教えてください)。
ところが本書の例では、プログラムのエントリポイントとなるmain.swiftで、
SearchReponse<Repository>
を使ってリポジトリは検索できるようになっているのに、せっかくのSearchReponse<User>
は使っておらず、ユーザーでは検索できないようになっていたのです?main.swift//中略 //SearchRepository構造体を用いてリクエストを作成 let request = GitHubAPI.SearchRepositories(keyword: "\(keyWord)") //リクエストをサーバへ送る client.send(request: request, completion: {(result) in switch result { case let .success(response): response.items.forEach {print($0)} exit(0) case let .failure(error): print(error) exit(1) } }) //中略せっかくユーザーで検索する仕組みを用意しているのに使わないではもったいない。
そこで、ユーザかリポジトリかどちらか選んで検索できるようにしようと実装してみました。具体的には、
SearchReponse<Repository>
もSearchReponse<User>
もどちらも渡して処理できるsendClient<T: GitHubRequest>(request: T)
関数を実装しようとしました。しかしコンパイルエラーが発生しました。
main.swiftfunc sendClient<T: GitHubRequest>(request: T) { let client = GitHubClient() client.send(request: request, completion: {(result) in switch result { case let .success(response): response.items.forEach {print($0)} //Error: Value of type 'T.Response' has no member 'items' exit(0) case let .failure(error): print(error) exit(1) } }) }は?
どうやら他の箇所にも手を加えないと実現できないようです。解決策を考えてみました。
エラー内容は
Value of type 'T.Response' has no member 'items'
。原因は、
GitHubRequest
プロトコルにて、associatedtype Response: Decodable
とのみ規定していた事、つまりコンパイラからはResponse
という型に対する制約は「Decodable
である」事以外に規定されていなかった、という事でした。言い換えれば、
Response
という型がitems
という変数を持つという制約は今の所どこにもないため、コンパイラからはそのことが分からないのです。だからエラーが発生したのです。まずは、
Response
という型に対して「Decodable
である」のみならず「items
という変数を持つ」という制約も付け加える必要があります。そのため、新しいプロトコルAbstructResponse
を作成し、Response
型をこれに準拠させることにしました。SearchResponse.swift//新たなプロトコルに準拠させる struct SearchResponse<Item: Decodable>: AbstructResponse { typealias Item = Item let totalCount: Int let items: [Item] //中略 } } //新たなプロトコルを定義 protocol AbstructResponse: Decodable { associatedtype Item: Decodable var totalCount: Int {get} var items: [Item] {get} //itemsという変数を持つという制約が加わる }はい、このようにするとコンパイラは
Response
型を見ただけで、それがitems
という変数を持つことが分かるようになり、エラーが解消します。あとは、変更点に合わせて他の部分も変更するだけです。全ての変更点をまとめると下記のようになります。
main.swift
コードをコピペなどしたい場合はこちら参照: https://gitlab.com/Satoru_Aikawa/githubsearchproject2/commit/6591bce7dfca238e47afc658a3e3b4e7e6bbd1c0
参考文献・ウェブサイト
www.amazon.co.jp/dp/477419414X
https://github.com/Satoru-PriChan/GitHubSearch
https://www.hackingwithswift.com/example-code/language/what-is-a-protocol-associated-type
- 投稿日:2019-12-24T06:44:06+09:00
SwiftUIでTabBarを作る方法
はじめに
SwiftUIでTabBarの作る方法を紹介します。
TIPSとして役立ててください。今回の作るTabBar画面
TabAView
とTabBView
を作りそれをTabBarで画面を切り替えるSwiftUIを作ります。作り方
TabAViewとTabBViewを作る
File>New>File...でSwiftUI Viewで
TabAview
とTabBView
を新規にファイル追加します。
中のコードは今回はわかりやすいように、TextにThis is Tab A.とThis is Tab B.と表示するようにします。TabAview.swiftimport SwiftUI struct TabAView: View { var body: some View { Text("This is Tab A.") } } struct TabAView_Previews: PreviewProvider { static var previews: some View { TabAView() } }TabBview.swiftimport SwiftUI struct TabBView: View { var body: some View { Text("This is Tab B.") } } struct TabAView_Previews: PreviewProvider { static var previews: some View { TabAView() } }ContentView.swiftにTabBarを定義する
TabViewと定義しその中に、TabBarの中のデザイン定義を
VStack{}
内に定義します。
今回は、アイコンと文字にしました。アイコンはどの環境にもあるSF Symbolを使っています。ContentView.swiftimport SwiftUI struct ContentView: View { var body: some View { TabView { TabAView() .tabItem { VStack { Image(systemName: "a") Text("TabA") } }.tag(1) TabBView() .tabItem { VStack { Image(systemName: "bold") Text("TabB") } }.tag(2) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }参考サイト
- 投稿日:2019-12-24T02:28:20+09:00
RxSwiftをざっくりと理解する(1)基礎用語まとめ
はじめに
andfactory Advent Calendar 23日目の記事です!
RxSwiftの内容を初学者に向けてざっくりとまとめてみました。
第1回は基礎用語です。Observable
それを購読(≒監視)しているオブジェクトに処理されるデータを流すもの。
* Observableはelementを含むnextイベントを流すことができる。
* terminateイベント(error, completed)が流れるまで続く。
* Observableが停止するとイベントは流れなくなる。Subscribe
Observableを購読(≒監視)すること。
onNext,onError,onCompleted,onDisposedのタイミングで行いたい処理を登録することができる。Disposable
メモリリーク防止のために必要であれば購読を解除してくれるオブジェクト
let observable = Observable.of(1, 2, 3) observable .subscribe( onNext: { element in // 値が流れてきたときの処理 }, onError: { error in // エラー時の処理 }, onCompleted: { // completeイベントが流れたときの処理 }, onDisposed: { // disposeされたときの処理 }) .disposed(by: disposeBag)Traits
通常のObservableより機能を限定したもの。
* コードの意図をより明確に伝えるのに役立つ。
* Single,Maybe,Completableの3つがある。
* Singleは success(value) か error しか流さないため、API通信などに便利。class Reader { static func loadData(from data: String) -> Single<String> { // 略 } } Reader.loadData(from: "text") .subscribe( onSuccess: { text in print(text) }, onError: { error in // エラー処理 }) .disposed(by: disposeBag)Subjects
ObserverとObservableの両方の役割を持てる。
RxSwiftにおいて4つのSubjectとそれをラップした2つのRelayがある。
* PublishSubject -> 初期値は持たない。購読者に対して新しいeventのみを流す。
* BehaviorSubject -> 初期値を持つ。最新の値を新たな購読者に対して流す。
* ReplaySubject -> バッファサイズを持って初期化される。そのサイズ分だけ値を保持して新たな購読者に流す。let subject = ReplaySubject<String>.create(bufferSize: 2) subject.onNext("a") subject.onNext("b") subject.onNext("c") subject .subscribe { print($0) // bとcがプリントされる } .disposed(by: disposeBag)
- AsyncSubject -> completedを受け取ると、nextイベントの最後の値だけを流す。あまり使われない?
- PublishRelay / BehaviorRelay -> それぞれPublishSubjectとBehaviorSubjectのラッパー。
- nextイベントのみを流せる。relayに対して.completedや.errorを流すことはできない。
- subjectをクラスの外部に公開してしまうと値の書き換えが自由にできてしまう。それを防ぐためにsubjectをprivateにしてObservableを外部に公開する。
class Some { private let someSubject = PublishSubject<String>() //Subjectはprivate var observable: Observable<String> { return someSubject } //Observableはpublic func sendString(str: String) { someSubject.onNext(str) //Subjectでイベントを流す } }
- 投稿日:2019-12-24T01:05:06+09:00
クリスマスまでにSwiftUIでPinterest風のグリッドビューを作りたかったのだけれど...
Qiita Advent Calendar iOS #2 最終日の記事です。
Merry Christmas
![]()
今日はクリスマスですね!大人になってしまったので
にプレゼント
をもらう楽しみはなくなってしまいましたが、逆に子どもたちにプレゼントして喜んだ笑顔
を見るのが楽しみになりました。
さて、最近そのプレゼント選びを便利に、そして楽しくするためにアプリを個人的に作りはじめました。そのアプリのイメージとしては、(僕の大好きな)Pinterest のアプリのように、並んだ写真を眺めながら選んでいくようなものを考えています。
せっかく新しく作るアプリなので SwiftUI で開発することにしました。まずは一番重要になる Pinterest 風のグリッドビューを SwiftUI で実現する方法をこのアドベンドカレンダーで共有したいと思い、調査を開始しました。
どうやらあの Pinterest 風のグリッドビューは Staggered grid view (ずらしたグリッドビュー) なんて呼ばれているみたいです。
SwiftUI には UIKit の UICollectionView にあたるものがない
![]()
いきなり出鼻をくじかれました...。
Staggered grid view を以前に UIKit で実装した時は UICollectionView を使いました。なので SwiftUI でもコレクションビュー的な View を探したのですが、ありません(キャッチアップ遅すぎ)。UITableView にあたるものは List として存在しているのに〜
![]()
SwiftUI での実装事例を漁る
![]()
調べている間に、SwiftUI で Grid view や Staggered grid view を実装しているような事例をいくつか見つけたので、どんな風に実装されているのか調べてみました。
SwiftUIExtensions/SwiftUIExtensions - Grid based layouts
GitHub リポジトリ SwiftExtensions/SwiftUIExtensions で Grid based layouts として全て同じサイズの Grid view と Staggered grid view の事例がありました!早速どんな風に実装されているのか見てみます。
重要なレイアウトの部分は主に Sources/SwiftUIExtensions/Layouts/Grid/Grid.swift に書かれていました。そのビュー構造を簡略化すると次の様になっています。
ZStack はその名の通りビューの Z 軸方向のスタックビューです。この下に ForEach でグリッドの数だけビューを並べていき、そのビューは GeometryReader から得た親ビューのサイズ/位置などの情報を元に計算して求められた場所に配置されています。
(GeometryReader については SwiftUIの肝となるGeometryReaderについて理解を深める でわかりやすく解説されています。)
これは親のビューのジオメトリを元に UIView を座標計算しながら配置していた Auto Layout 以前のレイアウトと同じではないか!?ということは表示するグリッドの数が少し増えただけですぐ限界くるやつでは?
試しにこれのデモアプリの Staggered Grid で表示するグリッドの数を 10,000 にしてシミュレータで実行してみたら、ビューが表示されるまでに 20秒 もかかりました(やっぱりそうだよね...)。一度に100くらいのグリッドなら問題なさそうですが、大量のデータでは実用レベルではないことがわかりました。次!
Q-Mobile/QGrid
SwiftUI でグリッドビューを実現する Q-Mobile/QGrid GitHub リポジトリを見つけました。このリポジトリの description にはこうあります。
? QGrid: The missing SwiftUI collection view.
これは期待できそう。Staggered が実現できるかどうかはおいておいて、実装の参考にはなることを期待してコードを読んでみます。このリポジトリのソース構成はとってもシンプルで Sources/QGrid/QGrid.swift だけです。そのビュー構造を簡略化すると次の様になっていました。
つまり、垂直方向のスタックビューと水平方向のスタックビューを組み合わせているんですね。なるほど、嫌な予感がします
いや、でも実は VStack は SwiftUI.List や UIKit.UITableView のように仮想リストビュー的に動いてくれるのかもしれないので念の為検証してみよう、そうしよう。
こちらもリポジトリにデモアプリがあるので、それのグリッドの数を 10,000 にしてシミュレータで実行してみたら、1分経っても何も表示されずに固まってしまいました。やはり VStack や HStack は UIKit の UIStackView 相当のものなんでしょうね(ForEach という繰り返し機構があるのでちょっとだけ期待したのですが)。
自分で考えてみるが...
現状、仮想リストビュー的に動作してくれるものが List しかないということがわかったので、この List を使ってなんとかできないか考えてみます。
- List を HStack で複数並べて、各 List に流すデータを制御すればいいのでは!?
- それぞれの List で独立してスクロールすることになってしまうなぁ
- List のスクロールイベントを拾って、それぞれの List のスクロール位置を同期させれば!?
- スクロール位置を知る手段も変更する手段もないらしい...
うーん、うーん、うーん ...
![]()
![]()
![]()
ということで悩んでいるうちにアドベントカレンダーの投稿日の朝となってしまいました。やはり実現するには SwiftUI としてコレクションビューが提供されることを待つしかなさそうな気がします。表示するグリッドの数が少なければ、紹介したライブラリを使うのもありです。何か良いアイデアがあれば教えていただけると嬉しいです。
今作っているアプリは、とりあえずこの Staggerd grid view の画面については UICollectionView で実装することになりそうです。
それではみなさん、良いクリスマスを
![]()
- 投稿日:2019-12-24T00:55:39+09:00
署名なしでiOSアプリのビルド&単体テスト〜GitHub Actions編〜
はじめに
GitHub Actionsを使い、iOSアプリのビルドと単体テストを行うCIを構築します。
「GitHub Actions」とは?
GitHubが提供しているクラウドのCI/CDサービスです。
2019/11/14に一般公開されました。
https://twitter.com/GitHubJapan/status/1194674026607562753?s=20環境
- Xcode:11.2.1 (11B500)
- Swift:5.1.2
注意
今回紹介するCIはもっとブラッシュアップできると思います。
あくまでGitHub ActionsでiOSアプリをCIする参考としてください。
この記事も随時更新していく予定です。設定ファイルの構成
ワークフローはYAMLファイルに記述します。
最初に全体の構成を説明します。run-unit-tests.ymlname: RunUnitTests on: [push] jobs: build: runs-on: macOS-latest env: DEVELOPER_DIR: /Applications/Xcode_11.2.1.app/Contents/Developer steps: …
名前 説明 name ワークフローの名前 on ワークフロー実行のトリガー
push
やpull_request
などが指定できるjobs ワークフローのジョブ runs-on 実行するマシン
iOSアプリはmacOSでしかビルドできないためmacOS-latest
を指定しているenv 環境変数
DEVELOPER_DIR
でXcodeのパスを指定している詳細は以下をご参照ください。
https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actionsステップの紹介
ワークフロー内のステップを1つずつ紹介します。
ステップは
steps
に記述します。run-unit-tests.ymlname: RunUnitTests on: [push] jobs: build: runs-on: macOS-latest steps: # ここにステップを記述する …チェックアウト
まずはチェックアウトです。
GitHubリポジトリからソースを取得します。定義されているアクションは
uses
で使えます。run-unit-tests.yml- uses: actions/checkout@v1Xcodeの選択
どのXcodeを使うか選択します。コメント を頂いて初めて知ったのですが、
DEVELOPER_DIR
という環境変数でXcodeのパスを指定できます。
sudo
を使わずに指定できるため、環境変数を使った方がよさそうです。run-unit-tests.yml- name: Set Xcode Path run: sudo xcode-select --switch /Applications/Xcode_11.2.1.app/Contents/DeveloperRuby製ライブラリのインストール
Bundlerで管理しているライブラリをインストールします。
シェルスクリプトの実行は、
name
に任意のステップ名、run
にシェルスクリプトを指定します。run-unit-tests.yml- name: Bundle install run: bundle installGemfileは以下の通りです。
必要に応じてCocoaPodsなども入れてください。Gemfile# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "xcpretty" gem "slather"各ライブラリの用途は使うときに説明します。
ビルド
ビルドを実行し、ビルドエラーにならないか確認します。
run
内では改行がスペースに変換されるため、可読性のために改行しています。run-unit-tests.yml- name: Xcode build run: xcodebuild -sdk iphonesimulator -configuration Debug build | bundle exec xcpretty
xcodebuild
コマンドを使ってビルドしていますが、デフォルトだとログの内容が煩雑で読みづらいです。
そのため、ログ整形ツールのxcpretty
を噛ませて読みやすくしています。GitHub ActionsのmacOSではデフォルトで
xcpretty
がインストールされているのですが、できる限りバージョンをロックしたいため、Bundlerでインストールしてbundle exec xcpretty
で実行しています。Bundlerについては以下の記事をご参照ください。
https://qiita.com/uhooi/items/4abf8c282ae23a259e4f単体テストの実行
単体テストを実行します。
run-unit-tests.yml- name: Xcode test run: xcodebuild -sdk iphonesimulator -configuration Debug -scheme {スキーム名} -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max' -skip-testing:{UIテスト} clean test | bundle exec xcpretty --report html単体テストの実行も
xcodebuild
コマンドを使います。
今回はiPhone 11 Pro Maxのシミュレータで実行しています。
-skip-testing
オプションでUIテストの実行をスキップしています。
私の場合、UIテストは単体テストほど頻繁には実施しません。
xcpretty --report html
で単体テストの結果をHTMLで出力します。コードカバレッジの出力
Slatherを使ってコードカバレッジをHTMLに変換します。
run-unit-tests.yml- name: Convert Code coverage to HTML run: bundle exec slather coverage --html --output-directory html_report
--html
オプションを付けることで、コードカバレッジをHTMLで出力します。Slatherの設定ファイルは以下の通りです。
.slather.ymlcoverage_service: cobertura_xml xcodeproj: {プロジェクト名}.xcodeproj workspace: {ワークスペース名}.xcworkspace scheme: {スキーマ名}Slatherの詳細は以下の記事をご参照ください。
https://qiita.com/uhooi/items/e1e464777d2163286c59テスト結果のアップロード
テスト結果をアーティファクト化してアップロードします。
run-unit-tests.yml- uses: actions/upload-artifact@v1 with: name: test-results path: build/reports
name
に任意のアーティファクト名、path
にアップロードしたいファイルやフォルダのパスを指定します。
xcprettyで出力したテスト結果はbuild/reports
フォルダに配置されるので、そこを指定すればOKです。コードカバレッジのアップロード
テスト結果と同様、コードカバレッジをアーティファクト化してアップロードします。
run-unit-tests.yml- uses: actions/upload-artifact@v1 with: name: test-coverage path: html_reportフォルダは先ほど
--output-directory
オプションで指定したhtml_report
を指定します。全体図
最後に設定ファイルの全体図を載せます。
run-unit-tests.ymlname: RunUnitTests on: [push] jobs: build: runs-on: macOS-latest env: DEVELOPER_DIR: /Applications/Xcode_11.2.1.app/Contents/Developer steps: - uses: actions/checkout@v1 - name: Bundle install run: bundle install - name: Xcode build run: xcodebuild -sdk iphonesimulator -configuration Debug build | bundle exec xcpretty - name: Xcode test run: xcodebuild -sdk iphonesimulator -configuration Debug -scheme {スキーム名} -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max' -skip-testing:{UIテスト} clean test | bundle exec xcpretty --report html - name: Convert Code coverage to HTML run: bundle exec slather coverage --html --output-directory html_report - uses: actions/upload-artifact@v1 with: name: test-results path: build/reports - uses: actions/upload-artifact@v1 with: name: test-coverage path: html_reportシンプルなYAMLファイルなので、慣れれば読みやすいと思います。
アーティファクトの確認
アップロードしたアーティファクトはGitHub Actions上からダウンロードできます。
テスト結果とコードカバレッジをHTMLでいい感じに出力できました?
S3などにアップロードし、直接HTMLを確認できるようにするとさらによさそうです。
他にやりたいこと
時間の都合上で実現できていないことを、備忘録として残します。
Slackに通知
通知は多過ぎると読まれない ため、アクションを起こす必要がある場合のみSlackに通知します。
具体的には以下の通りです。
- ビルドに失敗する
- エラーメッセージ
- ビルドで警告が発生する
- 警告メッセージ
- 失敗するテストがある
- ファイル名
- クラス名
- メソッド名
- 行数
- 失敗メッセージ
- コードカバレッジが80%以下のファイルがある
- ファイル名
- コードカバレッジ
おわりに
GitHub Actionsで基本的なiOSアプリのCIを回すことができました!
ほぼ全てシェルのコマンドをラップせずに使っているため、GitHub Actions以外のサービスを使っていても役立つ内容となっています?
参考リンク