20191224のiOSに関する記事は14件です。

Awesome CoreNFC in iOS 【iPhone でピッと IC カードを読む】

はじめに

CoreNFC は、iPhone でピッと IC カードを読みこめる機能です。

利用できるICカードは幅広く、交通系IC、各種電子マネー、大学生協の電子マネー、運転免許証、パスポート、マイナンバーカードなどあります。

今年9月19日に利用可能になった CoreNFC はまだ日本語の学習記事があまりありません。この記事では、CoreNFC を日本の開発者にもっと活用して頂く目的で、情報をまとめさせて頂きます。将来的に初学者に良いまとめになれば幸いです。

改善コメントや編集リクエストをどんどん募集しております。どうぞよろしくお願い申し上げます?‍♂️

キータ

サンプル・アプリケーション

  • TRETJapanNFCReader/Examples - 交通系ICと運転免許証の読み込み例がのっております。
  • TransitPal - アメリカのクリッパーカードの読み込みですが、とても良いサンプルアプリケーションです。Swift UI でシンプルながら、下記画像のようなかなり綺麗な UI を学べます。

image.png image.png
Screenshots from TransitPal

オープン・ソース・ライブラリ

  • TRETJapanNFCReader - 交通系IC、各種電子マネー、大学生協の電子マネー、運転免許証の読み込みを手軽に扱えます。

記事を良くするコメントや編集リクエスト

この記事は、CoreNFC について学ぶ際に、興味のある各記事に進むのにご利用いただければと思います。皆様の、記事を良くするコメントや編集リクエストをお待ちしております。お願い申し上げます?‍♂️

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Apple pencilとiPadでとった「手書きメモ」が自動でブログになる勉強方法が捗りすぎるので方法を公開します ( Evernote postach.io )

勉強でとったメモがそのままブログに!!

東京グルメ紀行
https://qiita.postach.io/

この記事と全く同じ内容+お絵かき入りのブログ

https://qiita.postach.io/post/ipaddetotuta-shou-shu-kimemo-gazi-dong-deburoguninarumian-qiang-fang-fa-gabu-risugirunodeyarifang-wojiao-emasu-evernote-postach-io

このブログのように、evernoteでとった手書きメモやお絵かき入りのノートを直接いい感じのブログにできます。

iPadと Apple pencilがあれば、簡単な表も手書きでパッと書いて公開できるので、アルゴリズムの解説記事はevernoteで書いて公開した方が楽です。
(Qiitaはお絵かきが挿入できないのがだるい)

手順(全部無料です)

  • Evernoteをインストール

Evernoteはテキストと手書きメモでノートを取れるアプリです。
使い方はググってください

  • postach.ioに登録する

以下のリンクにアクセスし、登録します

https://postach.io

  • postach.ioノートブック(登録したら勝手にできている)内にノートを作る。

このとき、publishedというタグを付けると公開されます。

これで準備は完了です

postach.ioというノートブック内にノートを取り、publishedタグを付けてノートを取ると、ブログが投稿されます。

ノートを更新すると自動でブログに反映されます。

超便利ですよね?自分の勉強がそのままブログになるので、モチベーション維持に欠かせないものになりました!!

FAQ

  • markdown記法はつかえるの?

markdownタグを付けるとできます。以下の記事もマークダウンで書かれてます。

この記事と全く同じ内容+お絵かき入りのブログ

https://qiita.postach.io/post/ipaddetotuta-shou-shu-kimemo-gazi-dong-deburoguninarumian-qiang-fang-fa-gabu-risugirunodeyarifang-wojiao-emasu-evernote-postach-io

  • アフィリエイトはできるの?

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を入力できる画面が出ます。

  • 以上ですが何かあればコメント欄で聞いて下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】算数で横スクロールのCollectionViewの高さを求める

スクローラブルと認識できるデザイン

まず初めに、iOSに限らずですがスクロール可能と認識できるデザインがあります。

Blank Diagram.png

こんな感じです。2つのセルが完全に表示されていて、残り1つは半分ほど出ているデザインです。
これはユーザーが見た時に「あ、横にスクロールできるんだな」と直感的に思わせるためですね。

