個人アプリを Xcode9 Swift4 対応した時にやったこと

個人アプリ2つを Xcode9 GM で動くようにした。
これは私のアプリの場合なので、違うプロジェクトでは不要なことや、他にもっとやらないといけないことがあると思う。

やったこと

1. Xcode9 DL

Xcode - Apple Developer から DL してインストール。
Xcode8もまだ使うので、8と9を同居させるため Xcode9GM.app にリネームした。

2. xcode-select で 9GM を使うようにする

$ sudo xcode-select -s /Applications/Xcode9GM.app/Contents/Developer/
$ xcode-select -p
/Applications/Xcode9GM.app/Contents/Developer

3. bundle update

念のため bundle update して cocoapods や fastlane を最新にした。

4. ライブラリ更新

$ pod update
$ carthage update --platform iOS --no-use-binaries

ここで Xcode 9 でプロジェクトをビルドすると動くようになった。 と言ってもこの時点ではまだ Swift3.2 のはなし。

5. Swift 4

メニュー > Edit > Convert > To Current Swift Syntax… を選ぶ、そうすると Xcode が自動でマイグレーションしてくれる。
マイグレーションしてくれたのはほとんどが dynamic@objc をつけるものだった。

あとはこんなところを手動で直した。

- let attributes = [NSFontAttributeName: UIFont.fontAwesome(ofSize: 20)] as [String: Any]
+ let attributes = [NSAttributedStringKey.font: UIFont.fontAwesome(ofSize: 20)]

