iOSDC2021に参加しました
去年に引き続きオンラインでの開催ということで、毎年恒例の雨でも影響ないのが良いところですね。
前夜祭には参加できなかったのですが、本編2日間楽しかったです。
セッションはどれも気になる内容ばかりで選ぶのが大変でした。
無料ユーザーでもタイムシフト予約できるようにしていただいており、後から見返すことができてありがたいです。
全部面白かったけど、特に印象に残ったセッションをメモしておきます。
機能ごとに動作するミニアプリでプレビューサイクルを爆速にした話
大規模なアプリのマルチモジュール構成の実践
いろいろなところで見るクックパッドさんのマルチモジュールのお話。
モジュール分割の技術的な面だけでなく、社内に浸透させるための取組みのお話が大変よかったです。
開発系の話にインセンティブという概念が出てくるのが印象的でした。
Discordもよかった。
Swift Package中心のプロジェクト構成とその実践
このセッションは本当によかったです。
前提知識、用語、概念の説明にかなりの時間を割いていて、とても丁寧な発表だったと思います。
パッケージマネージャーを活用してプロジェクトファイルのスリム化、さらにマルチモジュールのミニアプリ化、マルチプロジェクトによる環境の切り替えに繋がっていくところは目から鱗が落ちまくりでした。
SPMを勉強しつつ、モジュール分割できるように疎結合な状態にしていきたでいです。
アンカンファレンス
14年もののObjective-Cのコードを動かそうという試み。
当時のお話、懐かしい古いiPhoneの画面、エスパーのような修正箇所の指摘などなど、大いに盛り上がって見ていて楽しかったです。
最後、きっちり動いて終わったのが本当によかった。
iOSDCチャレンジ
毎度お馴染みトークン探し。
パンフレットだけでもコンプリートしようと目を皿のようにして眺めていましたが、その他ページがどうしても100%にならずタイムアップ。
トークン見つからんし、ややフライングだけどもういいじゃろ #iosdc pic.twitter.com/P7ARyWD975
— Yuki Sumida (@y_sumida) 2021年9月19日
ところがクロージング眺めてたら、なんと88位でキリ番という奇跡。ありがとうございました。
また来年!
スタッフの皆さん、参加者の皆さん、おつかれさまでした。
来年はオフライン開催できるといいなあ。
iOSDC Japan 2019 に行ってきた
9/6,7 に開催された iOSDC 2019 に参加してきました。
印象に残ったセッション
参加したセッション、LTすべて楽しかったです。
毎度のことながら、全部参加できたらいいのにという状態でした。
参加できなかったセッションについても、動画公開されたら見てみようと思います。
以下、印象に残ったセッションです。
ライブラリのインポートとリンクの仕組み完全解説
TrackAの大きなホールなのに立ち見が出るほどの盛況でした。
基礎的なところから丁寧な説明でとてもわかりやすかったです。
問題の切り分けフローはとても役に立ちそうです。
時間の関係で省略された応用編もどこかで聞いてみたいですね。
実機の管理とおさらば!AWS Device FarmでiOSのテストをしよう!
こちらも立ち見が出るほど大盛況のセッションでした。
AWS Device Farmの紹介と思いきや、XCTestを使ったUnitTest、UITestの基礎から始まって、AWS Device Farmでのテスト、CI/CDツールとの連携まで盛りだくさんな内容でした。
iOSアプリのテスト書いたことない、難しそうみたいに思ってる人は、この資料とセッション動画を見てみることをおすすめします。
Ask the Speaker でお話しできたのもよかったです。
iOSアプリのリジェクトリスクを早期に発見するための取り組み
QAチームが、リジェクト可能性を事前にできる限り潰しているのは強いなあと思いました。
ipaファイルの中身をチェックするツールを作るという発想が素晴らしい。
オープンソースにならないかなー。
SOLID原則を生活に適用する
人生設計にもSOLIDを適用できるのではというお話。
"疎結合な人生" で笑ってしまいました。
個人的にはLが一番むずかしいと思います。
テストケースで Ambiguous Layout を発見する
https://www.icloud.com/keynote/0nUzX497oPS1WvIIOwV1MODLg#iOSDC2019
テストでAmbiguous Layoutが検知できるライブラリ XCTAssertAutolayout がエラーも見やすくてとても良さそうでした。
突然低レベルプログラミングに突入していくのが面白かったです。
ご本人にとっては不本意かもしれませんが、デモ中に発生したアクシデントにも冷静に対処されていて、ライブデバッギングを見ることができたのも良かったです。
自作して理解するリアクティブプログラミングフレームワーク
https://fortee.jp/iosdc-japan-2019/proposal/a4e78fe8-6adb-4a92-a232-256ac0fa7976
RxSwiftを車輪の再発明することでリアクティブプログラミングを理解するというセッション。
会場のツイートがスライド上に流れていくまさに双方向なセッションでした。
RxSwiftもどきを作るために必要なTODOを順に潰していく、TDDのお手本のような進め方を動画で見せるという形式がとてもよかったです。
その他
セッションの合間にスポンサーブースを周りました。
WantedlyさんのブースではSwiftクイズを解いてSwiftUI本をゲットできてよかったです。
来年はスポンサー参加したいですね。
感想
今年もめちゃくちゃ楽しかったです。
印象に残った良かった点をいくつか上げてみます。
セッションの予約がよかった
今回初の試みとしてセッションの予約(サポーターorジョーカーのみ)ができました。
予約なのでギリギリに入室しても全て最前列で聞くことができて満足度が高かったです。
反面、立ち見が出るような状況において、予約席に空きがあったりしたのはもったいないなあという感じでした。
運営がスムーズで素晴らしい
相変わらず運営スタッフの練度が高く、とにかく運営がスムーズな印象でした
何かあったらすぐ対応してもらえる安心感。
ネットワークが素晴らしかった
短時間つながらないタイミングなどありましたが、概ね快適なネットワークでした。
1000人規模のイベントでこれはすごいことなのでは。
最終日のLTもおもしろかったです。
おまけ
2日間子どもたちの面倒を見てくれて、快く送り出してくれた妻に感謝。
妻にこの写真見せたらビアバー行ってきた?って聞かれた。 https://t.co/9QfPZkTJpR
— Yuki Sumida (@y_sumida) September 8, 2019
2018年振り返り
今年も無事終わってよかった。
お仕事
- 年明けくらいからiOSチームのリーダー&Androidチームのタスク管理とかするようになった
- 自分でもコード書くけど、若者が手を動かしやすいように雪かき業とか進んでやってる
- 何度かのリリースを無事に終えられて良かった
- 仕様書をgithubで管理したり、振り返りをするようにしたり、開発プロセスを少しずつ改善中
- iOSチームは定期的にモブプロするようになった
- 英文メールでやりとりしてBitriseの訪問受けたのは面白かった
子育て
- 4月から妻が育休からフルタイムの仕事に復帰した
- 思ってた以上に時間が足りないけど、リモートワークとかの制度を使ってなんとかやり繰りしている
- フルタイム共働きで子ども二人を育てるエンジニアのタイムマネジメント術 #Zaim
勉強とか
- iOSDC他、iOS関連の勉強会にいくつか参加した
- 渋谷開催だと会社帰りに参加できて便利
- 本は7冊読了、読みかけが2冊くらい。
- 『Go言語で作るインタプリタ』、『エンジニアリング組織論への招待』が面白かった
- 作者: Thorsten Ball,設樂洋爾
- 出版社/メーカー: オライリージャパン
- 発売日: 2018/06/16
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
エンジニアリング組織論への招待 ~不確実性に向き合う思考と組織のリファクタリング
- 作者: 広木大地
- 出版社/メーカー: 技術評論社
- 発売日: 2018/02/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
- 英語勉強しようと思ってスタディサプリを始めた
- 草は去年より多いけど、後半やっぱり少なくなりがち
来年の目標
- 毎日少しでも本読んだりコード書いたりする
- 業務プロセスとかいい感じに改善して、サービスをもっと良くしていきたい
ちょっと目標が低い気がするけど、子育てメインでまとまった時間とれないので、こんなもんで。
UICollectionViewのスクロールバーがセクションヘッダに隠れないようにする
環境
iOS12.0
Xcode Version 10.0 (10A255)
Swift4.2
問題
UICollectionViewのセクションヘッダを表示する必要があって、実装してみたらスクロールバーがセクションヘッダの下に隠れてしまったのでなんとかしたかった。
解決策
UICollectionViewDelegateの collectionView(_:willDisplaySupplementaryView:forElementKind:at:)
で zPosition を指定することで解消できた。
extension ViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) { view.layer.zPosition = 0.0 } }
リポジトリ
GitHub - y-sumida/UICollectionViewHeaderFooterSample: UICollectionViewにヘッダ・フッタを表示する
慣れてないのもあるけど、UITableViewに比べてUICollectionViewは癖が強い気がする。
参考
UIPageControllerの選択しているドットを大きくする
環境
iOS12.0
Xcode Version 10.0 (10A255)
Swift4.2
やりたいこと
こんな感じにUIPageControllerの選択中のドットを大きくしたい
カスタマイズしたUIPageCotrollクラス
class CustomPageControl: UIPageControl { private static let defaultDotSize: CGFloat = 7.0 // UIPageControllerのドットのサイズ private lazy var currentPageIndicator: UIImage = UIImage.dotImage(color: .gray, size: CGSize(width: currentPageIndicatorSize, height: currentPageIndicatorSize)) private lazy var pageIndicator: UIImage = UIImage.dotImage(color: .gray, size: CGSize(width: pageIndicatorSize, height: pageIndicatorSize)) private lazy var currentPageIndicatorOffset: CGFloat = calcIndicatorOffset(size: CustomPageControl.defaultDotSize) private lazy var pageIndicatorOffset: CGFloat = calcIndicatorOffset(size: CustomPageControl.defaultDotSize) var currentPageIndicatorSize: CGFloat = defaultDotSize { didSet { currentPageIndicatorOffset = calcIndicatorOffset(size: currentPageIndicatorSize) currentPageIndicator = UIImage.dotImage(color: currentPageIndicatorTintColor ?? .gray, size: CGSize(width: currentPageIndicatorSize, height: currentPageIndicatorSize)) updateDots() } } var pageIndicatorSize: CGFloat = defaultDotSize { didSet { pageIndicatorOffset = calcIndicatorOffset(size: pageIndicatorSize) pageIndicator = UIImage.dotImage(color: pageIndicatorTintColor ?? .gray, size: CGSize(width: pageIndicatorSize, height: pageIndicatorSize)) updateDots() } } override var numberOfPages: Int { didSet { updateDots() } } override var currentPage: Int { didSet { updateDots() } } override var currentPageIndicatorTintColor: UIColor? { didSet { if let color = currentPageIndicatorTintColor { currentPageIndicator = UIImage.dotImage(color: color, size: CGSize(width: currentPageIndicatorSize, height: currentPageIndicatorSize)) } } } override var pageIndicatorTintColor: UIColor? { didSet { if let color = pageIndicatorTintColor { pageIndicator = UIImage.dotImage(color: color, size: CGSize(width: pageIndicatorSize, height: pageIndicatorSize)) } } } override func awakeFromNib() { super.awakeFromNib() clipsToBounds = false updateDots() addTarget(self, action: #selector(didChangeValue(sender:)), for: .valueChanged) } } extension CustomPageControl { private func updateDots() { var i = 0 let currentPageIndicatorRect = CGRect(x: 0, y: 0, width: currentPageIndicatorSize, height: currentPageIndicatorSize) let pageIndicatorRect = CGRect(x: 0, y: 0, width: pageIndicatorSize, height: pageIndicatorSize) for view in subviews { if let imageView = imageForSubview(view) { if i == currentPage { imageView.image = currentPageIndicator imageView.frame = currentPageIndicatorRect imageView.frame.origin.y = imageView.frame.origin.y - currentPageIndicatorOffset imageView.frame.origin.x = imageView.frame.origin.x - currentPageIndicatorOffset } else { imageView.image = pageIndicator imageView.frame = pageIndicatorRect imageView.frame.origin.y = imageView.frame.origin.y - pageIndicatorOffset imageView.frame.origin.x = imageView.frame.origin.x - pageIndicatorOffset } i += 1 } else { var dotImage = pageIndicator if i == currentPage { dotImage = currentPageIndicator } view.clipsToBounds = false let addedImageView: UIImageView = UIImageView(image: dotImage) if dotImage == currentPageIndicator { addedImageView.frame = currentPageIndicatorRect addedImageView.frame.origin.y = addedImageView.frame.origin.y - currentPageIndicatorOffset addedImageView.frame.origin.x = addedImageView.frame.origin.x - currentPageIndicatorOffset } else { addedImageView.frame.origin.y = addedImageView.frame.origin.y - pageIndicatorOffset addedImageView.frame.origin.x = addedImageView.frame.origin.x - pageIndicatorOffset } view.addSubview(addedImageView) i += 1 } } } private func imageForSubview(_ view:UIView) -> UIImageView? { var dot: UIImageView? if let dotImageView = view as? UIImageView { dot = dotImageView } else { for foundView in view.subviews { if let imageView = foundView as? UIImageView { dot = imageView break } } } return dot } private func calcIndicatorOffset(size: CGFloat) -> CGFloat { return abs(size - CustomPageControl.defaultDotSize) / 2 } @objc private func didChangeValue(sender: UIPageControl){ currentPage = sender.currentPage } }
使い方
- StoryBoard上でUIPageController置いてこのクラスを指定する。コード上で生成も可。
viewDidLoad()
とかで、currentPageIndicatorSize
でカレントのドットサイズを指定する- カレント以外のドットのサイズも変えたい場合は、
pageIndicatorSize
を指定する
ポイント
- ドット用のUIImageを用意して、カレントページが変わるたびに差し替え
- 塗りつぶした画像生成用のextensionをUIImageに生やしてます
- ドットのサイズに合わせて位置調整をして、元のドットの中心と合わせるようにした
- 単純にサイズ変えてるサンプルが多かった
- タップアクションも忘れずに受けるようにした
- けど、このやり方でいいのかは不明
ウォークスルーの話のついでにUIPageControlはタップのアクションを受けることをみんな忘れてるので思い出してほしい。
— kishikawa katsumi (@k_katsumi) 2018年10月6日
ドットをタップしたらページも動かさないとドットだけ変化するカッコ悪い見た目になります。要するに双方向にバインドする必要がある。https://t.co/sdOoCba6ks
その他
- UIPageControllerをカスタマイズできるライブラリの多くは、UIPageControllerのカスタマイズじゃなくて独自のクラスで実現していた
- でもドットの大きさかえるためだけにライブラリ入れたくないので良し
リポジトリ
GitHub - y-sumida/CustomDotPageControlSample: UIPageControlのドットの色やサイズを変えるサンプル
参考
ナビゲーションのタイトルカラーを画面ごとに変更する
バージョン
- Xcode Version 10.0 (10A255)
- Swift4.2
やりたいこと
- 最初の画面はタイトルカラー赤
- 遷移先の画面はタイトルカラー青
実装1
ググるととよく出てくるやつ。
ただ、これだと最初の画面に戻った時に青のままです。
遷移元ViewController
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() title = "ViewController1" navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.red] } // 省略 }
遷移先ViewController
class ViewController2: UIViewController { override func viewDidLoad() { super.viewDidLoad() title = "ViewController2" navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.blue] } // 省略 }
実装2
同僚氏に教えてもらいました。
遷移先の willMove(toParent:)
で元の色に戻してやれば、最初の画面に戻った時に赤に戻ります。
遷移元ViewController
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() title = "ViewController1" navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.red] } // 省略 }
遷移先ViewController
class ViewController2: UIViewController { override func viewDidLoad() { super.viewDidLoad() title = "ViewController2" navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.blue] } override func willMove(toParent parent: UIViewController?) { super.willMove(toParent: parent) navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.blue] } // 省略 }
他にもいろいろ試行錯誤したけど、これが一番まともでした。
ほんとは遷移してきた時点で前の画面のタイトルカラーを保存して戻してやりたかったんだけど、遷移してきたときには nil
っぽかった。
なんかいい方法知ってたら教えてください。
試行錯誤の残骸です。
GitHub - y-sumida/NavigationTitleColorSample: ナビゲーションバーのタイトルカラーをページごとに変更する
Swiftのランタイムエラーを捕捉する
アプリがクラッシュした際に、ちょっとした後片付け的な処理をしたくて調べてた。
バージョン
- Xcode 9.4
- Swift4.1
NSSetUncaughtExceptionHandler
ググると、これがよく引っかかる。
NSSetUncaughtExceptionHandler(_:) - Foundation | Apple Developer Documentation
ただ、NSExceptionは捕捉できるけどSwiftランタイムエラーは捕捉できないっぽい。
ios - How to catch a Swift crash and do some logging - Stack Overflow
あと、うまく実装しないとCrashlyticsとかのハンドラーとかち合うみたい。
Signal
Stack Overflow でみつけたやつ。
Appleのアーカイブっぽいところに少しドキュメントがあった。
Technical Note TN2151: Understanding and Analyzing Application Crash Reports
この辺の記事も参考になった。 qiita.com
let SignalHandler : @convention(c) (Int32) -> Void = { (signal) -> Void in let log = "signal \(signal)" UserDefaults.standard.setValue(log, forKey: "signal1") sleep(1) exit(EXIT_FAILURE) } signal(SIGABRT, SignalHandler) signal(SIGILL, SignalHandler) signal(SIGSEGV, SignalHandler) signal(SIGFPE, SignalHandler) signal(SIGBUS, SignalHandler) signal(SIGPIPE, SignalHandler) signal(SIGTRAP, SignalHandler)
こんな感じでOSのシグナルをハンドリングできる。
Swiftというよりシステムプログラミングっぽくなった。
結局、今回はアプリがバックグラウンドに行くときと、フォアグラウンドへの復帰時などの処理を見直して対応した。