これをみた時にCollectionViewの高さとセルのサイズってどうするんだろうと思ってました :thinking:
よく考えてみるとある程度条件を絞って算数の力を使えば導き出せることに気づいたので実装してみます(正確には中学生で習う1次方程式ですが、ほぼ算数なので許してください)。

計算してみる

まずは手元で計算してみます。
条件を整理して実際に数式を出すところまでやります。

条件の整理

今回は事前に計算をして計算結果を実装に当てはめる、ということが必要なのである程度の条件が必要となります。
以下が条件です。

  1. 各セルの縦横比が確定している
  2. 各セル同士のスペースが確定している
  3. CollectionViewのwidthが確定している

図にするとこんな感じです。

Blank Diagram (2).png

そこまで条件が多いわけではないので、大体の場合で使えるかなーという感じはしてます。
今回は計算を楽にするためにパディングは上下と一番左を同じ値にしてセル間をその2倍としています。

数式を出す

条件が出たのでそれを数式に落とし込んでみましょう。

セルの比

まずは1つ目の条件 各セルの縦横比が確定している です。
これは比をそのまま計算式にするだけです。

Blank Diagram (1).png

このように定義すると

\begin{aligned}
a:b = w:h => h = \frac{b}{a}w
\end{aligned}

$h$を$w$で表せました。

CollectionViewのwidth

次に2つ目と3つ目の条件を使うとCollectionViewの横幅を表すことができます。
先ほどの図を見るとわかりやすいですが、$W$はセルの横幅の合計とパディングの合計を足した値になるのがわかると思います。

Blank Diagram (2).png

まずセルの横幅の合計は単純にセルの横幅×個数なので$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の高さから設定します。

スクリーンショット 2019-12-23 22.22.30(2).png

CollectionViewをストーリーボード上に配置し、top, right, leftを親に合わせていきます。
これで$W$を画面の横幅と一致させています。

スクリーンショット 2019-12-23 22.22.42(2).png

親のViewのheightとCollectionViewのheightをアスペクト比で合わせます。

スクリーンショット 2019-12-23 22.30.25(2).png

1つ前で親のheightを指定していることに違和感があったかもしれないですが、さっきの段階でheightをwidthに合わせる、みたいなことができないみたいようです。しょうがなく一旦親のViewのheightに合わせていました(もし知っている方がいたら教えて欲しいです :pray: )。
そういう理由もありここでheightからwidthに変更します。

スクリーンショット 2019-12-23 23.06.32(2).png

最後に数式の出番です。
ここでは $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にソースは載せてあるので良かったらそちらも見てください。

まとめ

ちゃんと計算したかったので、ガウス記号とかを取り入れました。それっぽい数式になって良かったです笑
ちなみにもっと簡単な方法を知っている方がいたら教えてください :pray:

最後に注意事項としてこのままのやり方だと問題点があります。それは画面回転した時の挙動です。
実際に実装してみて回転させると分かるのですが、セルが2行で表示されたりセルの大きさが変わったりしてしまいます。
そもそも縦固定が想定されてる実装になっているのでしょうがないのですが :rolling_eyes:
もし機会があれば画面回転にも対応した実装をして記事にしてみたいです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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年)が切れるとアプリが起動できなくなる
  • 配布用証明書が無効になるとアプリが起動できなくなる
  • 新たにデバイスを追加する場合、下記の手順が必要
    1. ADPにデバイスを登録
    2. 追加したデバイスを Provisioning Profile に追加
    3. ipaファイルの作成

カスタムB2B 配布

  • VPP(Volume Purchase Program)を利用した配布方法
  • ADPでしか使えない
  • 配布前に審査に出す必要がある
  • 配布先企業は Apple Business Manager に登録しなければならない
  • Apple Business ManagerVolume 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 Developer

Certificates

証明書の管理

開発用証明書

主に使うもの:

  • 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を使用して認証できるようにする時に利用する

あとがき

間違ってるところなどあればご指摘いただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.comFASTLANE_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. FASTLANE_SESSION="", cookieがない場合
    2FA
    Screen Shot 2019-12-24 at 22.32.37.png

  2. FASTLANE_SESSION="", 有効なcookieが存在する場合
    2FAスキップ
    Screen Shot 2019-12-24 at 22.31.01.png

  3. FASTLANE_SESSION="fastlane_session value of apple_id_2fa@test.com", cookieがない場合
    2FAスキップ
    Screen Shot 2019-12-24 at 22.30.17.png

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"] = ""
を設定します。