- dismiss(animated: true, completion: { [weak self] _ in
+ dismiss(animated: true, completion: { [weak self] in

これだけで Swift4 のビルドは通った。 しかし起動したらエラー。

6. UIVisualEffectView を修正

let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))
- effectView.addSubview(UILabel())
+ effectView.contentView.addSubview(UILabel())

UIVisualEffectView は contentView に subview を追加しないといけなくなった。
これは実行時エラーなのでコンパイルは通ってしまう。

これを直したら起動した。

7. warning 対応

Swift3 @objc InterfaceOff にした。
また、 substring が使えなくなったり CGFloat(truncating: String) を使うように修正した。

7. Runtime Error

これは Swift4 対応とは特に関係ないと思われる。

アプリは問題なく動いているのだが、 UIApplication あたりでなんか出てた。

runtime: UI API called from background thread: -[UIApplication delegate] must be used from main thread only
runtime: UI API called from background thread: -[UIApplication applicationState] must be used from main thread only
runtime: UI API called from background thread: -[UIApplication keyWindow] must be used from main thread only
runtime: UI API called from background thread: -[UIApplication supportedInterfaceOrientationsForWindow:] must be used from main thread only

画像で見るとこんな感じ。

f:id:star__hoshi:20170917232741p:plain

ググったら Firebase が古い時のエラーのようで、 Firebase を新しくすれば消える。

  pod 'Firebase' , '~> 4.0'
  pod 'Firebase/AdMob' , '~> 4.0'
  pod 'Firebase/RemoteConfig' , '~> 4.0'

これで pod update すれば OK。

8. 新しい Google Analytics, Firebase 修正。

Firebase を新しくしたら依存する Google Analytics の書き方も変わったみたいなので、 Bridging-Header.h を直した。

+ #import <Google/Analytics.h>
- #import <GoogleAnalytics/GAI.h>
- #import <GoogleAnalytics/GAIFields.h>
- #import <GoogleAnalytics/GAILogger.h>
- #import <GoogleAnalytics/GAITracker.h>
- #import <GoogleAnalytics/GAIDictionaryBuilder.h>

あと Firebaes の FIR のプレフィックスを消す必要があった。

9. おわり

これで Simulator で Run したらエラーも消えてめでたし ㊗️

UITabBar の背景をすりガラスのまま Dark にする

普通の UITabBar は明るいすりガラスだけど、これを UIBlurEffect(style: .dark) と同じように暗くしたい。

Light Dark
f:id:star__hoshi:20170829220502p:plain f:id:star__hoshi:20170829220551p:plain
override func viewDidLoad() {
    super.viewDidLoad()

    tabBar.backgroundImage = UIImage()
    let dummy = UIToolbar(frame: tabBar.bounds)
    dummy.barStyle = .black
    tabBar.insertSubview(dummy, at: 0)
}

UITabBarController の viewDidLoad にこんな感じでかくと、これで Dark な UITabBar を作れる。透明な画像を UITabBar の背景に設定して、その下に Dark な UIToolbar を追加するハックっぽいやり方。

参考: ios - UITabBar with blur - Storyboard - Stack Overflow

また転職することにした

10ヶ月ほど前に意気揚々と 退職エントリ を書いたくせに、1年経たず退職となってしまった。

最初に断っておくと、前職よりは100倍マシだった。
けど悩みや欲望というのは無限に湧いてくるわけで、色々考えて転職することにした。

結局なんでやめんの

何か大きな出来事があったとかではなく、色々あった。

開発を外注して納期厳守だから突貫工事でリリースまではやるけど後は内部の人がメンテしてねという開発スタイルで渋い気持ちになったり、渋い割に給料が良くねえなと思ったり、 iOS エンジニアが 1 人しかいないので雑務が多かったり外注管理おじさんになったり、一人でプルリクみて一人でマージして寂しい気持ちになったり、ジョイントベンチャーだと自分たちじゃどうしようもないことあって辛いな〜と思ったりした。

「アプリなんてなければいいのに」という発言もあってだいぶショックを受けた。これは Apple の制限でできないことが多いことに対する文句なのだが、会社の体質をよく表していると思う。
アプリは「A は快適に使えるけど B は出来ない、 B をしたいなら Web を使ってくれ」というように、何かの機能に特化するのがよくある考え方だと思うけど『お客さんに出来ないと言えない』ので、アプリにも全ての機能を乗せようとする。しかし工数は限られるので WebView でそのページを開くだけ、みたいなことになる。そしてアプリ上の WebView がうまく動かないと問い合わせが来る。もうアプリない方が良くね?ユーザも混乱してるよ?全部 Web でやれば?ってなるじゃないですか。

やっていくぞ!!!!!!!!みたいに勢いある人がいてもつらくて、今まで色々開発を経験してそのノウハウを還元したい人とか、言われたことを素直にやる人にとってはいい会社なのかなと感じた。

ちょっと悲しいのが、自分が辞めるときに退職理由とか聞かれなかったこと。以前辞めた人も同じく聞かれなかったらしい。
新しい人を採用するぞ!と採用活動をするのはいいけど、今いる人を出さないことが一番大事なんじゃないのかな、と思う。 最近 1 on 1 が始まったらしいので、それで内部環境が改善されていくことを願っている。

別に悪い話ばかりじゃなくて、 iOS エンジニア 1 人しかいないから開発から CI 環境整備までやったので圧倒的成長ができた。辛いこともあったけど色々なことを経験できたし、入社して良かったと思ってる。

前職で不満だった上長のご機嫌取りとかする必要もないし、クラウドサービスも自由に使えたし、開発する時間も増えた。

一緒に働く人はいい人が多くてワイワイやれて楽しかったし、自分の前に入った2人の先輩には特にお世話になりました、感謝しています。

教育という事業は素晴らしいと心から思うし、子供たち、学校の先生のために頑張っていってほしい。

次は

今まではひたすらサービス開発をしていたので、そうじゃなくて開発者がサービス開発に集中できる環境づくりの手助けをしたいなと思って iOS の技術基盤をやることになっている。といっても別に iOS じゃなくても良くて、 Android の人が足りないならやりたいしサーバ側が足りないならそっちをやってもいい。

技術基盤というとメモリやOSなども詳しくないとダメなのでは?という気がして戦々恐々としている。 Objective-C も全然出来ないので不安である。サービス開発をメインやってきた人という目線で技術基盤として貢献できればと思っている。

前回の事業はドッグフーディング出来なかったけど次のはできるので使って改善していきたい。

ほしい物リスト

www.amazon.co.jp

fastlane pem で Push 通知に必要な p12 ファイルを生成する

iOS 用の Push 通知 証明書を生成したい。 真心を込めて手動で生成することもできるが、ここでは fastlane pem でコマンドで生成する。

fastlane pem

fastlane/pem at master · fastlane/fastlane

詳細は上の README みればやり方書いてる。

gem install fastlane

$ gem install pem

pem

  1. コマンドラインpem と打つ
  2. Apple ID 聞かれるので入力する
  3. Apple ID パスワード聞かれるので入れる
    • fastlane を信用する、不安なら OSS なのでコードを読むべし
  4. bundle id を聞かれるので該当のアプリの bundle id を入れる
  5. 証明書の期限が 30 日以内の場合、p12 ファイルが吐かれる
    • –force オプション使うと 30 以上でもファイルが吐かれるっぽい

下記のようなログと流れになる。

$ pem
+------------------+-------+
|  Summary for PEM 1.4.0   |
+------------------+-------+
| development      | false |
| generate_p12     | true  |
| force            | false |
| save_private_key | true  |
| output_path      | .     |
+------------------+-------+

[14:56:45]: To not be asked about this value, you can specify it using 'username'
Your Apple ID Username: Your Apple ID Username
[14:56:53]: Starting login with user 'Your Apple ID Username'
-------------------------------------------------------------------------------------
The login information you enter will be stored in your Mac OS Keychain
You can also pass the password using the `FASTLANE_PASSWORD` environment variable
More information about it on GitHub: https://github.com/fastlane/fastlane/tree/master/credentials_manager
-------------------------------------------------------------------------------------
Password (for Your Apple ID Username): *************
[14:57:01]: Successfully logged in
[14:57:02]: To not be asked about this value, you can specify it using 'app_identifier'
The bundle identifier of your app: jp.starhoshi.hoge
[14:57:18]: Existing push notification profile 'jp.starhoshi.hoge' is valid for 135 more days.
[14:57:18]: You already have a push certificate, which is active for more than 30 more days. No need to create a new one
[14:57:18]: If you still want to create a new one, use the --force option when running PEM.

あとは p12 ファイルを Amazon SNS とかにあげれば OK !

xcconfig を使い本体アプリと Embedded Framework を同じ環境設定でビルドする

本体アプリを Build した時に、本体アプリの CONFIGURATION を Embedded Framework 側に渡したい。

  • 本体アプリを Debug Build
    • Embedded Framework も Debug Build
  • 本体アプリを Release Build
    • Embedded Framework も Release Build

というように、本体アプリのビルド環境によって自動で Embedded Framework のビルド環境も変えたい。

xcconfig を使う

  1. Embedded Framework 側で xcconfig を作成する
    • New File から Configuration Settings File を選択
    • Targets は Embedded Framework を選ぶ
  2. hoge.xcconfig に下記を追記
    • OTHER_SWIFT_FLAGS = "-D" $(CONFIGURATION)

これでビルドすると、本体でビルドした環境を Embedded Framework 側に渡せる。

参考

The Unofficial Guide to xcconfig files

ここにサンプルやユースケースなど色々書いてあって便利。 今回は CONFIGURATION を渡したが、他にも色々な設定ができる。

TodayViewController の viewDidLoad が呼ばれるタイミングについて

iOS の TodayExtension を実装していて、 TodayViewController の viewDidLoad が走るタイミングが最初はわからず苦労した。

viewDidLoad が呼ばれるタイミング

画面に表示されたタイミングで ほぼ毎回 viewDidLoad が呼ばれる。
TodayExtension を 10 こくらい並べて、スクロールして画面外から表示させても viewDidLoad が呼ばれる。

開発し始めた最初は

  1. 端末起動後/Widget 追加後に viewDidLoad が 1 度だけ呼ばれる
  2. 画面表示時に widgetPerformUpdate(completionHandler:) が呼ばる
    • 更新の必要があれば completionHandler(.newData) を実行する

だと思って開発を始めたため、何度も viewDidLoad が呼ばれて戸惑った。

TodayExtension の高さを変えるような時に問題が生じる

TodayExtension Widget 表示時、こう動く。(TableView で、 List の要素 1 つにつき 50px 使うようなコードだとする。)

  1. 一瞬だけ前回取得したデータが表示される
    • 要素が 4 つあったので、 height が 200px くらいある
  2. Extension の表示が初期表示になる
    • 初期は要素数が 0 なので、 height がデフォルト(110px)になる
  3. 取得されたデータが表示される
    • しかし、 Show More になっているのにデータが2つしか表示されない…

Show More の状態は変わらないが画面はリセットされてしまい、表示がおかしい…。
こんな感じ。

f:id:star__hoshi:20170720140917g:plain

表示を改善するためには?

初期表示時に前回取得したデータを表示すれば良い。

  1. 一瞬だけ前回取得したデータが表示される
    • 前回の height が 200px だったので、それがそのまま表示される
  2. Extension を初期化する
    • 初期化時にローカルに保存しておいたデータを表示する
      • 画面に変化がないので height 200px を保つ
  3. 取得された最新のデータを表示する
    • 素数が 4 つだったので、 height が 200px になる
    • 取得したデータをローカルに保存しておく

つまり、前回取得したデータを保存しておいて、 viewDidLoad 時にそのデータを描画するとユーザとしては画面がチラチラせずに違和感なく使える。

そうすると初期化時に高さが前回と同じに設定されるので、突然小さくなったりしないですむ。

↓のは画面に変化がないように見えるが、実際には画面が初期化されたりしている。

f:id:star__hoshi:20170720140853g:plain

ローカルにデータを保存

保存したいデータが String だけとかなら楽でいいけど、実際は JSON Mapping されたクラスだったりするので、そのクラスに NSCoding を継承して data 型として保存すると楽。

UserDefault などに class を保存するサンプル

ちなみに TodayExtension で UserDefaults を使うには AppGroups の設定が必要。

おわり

最初は viewDidLoad が呼ばれまくることに違和感があるが、 UserDefaults などを使って適切にデータを管理すればそんなに難しくなかった。

しかし Show More / Show Less の挙動とかうまくいかないところとかあってなかなか難しい…。

EdTech Engineer Meetup #1 で「学校の iOS 端末事情」について話した #edtech_ja

Education×Technolog 業界の3社 Classi, LITALICO, Studyplus が集まって会社紹介やらパネルディスカッションやら LT やら懇親会しましょう、という会で LT してきた。

edtechem.connpass.com

Lightning Talks

教育業界、というか BtoB 業界のモバイルアプリは辛い話が結構あって、その 1 つに MDM がある。

やっぱり他の会社の人も同じ悩みを抱えている人がいて「その辛さわかる…」と言ってもらえて、共感してもらえたっぽくて嬉しい。

「ウチはめっちゃテストして安定している Version を MDM で提供するようにしている」という話を聞いて、うちも MDM 提供バージョンは固定したほうがいいのかもなあと思った。

今回は MDM によるアプリバージョン固定化問題について話したけど、他にも

  • 端末共有問題
    • 複数人の生徒が1台のデバイスを使う
    • 端末使い終わった後にログアウトし忘れると…?
    • Keychain Sharing しちゃうと、意図しないユーザでログインしてしまう
  • MDMSafari 無効化しているはずなのに Safari 使えちゃう問題
  • 強制アップデートがめっちゃ大変

など、 toC だとあまり考えなくて良いことが toB だと大きな障壁になるケースがあり、なかなか大変な業界である。

勉強会それ自体について

開始が 18:20 予定だったのが、実際には 18:40 開始になった。人の入りがあまりよくなかったので開始を遅らせたらしいが、特にアナウンスなどはなくただ gdgd と開始が遅れているように見えて、参加者だったら印象良くないだろうなあと思っていた。開始遅れるならそのタイミングでアナウンス入れたり、画面に理由を表示したりとかしたほうが良さそうに思った。

また、勉強会の運営側を初めて目の当たりにしたが、とても大変そうだった。昨今騒がれている勉強会をドタキャンする人は運営側になったことがないのだろう。
自分は LT 参加者の軽い気持ちで参加してしまったのであまり貢献できなかった、もうちょっと能動的に動ければよかったと反省である。

しかし懇親会は盛り上がっていた? ように見えたので、終わりよければ全てよし。