Screen Shot 2019-12-24 at 23.03.11.png

これにより、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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で作ろうかなんて考えてます。もちろん技術だけではなく自分なりに「これは受ける!」ってアプリを作っていきます。

宣伝にはなっちゃいますが自分が作ったチケットタスクというアプリ、親バカですがすごく可愛いアプリなので是非インストールしてみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を作っておくと個人的には管理が楽だなぁと思います。

スクリーンショット 2019-12-24 11.46.28.png
Groupを作ったら先ほど教えてもらったアカウント名を作ったGroupに追加してください。

スクリーンショット 2019-12-24 11.34.07.png
次にアプリのダッシュボード -> Teamに先ほどのGroupをTESTERS / QAに追加しましょう。

スクリーンショット 2019-12-24 11.42.58.png
「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はいいんじゃないかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

URL: https://apps.apple.com/jp/app/jpki%E5%88%A9%E7%94%A8%E8%80%85%E3%82%BD%E3%83%95%E3%83%88/id1481154805?mt=8

作成者:地方公共団体情報システム機構(JBIS)

マイナンバーカードの読み取りに利用されています。マイナンバーカードは仕様を知らないのですが、以下の2点が読めるようです

  • 署名用電子証明書
    • 主に電子申請(WEBでの確定申告など?)に利用される署名用の証明書
  • 利用者証明用電子証明書
    • これは本人が利用する証明になります。

マイナンバーカードの鍵や仕様はたしか非公開だったので政府等の依頼で作成したりしたのでしょうかね…?

LibJeID

URL: https://apps.apple.com/jp/app/id1480652022

作成者: オープンソース・ソリューション・テクノロジ株式会社

上記会社が公開しているライブラリの試用版。もともとandroidでは日本の本人確認書類4種に対応していたので、現在iOS用に調整中?だと思われる。

現在は運転免許証とマイナンバーカードの読み取りがされている。上記のJPKIMobileとは読み取りの種類が違うが、入力補助と券面と2種類がある…


今後の展望

会社で初めてiOSの開発環境に触って。鍵だったり規格だったり全く知らないことを詰め込んで頭がパンクしていますが、自分が知ってる限りだけでもここまで広い活用をされていると思うと驚きでなりません。

今回紹介しなかったFelicamifare規格などの読み込みも盛んになっていけばと思うのと同時に、データを悪用するようなことがないよう開発者としては、正しい知識などを得ていければと思います。
こういった記事内や小さな勉強会などでもあればいいな…

(本記事、手元にiPhoneが無い状態で作成したので画像が無くて申し訳ないです。手に取れるようになり次第画像を添付して再更新したいと思います!!)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIを簡易的にUserDefaultsを使う方法

はじめに

SwiftUIでViewModelを使ったUserDefaultsを使う方法はよく見かけるのですが、簡易的にUserDefaultsを使う方法がありましたので紹介します。
あくまでの簡易的な使い方です。

今回のサンプルコード

TextFieldに文字入力が終わるとTextに入力した結果が表示されます。
今回は、文字入力が終わったタイミングでUserDefaultsに保持して、アプリ起動時にUserDefaultsから読み出します。

1.png 2.png 3.png

コード

今回は、文字入力が終わるとonCommit内が実行されるのでそこでUserDefaultsに保存してします。
VStack{}の後に.onAppearが表示前に呼ばれるのでそこでUserDefaultsから値を読み出しています。

ContentView.swift
import 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()
    }
}

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]GitHub検索プログラムをprotocol, associatedTypesで改良する

先日、Swiftの基本的な内容を記した本「改訂新版 Swift実践入門(石川洋資・西山勇世、技術評論社、2018)」に従って、GitHubのリポジトリの検索プログラムを実装しました。

GitHub検索用APIには豊富な検索オプションが用意されているのですが、この本ではユーザーとリポジトリの検索のみ記載されていたのでとりあえずそれに従って実装しました。

検索結果を表すために、ジェネリクスを利用したSearchResponse構造体を実装しています。

SearchResponse.swift
struct SearchResponse<Item: Decodable>: Decodable {
    let totalCount: Int
    let items: [Item]//構造体User, 構造体Repositoryを収められる

    //中略
}

この構造体は、SearchRepositories, SearchUsersという、APIへのリクエストを表す構造体にそれぞれassociatedTypesを用いて関連づけられています。

GitHubAPI.swift
final 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.swift
protocol 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.swift
func 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という変数を持つことが分かるようになり、エラーが解消します。あとは、変更点に合わせて他の部分も変更するだけです。

全ての変更点をまとめると下記のようになります。

GitHubRequest.swift
螢幕快照 2019-12-12 11.46.04.png

SearchResponse.swift
螢幕快照 2019-12-12 11.46.17.png

main.swift

螢幕快照 2019-12-12 11.46.49.png
螢幕快照 2019-12-12 11.47.05.png
螢幕快照 2019-12-12 11.47.33.png
螢幕快照 2019-12-12 11.47.42.png

コードをコピペなどしたい場合はこちら参照: 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIでTabBarを作る方法

はじめに

SwiftUIでTabBarの作る方法を紹介します。
TIPSとして役立ててください。

今回の作るTabBar画面

TabAViewTabBViewを作りそれをTabBarで画面を切り替えるSwiftUIを作ります。

TabA.png TabB.png

作り方

TabAViewとTabBViewを作る

File>New>File...でSwiftUI ViewでTabAviewTabBViewを新規にファイル追加します。
中のコードは今回はわかりやすいように、TextにThis is Tab A.This is Tab B.と表示するようにします。

TabAview.swift
import SwiftUI

struct TabAView: View {
    var body: some View {
        Text("This is Tab A.")
    }
}

struct TabAView_Previews: PreviewProvider {
    static var previews: some View {
        TabAView()
    }
}
TabBview.swift
import 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.swift
import 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()
    }
}

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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でイベントを流す
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クリスマスまでにSwiftUIでPinterest風のグリッドビューを作りたかったのだけれど...

Qiita Advent Calendar iOS #2 最終日の記事です。

:santa::christmas_tree::tada: Merry Christmas :santa::christmas_tree::tada:

今日はクリスマスですね!大人になってしまったので :santa: にプレゼント :gift: をもらう楽しみはなくなってしまいましたが、逆に子どもたちにプレゼントして喜んだ笑顔 :smile: を見るのが楽しみになりました。

さて、最近そのプレゼント選びを便利に、そして楽しくするためにアプリを個人的に作りはじめました。そのアプリのイメージとしては、(僕の大好きな)Pinterest のアプリのように、並んだ写真を眺めながら選んでいくようなものを考えています。

せっかく新しく作るアプリなので SwiftUI で開発することにしました。まずは一番重要になる Pinterest 風のグリッドビューを SwiftUI で実現する方法をこのアドベンドカレンダーで共有したいと思い、調査を開始しました。

どうやらあの Pinterest 風のグリッドビューは Staggered grid view (ずらしたグリッドビュー) なんて呼ばれているみたいです。

SwiftUI には UIKit の UICollectionView にあたるものがない :cry:

いきなり出鼻をくじかれました...。

Staggered grid view を以前に UIKit で実装した時は UICollectionView を使いました。なので SwiftUI でもコレクションビュー的な View を探したのですが、ありません(キャッチアップ遅すぎ)。UITableView にあたるものは List として存在しているのに〜 :cry:

SwiftUI での実装事例を漁る :mag_right:

調べている間に、SwiftUI で Grid view や Staggered grid view を実装しているような事例をいくつか見つけたので、どんな風に実装されているのか調べてみました。

SwiftUIExtensions/SwiftUIExtensions - Grid based layouts

staggeredGrid.png

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

People3.png

SwiftUI でグリッドビューを実現する Q-Mobile/QGrid GitHub リポジトリを見つけました。このリポジトリの description にはこうあります。

? QGrid: The missing SwiftUI collection view.

これは期待できそう。Staggered が実現できるかどうかはおいておいて、実装の参考にはなることを期待してコードを読んでみます。このリポジトリのソース構成はとってもシンプルで Sources/QGrid/QGrid.swift だけです。そのビュー構造を簡略化すると次の様になっていました。

つまり、垂直方向のスタックビューと水平方向のスタックビューを組み合わせているんですね。なるほど、嫌な予感がします:sweat_drops:いや、でも実は VStack は SwiftUI.List や UIKit.UITableView のように仮想リストビュー的に動いてくれるのかもしれないので念の為検証してみよう、そうしよう。

こちらもリポジトリにデモアプリがあるので、それのグリッドの数を 10,000 にしてシミュレータで実行してみたら、1分経っても何も表示されずに固まってしまいました。やはり VStack や HStack は UIKit の UIStackView 相当のものなんでしょうね(ForEach という繰り返し機構があるのでちょっとだけ期待したのですが)。

自分で考えてみるが...

現状、仮想リストビュー的に動作してくれるものが List しかないということがわかったので、この List を使ってなんとかできないか考えてみます。

  • List を HStack で複数並べて、各 List に流すデータを制御すればいいのでは!?
  • それぞれの List で独立してスクロールすることになってしまうなぁ
  • List のスクロールイベントを拾って、それぞれの List のスクロール位置を同期させれば!?
  • スクロール位置を知る手段も変更する手段もないらしい...

うーん、うーん、うーん ...

:sunrise: :sunrise: :sunrise: :sunrise:

ということで悩んでいるうちにアドベントカレンダーの投稿日の朝となってしまいました。やはり実現するには SwiftUI としてコレクションビューが提供されることを待つしかなさそうな気がします。表示するグリッドの数が少なければ、紹介したライブラリを使うのもありです。何か良いアイデアがあれば教えていただけると嬉しいです。

今作っているアプリは、とりあえずこの Staggerd grid view の画面については UICollectionView で実装することになりそうです。

それではみなさん、良いクリスマスを :wine_glass:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

署名なしで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.yml
name: RunUnitTests

on: [push]

jobs:
  build:
    runs-on: macOS-latest
    env:
      DEVELOPER_DIR: /Applications/Xcode_11.2.1.app/Contents/Developer
    steps:
      
名前 説明
name ワークフローの名前
on ワークフロー実行のトリガー
pushpull_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.yml
name: RunUnitTests

on: [push]

jobs:
  build:
    runs-on: macOS-latest
    steps:
      # ここにステップを記述する
      

チェックアウト

まずはチェックアウトです。
GitHubリポジトリからソースを取得します。

定義されているアクションは uses で使えます。

run-unit-tests.yml
- uses: actions/checkout@v1

Xcodeの選択

どの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/Developer

Ruby製ライブラリのインストール

Bundlerで管理しているライブラリをインストールします。

シェルスクリプトの実行は、 name に任意のステップ名、 run にシェルスクリプトを指定します。

run-unit-tests.yml
- name: Bundle install
  run: bundle install

Gemfileは以下の通りです。
必要に応じて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.yml
coverage_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.yml
name: 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上からダウンロードできます。
スクリーンショット_2019-12-24_0_17_03.jpg

テスト結果
スクリーンショット_2019-12-24_0_35_56.jpg

コードカバレッジ
スクリーンショット_2019-12-24_0_36_13.jpg

テスト結果とコードカバレッジをHTMLでいい感じに出力できました?

S3などにアップロードし、直接HTMLを確認できるようにするとさらによさそうです。

他にやりたいこと

時間の都合上で実現できていないことを、備忘録として残します。

Slackに通知

通知は多過ぎると読まれない ため、アクションを起こす必要がある場合のみSlackに通知します。
具体的には以下の通りです。

  • ビルドに失敗する
    • エラーメッセージ
  • ビルドで警告が発生する
    • 警告メッセージ
  • 失敗するテストがある
    • ファイル名
    • クラス名
    • メソッド名
    • 行数
    • 失敗メッセージ
  • コードカバレッジが80%以下のファイルがある
    • ファイル名
    • コードカバレッジ

おわりに

GitHub Actionsで基本的なiOSアプリのCIを回すことができました!

ほぼ全てシェルのコマンドをラップせずに使っているため、GitHub Actions以外のサービスを使っていても役立つ内容となっています?

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む