【オンラインMeetup イベントレポート】After iOSDC Japan 2021

f:id:ikenyal:20211004081817p:plain

こんにちは、ZOZO CTOブロックの池田(@ikenyal)です。

ZOZOでは、10/6にAfter iOSDC Japan 2021を開催しました。 zozotech-inc.connpass.com

本イベントでは、ZOZO、Mobility Technologies、Sansanの3社による合同イベントです。9/17-19に開催されたiOSDC Japan 2021のスポンサーである3社からエンジニアが集まり、各社の社員によるiOS関連技術のLTと、iOSDC Japan 2021イベントを振り返るパネルディスカッションを行いました。本イベントには、ZOZOの技術顧問でもある岸川氏も登壇しました。

登壇内容 まとめ

ZOZO、Mobility Technologies、Sansanよりそれぞれ1名ずつ、合計3名がLTで登壇し、ZOZO 技術顧問の岸川 克己氏が特別講演を、その後パネルディスカッションも実施されました。

  • LT1「CompositionalLayoutは銀の弾丸となるのか!?実際に導入してみて得た知見、全て公開しちゃいます」 (ZOZO / 小松 悟)
  • LT2「機械的なコーディングの自動化」 (Mobility Technologies / 今入 庸介)
  • LT3「【TCA】書きやすくて分かりやすい!Reducerのテストの基本」 (Sansan / 池端 貴恵)
  • 特別講演「GitHub Actionsでテストの結果をわかりやすく表示する」 (ZOZO 技術顧問 / 岸川 克己)
  • パネルディスカッション (モデレーター: Mobility Technologies / 日浅 貴啓、パネラー: ZOZO / 坂倉 勉・Mobility Technologies /古屋 広二・Sansan / 相川 健太)





最後に

ZOZOでは、プロダクト開発以外にも、今回のようなイベントの開催など、外部への発信も積極的に取り組んでいます。

一緒にサービスを作り上げてくれる方はもちろん、エンジニアの技術力向上や外部発信にも興味のある方を募集中です。 ご興味のある方は、以下のリンクからぜひご応募ください!

corp.zozo.com

「その課題解決はユーザー体験向上に寄与する?」ボトムアップからの改善提案の工夫

ogp

はじめに

こんにちは、ZOZOTOWN開発本部でZOZOTOWN iOSアプリを開発している松井です。

ZOZOでは、お客様がファッションを心から楽しむことができるよう、日々さまざまな新規案件に取り組んでいます。最近では、ZOZOTOWNの全体的な大幅リニューアルや、ZOZOGLASSのリリースなどが記憶に新しいのではないでしょうか。まだ試していない方はぜひ使ってみてください!

新規の開発案件だけではなく、お客様が使いやすいアプリをリリースし続けていくために取り組むべきことがあります。それは既存のアプリを見直し、お客様の声に耳を傾けたり、数値を見たりしてより良くしていく活動です。 uriagekouken その活動の一環として、毎週行われるアプリ部の定例で誰でも改善提案をできる時間がとられています。提案書のフォーマットも用意されており、社員の意見を積極的に取り入れようとする仕組みが整っています。そのため、社員の意見をスピーディに上へあげることが可能です。多くの社員がZOZOTOWNの改善に取り組んでいる中で、iOSチームでも「改善できるところを見つけ、提案・開発・計測をしていくプロジェクト」を実践しています。

本記事では、そのプロジェクトにおいて限られたリソースの中でどのように課題を見極め改善提案を行っているか、その工夫点と成果をお伝えします。

ボトムアップで改善提案を行う理由

いきなりですが、エンジニアが売上貢献するにはどんな道があるでしょうか。まず最初に思いつくのは、会社の企画部署や上層部が意思決定した新規開発をガンガン進めるという道です。しかし、それ以外にもエンジニアが売上貢献できる道はありそうです。

大きなインパクトを与える新規案件の開発だけに集中してしまうと、お客様がアプリを使っていて感じる不満や改善点が取りこぼされていきます。しかし、iOSチームという小さなスコープで取り組むことによって、そうした小さな部分にも目を向けることができます。わたしたちは数値データだけでなく、お客様からの問い合わせの声にもひとつひとつ目を通しています。そこから課題を見つけ、改善提案をすることでお客様により良い体験を提供するよう努めています。そのような小さな改善の積み上げによってお客様の体験を向上させることは、ZOZOTOWNに愛着を持って長く使い続けてもらうことに繋がり、結果として売上にも貢献すると考えています。

小さな改善への取り組み方

flow 実際に、上記の流れで取り組んでいます。

課題を明確にし、原因を分析、データを元に仮説を立てて、提案する。

特に目新しいことはしていないことに気付くでしょう。流れとしては一般的ですが、その中でも「課題の深掘り」と「調査結果を元に仮説を立てる」に特に注力しています。

課題の深掘り

flow_fukabori 課題が発見されたら、すぐに解決策のアイデアを出したくなります。本記事に興味のある読者の方は、特に改善というキーワードに感度が高く、賛同いただける方も多いでしょう。普段アプリを使っていて「ここ改善したいな」と思うところは容易にいくつも思いつくのではないでしょうか。わたしたちもそうでした。しかし、それを片っ端から改善していく訳にもいきません。わたしたちは「改善提案専門部隊」ではありません。他の案件も並行で進めながら、サービスに寄与する効果をあげる必要があります。課題を思いついたときに、それが本質的な課題なのか深掘ることで、限られたリソースで最大の効果を発揮できる選択が可能になります。

fukabori

1つ具体例を交えてお話します。「お気に入り画面にカートボタンを置く」という改善提案についてです。なお、この施策は既にリリースされ、数値の改善も見られたため、詳細は事例紹介として後述します。

cart

改善前は、お気に入り画面で、特定の商品の値段部分をタップするとその商品をカートに投入できました。しかし、「値段をタップしたら商品がカートに投入される」という挙動は、お客様が想定することは難しいでしょう。多くのお客様は、その挙動に気付くことができません。「お気に入り画面から直接カートに入れる機能が気付きにくい」という課題が出てきたことで、「じゃあ、気付かれるようにしよう!」というアプローチのアイデアを出したくなります。しかし、そのアプローチは本当に正しいのでしょうか。もしかしたら、そのアプローチが間違っていることもあるでしょう。実はこの機能自体に需要がなく、そもそも無くしても良いものかもしれません。その場合、気付かれるようにしたところでユーザー体験は向上しません。改善案のリリース後、確度高く結果を得るためにも、実装前に一度思いついた課題を深掘ることを仕組み化することが重要です。 gutairei そこで、「そもそもこの機能に気付いて使っているお客様はどれくらいいるのか」「機能自体、本当に必要なのか」「どの画面からのカート投入率が高いのか」を、Google Analyticsを使って集計しました。必要な数値が収集できていない場合は、A/Bテストを実施するのもひとつの手です。その結果、想定よりも使っているお客様が多く、必要な機能だということがわかりました。しかし、そもそも気付きにくいという課題は依然として残るので、「わかりやすくする」ことで必要なお客様にもっと使っていただけるであろうという仮説のもと、「カートに入れる」と明記する提案をしました。

なお、冒頭で「改善提案をすることで売上貢献する」という話をしました。「この改善は他の画面からのカート投入率を奪うものであって、本質的にはカート投入率が増える改善ではないのでは」と思った方もいるかもしれません。この点は、ユーザビリティを良くしていくことにより、「ZOZOTOWNは使いやすいからまた使おう」と思ってもらえる機会を増やしていき、結果として売上拡大に貢献できるような仕組みです。

調査結果を元に仮説を立てる

kasetsu 「仮説を立てる」ことにこだわることで、説得力が高まり、アイデアが案件として採用されやすくなります。また、仮説を立てられるかどうかによって、取り組むべき課題のフィルター効果が期待できます。仮説を立てようとすると、その仮説を担保するだけの根拠が必要になります。そのため、提案に説得力が生まれ、仮説を検証するための有効な課題解決策がおのずと湧いてきます。逆に仮説が立てられないのであれば、設定している課題が間違っている可能性があります。その場合は、課題の深掘りサイクルに戻り、本質的な課題は何であったかを見直します。改善提案だけに時間を割ける環境ではないことがほとんどだと思うので、少ない時間で最大限の改善をするためにも、「仮説を立てることができるか」という視点を大事にしています。

チーム内レビュー

teamreview 提案書を作成したあとに、iOSチーム内でレビューの時間を必ず設けています。この段階で根拠が足りていなかった点や他に解決できそうなアイデアがないか、など意見をもらっています。上に提案を持っていく前に複数人の目を通すことで、新たな気付きを得られることも多く、より筋の通った改善提案にブラッシュアップできます。

取り組みの流れは以上です。エンジニアからも売上に貢献することは充分に可能ですし、エンジニアの視点だからこそ気付く部分や、出てくるアイデアもあります。改善提案を行う上で、少しでも参考になる部分があれば幸いです。

事例紹介

さいごに、上記の取り組みを全て改善提案のフローに適用し、リリースまで行った案件をご紹介します。他にもいくつもの提案がありますが、本記事では2つ具体例として挙げます。

お気に入り画面にカートボタンを置くことでカート投入率が向上

cart

カートボタンの設置に伴い、値段部分のタップでカートに入れられる導線は削除しています。お気に入り画面からのカート投入率の計測によって、検証をしました。その結果、リリース直後から数値が向上したことから、この機能の存在に気付き、使っていただけるお客様が増えたと言えます。

ホーム画面のモジュールを2段表示にすることで商品詳細画面への遷移率が向上

home

ホーム画面から商品詳細画面への遷移率を向上させるための改善です。

商品詳細画面への遷移率を上げるということは、お客様に商品のお気に入りや、カートへ入れてもらう機会を増やすことに直結します。例えば実店舗だとしても、まず服を手にとって見てもらうことが大事になってきますよね。こちらはA/Bテストでの検証の結果、正式にリリースすることとなりました。Androidアプリでは先行して既にリリースされており、iOSアプリも現在開発中です。 # おわりに 本記事では、iOSエンジニアが主体的に行っている改善提案のフローをご紹介しました。こうした改善にも興味あるよという方は、ぜひ面談してみませんか。ZOZOでは、これからもこうした活動を通してお客様により良い体験を提供し、売上の最大化を目指すことでサービスにとってプラスとなるよう努めていきます。 ZOZOではiOSエンジニアを大募集中ですのでご興味のある方はこちらからご応募ください。

hrmos.co

iOSDC Japan 2021に7名のエンジニアが登壇しました

こんにちは、ZOZOTOWN開発本部の名取(@ahiru___z)と計測プラットフォーム開発本部の寺田(@tama_Ud)です。先日、9/17から9/19までの3日間に渡ってiOSDC Japan 2021が開催されました。例年通り素晴らしい発表が盛り沢山でしたね! iosdc.jp

昨年に引き続きオンライン開催となりましたが、Discordを使ったAsk the Speakerやニコニコ動画の弾幕などリアルタイム性のあるコミュニケーション手段が今年も充実しており非常に楽しく学びの多い3日間となりました。今年はアンカンファレンスという新しい取り組みもあり、例年以上に楽しいイベントだったと実感しています。

弊社は今年もスポンサーとして協賛し、7名のエンジニアがスピーカーとして登壇、2名のエンジニアが原稿を寄稿いたしました。本記事ではiOSDC Japan 2021で登壇・寄稿した弊社エンジニアの発表内容を、登壇者・寄稿者のコメントを添えてご紹介します。

登壇内容の紹介

「A Swift Stack Overflow」

@kapsy1312 のレギュラートークです。

スタックメモリの基本とスタックオーバーフロー現象に関する解説です。特に、 Swiftのスタックオーバーフローとその回避仕方を紹介しています。

スタックメモリは自動的に管理され、Swiftは主にヒープに割り当てられたオブジェクトを使用するため、ほとんどのプログラマはスタックメモリに関する深い知識が必要ではありません。しかし、C言語のコードと連携する場合には、スタックメモリの落とし穴がいくつか存在しているので、学ぶ価値があります。

登壇では、スタックとは何かを説明し、clangとswiftcが出力したスタックの簡単な例の紹介、Swiftでの悪いスタック使用例の紹介、スタックの問題をデバッグして回避する方法を解説しています。

fortee.jp

「iOSアプリ開発に入門して、いきなりUnity as a Libraryに挑戦してわかったこと。」

@i_kinopee のレギュラートークです。

Unity as a Libraryを活用し、3Dシミュレーションを組み込んだiOSアプリ開発に挑戦した際の内容です。

今回iOSアプリ開発自体も初挑戦であったため、学習方法や初心者でもUnity as a Libraryに挑戦可能であることをお伝えします。実際には、先人のおかげでUnity as a Libraryに関する日本語記事が充実していることもあり、大きな問題もなく実装を進められています。

ARを開発する際にも便利なUnity as a Library、ぜひ皆さんも試してみてはいかがでしょうか。

fortee.jp

「iOSではじめるWebAR 2021」

@ikkou のレギュラートークです。

昨年に引き続き、iOSにおけるWebARの最新動向を駆け足20分で紹介しました。

今年はARや関連するLiDARに触れるトークが去年よりも多かった印象ですが、WebでARというテーマは今年もまだまだニッチでした。だからこそ、日本国内のiOSエンジニアが多く集まるiOSDC Japanという場でそれを伝える意義があると思っていますし、実際に伝える場を持てて良かったです。

当日ご覧いただけなかった方は、是非スライドを覗いてWebにおけるARの現状を知ってもらえると幸いです。

fortee.jp

「未知のファイル形式をCodableで読み書きするのに役立つテクニック 『Apple Watchの文字盤ファイル』」

@banjun のレギュラートークです。

Apple Watchの文字盤ファイルを題材に、未知のファイルを解析してCodableとNSFileWrapperでMacアプリのビューアーを作っていき、そのなかで出てきた普通とは言えないCodableの対処テクニックを紹介しました。

Apple Watchユーザーではない人も聴きに来てくれたようです。Ask the Speakerでは普段使っている文字盤のタイプを教えてもらったりしましたが、やはり写真・インフォグラフ・Siriあたりが人気のようでした。Appleの言う多彩な文字盤の良さを見つけるためにも、他の文字盤を使っている人の話も聞いてみたいと思いました。

fortee.jp

「再現ができない?特定ができない?ZOZOTOWNアプリのトップクラッシュに立ち向かった話」

@chichilam86 のLTです。

メモリ不足によるクラッシュは直接の原因がログやスタックトレースに現れないので再現が容易でなく、特定困難です。そのため、リニューアル後のZOZOTOWNアプリでのトップクラッシュに対して、メモリ不足の仮説と原因の解析から検証までの流れを紹介しました。

同じ悩みをお持ちの方の参考になれば幸いです。

fortee.jp

「あなたの知らないSafariのExperimental Featuresの世界」

@ikkou のLTです。

わりとマニアックだと思っているSafariの設定、しかも普段使う分には触る必要のないExperimental Featuresについて5分でお伝えしました。

どちらかと言うとLT芸ではなく、ガチで時間いっぱいお伝えする内容でしたが、反応を伺っていると「知らなかった!」の声もあり、少なからず伝えたいことを届けられて良かったです。

fortee.jp

「作ってわかる!LiDARによるカメラの暗所オートフォーカス機能」

@tama_Ud のレギュラートークです。

2020年3月発売のiPadから搭載されたLiDARスキャナですが、AR領域で特に注目を浴びていますね。しかし、今回はARでなくLiDARのAF機能補助について焦点を当ててお話しています。

AF機能をLiDARを使って実装してみることで、LiDARを使ったアプリ開発への理解が深まれば幸いです。

fortee.jp

「SceneKitを使ってアプリのクオリティを劇的に上げる」

@ahiru___z のレギュラートークです。

UIKitだけでは実現が難しいリッチな表現を、SceneKitを使って実装する方法を紹介しました。

SceneKitは3Dコンテンツを扱うアプリ開発でのみ使用するFrameworkと思われがちですが、実際にはそんなことはありません。UIKitとの親和性は高く、使い方や概念を適切に理解することで非常に強力な武器となり得ます。私自身、個人開発でよく使用するFrameworkの1つでもあります。

興味を持った方、ぜひSceneKitを触ってみてください。

fortee.jp

「ほんの一瞬だけでもConcurrencyの計算理論に触れてみませんか?」

@banjun のLTです。

並行計算のモデルのひとつであるCCSについて、その入口を紹介しました。

おそらく事前知識のある人がほとんどいない分野だったのではないかと思いますが、その分、多くの方に概念の存在だけでも知ってもらえたら幸いです。今年のLTは、大学の講義よりも、さらに多くの人に並行計算を伝えられる最強の場だったのかもしれません。

誰も来ないことも覚悟していたAsk the Speakerですが、このLTのきっかけや参考文献の話をしたり、「コルーチンとインターリーブは似ているのでは?」「π計算との違いは?」など、ディープな話もでき、このネタでLTしておいて良かったと感じました。

fortee.jp

寄稿内容の紹介

「誰も知らないASO(App Store Optimization)の話」

@ahiru___z の寄稿です。

ASOに関する原稿を寄稿しました。

個人開発のアプリで累計200万DL以上を達成しました。その過程で行った自身の取り組みを中心に、まずは手軽に誰でも始めることができる基本的な手法をまとめています。ASOは一朝一夕に効果が現れるものではありませんが地道な取り組みによってある程度の効果を得ることは間違いなく可能です。

応用的なテクニックはまた別の機会にまとめたいと思います。

誰も知らないASO(App Store Optimization)の話 拡大

「CodableでJSONのNullを出力するためのTips」

hirotakanの寄稿です。

Codable + Property WrapperのTipsを2ページの原稿で紹介しました。

実務の参考や、Codableの理解に繋がれば幸いです。今回初めての寄稿でしたが、2ページは他の募集に比べるとハードルが低いので、チャレンジしてみたいけどなかなか踏み出せない方におすすめです。

CodableでJSONのNullを出力するためのTips 拡大

CfPネタ出し会 & レビュー会

弊社では毎年自由参加でiOSDC JapanのCfPネタ出し会 & レビュー会をiOSエンジニア同士で行っています。

昨年はネタ表を利用したCfPネタの整理を実施していましたが、今年はDiscord上でネタ出し会を行いました。既にネタがある人は発表してコメントをもらい、何を発表しようか迷っている人は参加者との会話の中でネタを引き出してもらうスタイルで進行しました。6月の段階でネタ出し会を行ったため、早い段階でネタを固めることができました。

技術顧問の岸川さんには例年CfP採択後のレビュー会に参加していただいていました。しかし、今年はCfP採択前に行うレビュー会の段階で参加していただき、CfPの書き方はもちろん、どうすれば自分の発表したい内容がより適切かつ魅力的に伝わるのかなどを教えていただきました。その結果、例年以上に実りの多いレビュー会となりました。

弊社は複数の事業ドメインを有しているため、一括りに「iOSエンジニア」と言ってもそれぞれ強い分野や興味のある分野が異なります。そのためレビュー会では様々な分野の話を聞くことができてとても楽しかったです。

少し余談となりますが、最近では岸川さんとの1on1も積極的に行っており、エンジニアとしてスキルアップしていく環境が十分に整備されています。

また、弊社ではカンファレンスへの参加は業務として扱われるため、iOSDC Japanには休日出勤という形で参加しました。今年は内定者アルバイトの方も複数名イベントへ参加しましたが、社員と同様にチケット代は経費となりイベントへの参加は業務時間として扱われています。

ZOZOではiOSエンジニアを大募集中ですのでご興味のある方はこちらからご応募ください。

hrmos.co

After iOSDC Japan 2021を今年も開催

iOSDC Japanのアツい3日間が過ぎ、レポート記事を書いたので今年のiOSDC Japanは終了! ……ではありません。

今年もラップアップイベントにあたるAfter iOSDC Japan 2021をZOZO、Mobility Technologies、Sansanの3社の合同で開催します。

各社の社員によるLT、パネルディスカッションを行いますので、興味のある方はぜひご参加ください。

こんな方におすすめです。

  • iOSに関わるソフトウェアエンジニア
  • iOSDCを一緒に振り返りたい方
  • iOSDCには参加しなかったけど、情報が知りたいという方

なお、本イベントにはZOZOの技術顧問でもある岸川さんも登壇します。

iOSDC Japan 2021に参加した方もそうでない方も、みんなで振り返りましょう。イベント申し込みは以下のページからお願いします。

zozotech-inc.connpass.com

ZOZOTOWNアプリHome画面再設計の軌跡~10年以上歴史を持つアプリはどのようにして生まれ変わったのか~

ogp

はじめに

こんにちは、ZOZOアプリ部でZOZOTOWN iOSアプリを開発している小松です(@tosh_3)。

気づけば、ZOZOテクノロジーズに新卒入社して1年が過ぎていました。オフィスの近くに引っ越したのですが、オフィスに出社する前に、オフィスが移転しました。

さて突然ですが、最近ZOZOTOWNに大きな変化があったことをみなさんお気づきでしょうか。2021年3月18日よりZOZOTOWN全体が大幅リニューアルされ、コスメモールがオープンされるなどの大きな変化がありました。アプリも7.0.0とメジャーバージョンの更新を行い、ほとんど全ての画面が新デザインになりました。

そこで、本記事ではHome画面のリニューアルを担当した私が、そこで使用した技術とその背景について触れながら、ZOZOTOWN iOSアプリのHome画面リニューアルの裏側をお伝えします。

ZOZOTOWNアプリの新旧デザイン比較

ZOZOTOWNのHome画面がどのくらい変わったのか、リニューアル前後のUIを比較してみましょう。

旧デザイン 新デザイン

黒ベースから白ベースへと変化し、よりモダンなデザインになっています。今まで、1つのページで構成されていたデザインは「すべて」「シューズ」「コスメ」と3つのタブの構成になりました。

Home画面のリアーキテクチャ

この改修をするにあたり、まずHome画面の再設計をするか否かを考えました。仮に再設計せず、既存のHome画面に新しい機能を追加していく場合、後述するHome画面が抱える潜在的な課題に遭遇する可能性が高くなります。開発の終盤で大きな変更を求められる課題に直面すると、スケジュール的にも場当たりな対応になりがちです。今までのZOZOTOWNアプリ開発の中でも似たようなシチュエーションが何度かありました。

また、再設計という選択肢を取った場合、QAチームによるフルリグレッションテストが必要になります。そのため、なかなか手軽に再設計をする判断をすることは難しいです。しかし、今回のケースでは再設計の有無に関わらず、どちららの選択をしてもQAではフルリグレッションテストを行う予定でした。以上の観点も踏まえると、再設計を行うには絶好の機会だったとも言えます。そのため、既存の課題の多くを救うためにも、再設計するという選択肢を取りました。

既存のHome画面が抱えていた課題

まず、既存のHome画面が抱えていた課題を紹介します。

  • 課題1. 改修すると様々な機能が影響範囲になる
    • ViewControllerが必要以上にたくさんの責務を担っており、改修に対して影響範囲が大きくなってしまう可能性が高かった
  • 課題2. Conflictの温床だった
    • Storyboardメインの開発が行われており、現在のiOSチーム構成(8人体制)の元では、Conflictが多発する原因になっていた

HomeViewControllerは数あるVCの中でも改修が多い対象であり、実際にプロジェクト内全てのVCの中で3番目にマージされた数が多かったです。

既存の課題解決へのアプローチ

前述の課題を解決するために、それぞれ以下のアプローチを取ることにしました。

  • 課題1. 改修すると様々な機能が影響範囲になる
    • 3タブ構成になることを踏まえて、HomeViewControllerの役割を再定義する
    • 疎結合かつ役割が明確になるようにクラスを定義する
  • 課題2. Conflictの温床だった
    • Storyboardベースからコードベースへと移行する

次に、疎結合とStoryboardの使用について説明していきます。

HomeViewControllerの整理

まず、既存のHomeViewControllerが持っていた役割を整理しました。

  • Home画面コンテンツの管理
  • Home画面コンテンツのAPI通信
  • Google Analytics(以下、GA)に関する機能
  • アプリのライフサイクルに関する機能
  • Home画面のライフサイクルに関する機能

これを見る限り、今までのHomeViewControllerはHome画面としての機能だけではなく、一番最初の画面としての機能も持っていました。結果として、HomeViewControllerは1400行以上にも及ぶFatViewControllerと化していました。

また、3タブ構成(すべて、シューズ、コスメ)へ変更すると、HomeViewControllerはさらに肥大化することが明らかです。そこで、HomeViewControllerの役割を再定義し、新しく各TabのViewControllerとLoggerクラスを作成しました。

それぞれの役割は以下の通りです。

HomeViewController

  • Home画面のタブの管理
  • アプリのライフサイクルに関する機能
  • Home画面のライフサイクルに関する機能

TabViewController

Home画面に存在する3つのタブ(すべて、シューズ、コスメ)をそれぞれ管理するViewController。

  • Home画面コンテンツの管理
  • Home画面コンテンツのAPI通信

Logger

  • GAに関する機能

以上のように、それぞれのクラスに明確な役割を持たせることで、3タブ構成に変更しても今までのコード量と同等で実装できます。

Storyboardを使用することは悪なのか?

StoryboardはConflictが多発する原因となっていましたが、「Storyboardを使用することはそんなに悪いことなのか?」と言われるとそうとは思いません。コードベースでUIを書くこと、StoryboardでUIを構築していくことそれぞれに良さがあります。

  • ZOZOTOWN iOSチームは現在8人体制で開発を行っている
  • 今後もチームのスケールアップを行う可能性が高い

このような環境では、Storyboardを使用するよりも、コードベースでUIを構築していくことの方が確実に向いていました。 他の画面では、Storyboardで多数のConflictが発生し、その度に手間がかかるということが多々発生している状態でした。ZOZOTOWNは10年以上の歴史を持つアプリなので、恐らくStoryboardで開発するのが向いていた時代もあったが今は向いていない、ただそれだけのことです。今回の改修では今までHome画面全体だけではなくUICollectionViewCellまでStoryboardで構成されていたものを、Home画面のHeader部分を除き全てコードベースへと変更することに成功しました。

Home画面のリインプリメンテーション

本章では、Home画面の再設計をどのように行ったのかを紹介します。

今回のリニューアルで実装したこと

Home再設計にあたり、以下の項目に挑戦しました。

  • Sandboxの作成
    • さまざまなパターンのHome画面をすぐに確認できるため、たくさんの試行錯誤が可能に
  • CompositionalLayoutの採用
    • 新デザインに柔軟に対応するために、CompositionalLayoutを採用
    • 適切なComponentへと切り出すことに成功
  • Storyboardの削除
    • Storyboardベースで設計されていた画面をコードベースへと移行
  • HomeViewControllerの疎結合化
    • GA用のクラスを別に切り出すなど、責任過多を解消
    • 1400行から600行に

本当は全ての内容を紹介をしたいのですが、本記事では特にCompositionalLayoutについて取り上げます。

CompositionalLayoutの採用

大幅なデザイン変更を行うということは、同時にHome画面で使用している技術を刷新する機会でもあります。また、チームとしても既存の部分とうまく結合できるのであれば、積極的に新しい技術に挑戦していく方針があります。そこで、ComopositionalLayoutを使用してみることにしました。CompositionalLayoutはWWDC19で紹介された機能です。続くWWDC20でも新たにCollectionViewListが強化されるなど、Appleとしてもここ数年はCollectionViewに力を入れていると感じていたので、是非機会があれば導入してみたいと考えていました。

なお、CompositionalLayoutとは、CollectionViewのレイアウト方法の1つであり、App Storeのようにセクションごとに異なるレイアウトを簡単に組むことのできる技術です。

引用: Layouts | Apple Developer Documentation

App Storeのようなカルーセルでも、FlowLayoutのカスタマイズや、中に別のCollectionViewを置くことなく容易にレイアウトを組めます。

さて、ここまでの説明を読みながら気になっていた方もいると思いますが、CompositionalLayoutが使用可能なのはiOS 13以上です。ZOZOTOWN iOSアプリは原則として3つの最新バージョンをサポートしており、当時サポートしていたOSはiOS 12、iOS 13、iOS 14でした。そのため、iOS 12でCompositionalLayoutをどのようにして使用するか、という問題に直面しました。

調べてみると、iOS 12でもCompositionalLayoutを使用可能にする、IBPCompositionalLayoutというライブラリがありました。そして、下記3点の理由からこのライブラリの導入を決めました。

  • ライブラリの作成者が弊社の技術顧問である岸川克己さんなので、何か困ったことがあった際にはいつでも相談できる
  • iOS 13以降では、純正のCompositionalLayoutを使用しており、iOS 12を切るタイミングではほとんど労力なく切り替えることができる
  • 弊社のWEARチームでも使用しているライブラリであり、社内での利用実績がある

このライブラリをZOZOTOWNで使ってみると、contentInsetAdjustmentBehaviorの値によってはうまく動かないパターンが見つかりました。原因を特定できたので、修正のPull Requestを出したため、現在では解消されています。ちなみに、このPull Requestで、初めてOSSにコントリビューションする実績をあげることができました。

github.com

CompositionalLayoutは、まだ新しい技術ということもあり、慣れるまではどのようにレイアウトを組むのが正解なのかわかりませんでした。そこで、技術検証を十分に行い、実装を進める上で疑問が生じた際には技術顧問の岸川克己さんにも相談に乗っていただきました。

実際に使用してみて感じた、CompositionalLayoutのメリット・デメリットを下記に挙げます。

メリット

  • App Storeのようなセクションごとに異なるレイアウトを組み合わせることが簡単な記述で実装できる
  • カルーセルの動きもサポートされている
  • カルーセル用のスクロールビューを自前で置く必要がない

デメリット

  • セクション内のスクロールビューの制御が必要になることにより、実装が難しくなる
  • ライブラリを使用しない限り、iOS 12以前のOSをサポートできない
  • OSによって挙動が若干異なり、一部OSでのみ発生するバグが存在する

個人的には、 今回、新たしい技術に挑戦してみて、確かに難易度が高い部分もありましたが、とても便利な機能だと感じました。是非このようなレイアウトを組む際には、検討してみるといいでしょう。

リザルト

課題とその解決法、そして技術的なアプローチを紹介してきました。それらを利用し、冒頭で紹介したZOZOTOWNが本質的に抱えていた課題をどのように解決したのか、一度まとめておきます。

  • 課題1. 改修すると様々な機能が影響範囲になる
    • 再設計する際に、疎結合にすることを意識し、役割ごとに明確なクラス分岐を行った
    • 1400行以上あったHomeViewControllerは役割が明確になり、600行ほどへ
  • 課題2. Conflictの温床だった
    • Storyboardの使用箇所を大幅に減らし、大部分をコードベースの設計へと変更した
    • その結果、Storyboardの制限に縛られることなくコード上で柔軟な分岐をすることが可能になった

今回の修正により、今後何か改修する際には、より少ない労力、かつ小さい影響範囲で対応できるようになりました。

リキャップ

再設計方法やその考え方に決して正解はありません。しかし、今回の再設計を通じ疎結合にすること積極的に新しい技術を検討していくことの重要性を再認識しました。また、技術選定をしていく中で、チームの状況とこれからを考えるという視点を持つ重要性にも新しく気づくことができました。

最後に

ZOZOテクノロジーズでは、一緒にモダンなサービス作りをしてくれる方を募集しています。ご興味のある方は、以下のリンクから是非ご応募ください!

tech.zozo.com

AppleのデザイナーからZOZOTOWN新UIへのフィードバック - WWDC21参加レポート

ogp

こんにちは。ZOZOアプリ部の遠藤と林です。

memoji_endo_rin

日本時間の6月8日から12日にかけて開催された今年のWWDC21も、昨年と同様にオンライン開催でした。

FaceTimeの新機能であるSharePlayや、プライバシーが更に強化されたiCloud+、アプリ開発を一元管理できるようになったXcode 13など、新機能から開発環境周りまで幅広い発表が目白押しでした。

本記事ではオンライン開催が2年目になったWWDC21に対し、弊社のエンジニアが昨年の経験を活かしてどのように臨んだのか、また開催期間中の活動内容をお伝えします。今年3月に10年ぶりにリニューアルしたZOZOTOWNアプリとZOZOGLASSに対するデザインフィードバックも可能な範囲で紹介しますので、是非最後までご覧ください。

WWDCの概要

WWDC(Worldwide Developers Conference)は、Appleが年に1度開催している開発者向けのカンファレンスです。iOS、iPadOS、macOS、watchOS、tvOSのアップデートをはじめ、開発環境周りの新機能などが発表されます。また、例年通りの各種セッションやLabsに加え、今年は新たに「Digital Lounges」と「Challenges」が追加されました。それらの新しい体験を加えた形でWWDCを楽しむことができます。

昨年の経験を活かしたオンライン参加の工夫

オンライン開催のWWDCへの参加も2回目ということで、昨年の経験を活かしてより効果的な参加方法を模索しました。その結果、開催日までの事前準備や、開催期間中の情報共有も昨年よりもスムーズに行えました。ここでは、期間中の働き方や、実施した事前準備や情報共有の工夫点を紹介します。

開催期間中の働き方

弊社では海外カンファレンスへの参加が推奨・サポートされており、オンライン開催でも業務の一環として参加できます。1-on-1 Developer Labs(以下、ラボ)へ参加することもあり、今年も昨年同様、希望したメンバーは業務調整を事前に行った上で現地時間に合わせた勤務時間として参加しました。昨年の内容は以下の記事をご覧ください。 techblog.zozo.com

現地時間に合わせて参加するメンバーは、以下の点を考慮してスケジュールを組みました。

  • 現地時間に合わせた勤務時間の調整
    • 日本時間2:00〜11:00を勤務時間とする
  • 休日出勤を利用した勤務日調整
    • 最終日が日本では土曜日なので、該当の6月12日を休日出勤として、振替休日を取得する

事前準備と情報共有にMiroを活用

昨年もラボやセッションへ参加するための準備・共有は実施していました。質問したい内容をスプレッドシートで管理し、各自が参加するセッションをSlackで共有していました。しかし、昨年のやり方では情報が分散してしまうことと、情報が流れてしまうことが課題として挙がりました。そこで、今年はMiroを使用した情報の一元管理を実施しました。

Miroのボードには、ラボでの質問だけではなく、後日社内に向けて共有すべき情報を書き込めるようにしました。

WWDC21の全期間を終え、Miroにはたくさんの情報が集約されました。以下の画像は開催前と開催後のMiroのボードを比較したものです。開催後の一番大きい枠は、ラボに関する内容で、11個の質問がまとめてあります。

WWDC21開催前 WWDC21開催後
WWDC21開催前のMiro WWDC21開催後のMiro

事前準備やセッション、ラボで聞いた内容はMiroのマインドマップのテンプレートを使用して整理しました。要素を簡単に増やせ、その内容を繋げることで、あとから見たときに関連性を把握しやすくまとめることができました。

どのようにまとめたのか、その一部を紹介します。以下はラボで質問した内容をまとめた一例です。

ラボの例

予想ビンゴ!

毎年WWDCの開催前には、どのような発表がされるのかを予想し、SNS上に多数投稿されます。今年は、社内でそれを実施しました。何が発表されるのかをビンゴ形式で予想するようにし、楽しむ要素をプラスして実施しました。

ビンゴの結果を発表します。

下図の赤色の内容が正解したものです。残念ながらビンゴは成立しませんでした。しかし、「Scribbleの日本語対応」などいくつか予想が当たっている項目もありました。「iPadでXcodeが動く」についてズバリ当たりはしませんでしたが、「Swift PlaygroundsからApp Storeへアプリを公開できるようになった」ということで当たりと判定しました。

「ビンゴに書いた内容が当たるかな」とKeynoteで内容が発表される度にドキドキして楽しみながら参加できました。来年はビンゴが成立できるよう、リベンジしたいと思います。

予想ビンゴ

WWDCの新しい楽しみ方

今年のWWDCには新しく2つの要素が追加されました。追加されたこれらの要素に参加したので、その内容を紹介します。

Digital Lounges

まず1つ目は「Digital Lounges」です。こちらは、Slack上にデベロッパーツール、SwiftUI、アクセシビリティ、機械学習についてのチャンネルが用意され、そこで質問ができる仕組みでした。質問できる内容は限られていますが、ラボに行かなくてもSlack上でAppleのエンジニアとデザイナーに気軽にリアルタイムで質問できます。

そして、各チャンネルでは質問だけではなく、「Trivia Night」というクイズ大会などのイベントも開催されていました。Trivia Nightを通してAppleの歴史を知ることができ、楽しむことができるコンテンツでした。

Challenges

2つ目は「Challenges」です。Challengesというタイトルから推測できる通り、問題にチャレンジして達成できたら、その内容をApple Developer ForumsやDigital Loungesに共有して楽しむことができます。

出題された問題に、「Throwback with SwiftUI」というものがありました。これは、1984-2013年の範囲からランダムに指定された年に対し、「その年のUIっぽいもの」をSwiftUIで作るという問題です。

チャレンジしたメンバーは1984年を引いて、その年の有名なCMを再現していました。 Challenges: Throwback with SwiftUI

出題される問題には関連するセッションのリンクも付いています。セッションを見るだけではなく、実際に手を動かして問題を解く体験もセットででき、とても楽しく参加できました。出題される問題はどれも面白く、毎日追加されるので、問題の追加をワクワクしながら過ごすこともできました。

Labs & Sessions

この章では、WWDC21へ参加した社員が、それぞれ参加したラボやセッションの内容を紹介します。

Design Lab × ZOZOTOWN

memoji_rin

最初の報告はZOZOアプリ部の林がお送りします。

WWDC21では、姿を消していたDesign Labが1年ぶりに復活しました。AppleのデザイナーにリニューアルしたZOZOTOWNアプリのフィードバックを頂いたので、その一部を紹介します。

ラージタイトルに関するフィードバック

ZOZOTOWNアプリのリニューアル時にラージタイトルを導入しました。しかし、「お気に入り」ページのような上部にタブがある場合、標準のNavigationではラージタイトルを対応できないため独自で実装しました。

favorite_header

Human Interface Guidelinesにも、このパターンに関する記載がないので、Appleのデザイナーに質問してみました。そして、「タブを切り替える際、ユーザーの気が散らないようにナビゲーションバーの状態を維持すべきだ」というフィードバックを頂きました。今後、UI改善をする際の参考にしたいと思います。

ダークモードに関するフィードバック

ダークモードが普及していく中で、リニューアル後の白を基調としたZOZOTOWNアプリはダークモード対応の必要性が高くなってきます。その将来を見据えて、ダークモード対応の注意点を確認してきました。商品画像についてはダークモード用の画像を用意しなくてもいいということや、WebViewともバランスよくダークモードに合わせるべきなど、ダークモード対応について貴重なアドバイスを頂きました。

全体へのフィードバック

全体へのフィードバックとして、「ZOZOTOWNアプリは洗練されていて一貫性があるクリーンなECアプリであり、個人的にも気に入った」という嬉しいコメントを頂きました。今回の大規模なリニューアルに携わったメンバーとして、このようなコメントを頂くことができ、とてもやりがいを感じました。

Design Lab × ZOZOGLASS

memoji_sayapoco

こんにちは、ZOZOアプリ部の松井です。ZOZOTOWNでは、自宅にいながら簡単にフェイスカラー計測ができる「ZOZOGLASS(ゾゾグラス)」を3月18日にリリースしました。このZOZOGLASSを使った一連の計測フローを、より使いやすいUIにするために、Appleのデザイナーに意見を聞いてみました。

計測フローをお見せしながら説明したところ、「一連の流れに筋が通っており、とても使いやすい」と言って頂けました。「手順やコンテンツについてきちんと説明がされているし、改善点がないくらい。私も使ってみたい!」という嬉しい感想を頂けました。「強いて改善点を挙げるならば、UXの流れの中で表示されるコンテンツが、何のために表示されているのか説明があれば分かりやすい」というフィードバックを頂けました。

全体を通し、コンテンツを見たユーザー自身が何をすれば良いか理解できること、コンテンツの表示理由が明確であることを意識しているように感じました。

Design Labに過去10回以上参加している同僚によると、ほとんどの場合はフィードバックが止まらなく、時間が足りなくなるようです。ところが、それに該当せず、非常に好評だったZOZOGLASSのUI/UX。まだお使いでない方は、是非Appleのデザイナーも認めるUI/UXを堪能してみてください。

Object CaptureはECにおけるARの利活用を加速させるか?

memoji_ikkou

ARやVRといったXR領域に注力している@ikkouです。ここ数年のWWDCではAugmented Reality(AR)関連の発表も続いているため、特にAR関連セッションを注視しています。

AR関連で特に注目すべきは2D写真から3Dモデルを生成する「Object Capture」です。これはMacでフォトグラメトリを実現するものです。既にLiDARが搭載されたiPad/iPhoneを使ったものもありますが、こちらはスキャンから3Dモデルを生成する、似て非なるものです。

私は会期中にApple M1チップが搭載された私物のMacを購入し、#WWDC21Challenges のお題でもあるObject Captureを試しました。その結果、ラフに撮影したにも関わらず、思いのほか綺麗なUSDZファイルが生成されて驚きました。

これまで、フォトグラメトリには「RealityCapture」を始めとする「有償」アプリケーションの利用が一般的でした。しかし、今回発表されたObject Captureは動作するMacの機種に制限があるものの「無償」です。

RealityCaptureは建造物のような広い範囲を対象とする「広域フォトグラメトリ」にも対応しています。対してObject Captureは広域フォトグラメトリ向きではなく、必ずしも比較すべき対象ではありません。それでも特定の用途に限っては、ECにおけるARの利活用の課題として挙げられる「3Dモデルの生成」を容易にします。

ARKitによってARが身近なものになり、Object CaptureによってARで映し出す3Dモデルの生成が容易になりました。今後「ARで商品を隅々まで眺めてから購入するという買い物体験」が今まで以上に加速することは想像に難くありません。非常に楽しみです。

自前実装で悩んだ日々にさようなら、UISheetPresentationControllerで頑張らないハーフモーダル

memoji_dera

こんにちは、でらけんです。WEAR部のiOSチームで日々頑張っています。WEARアプリでは、類似画像の検索画面など、既にいくつかの画面でハーフモーダルを取り入れてきました。そのため、今回のWWDC21で、私は「Customize and resize sheets in UIKit」に釘付けでした。

実際にサンプルコードで動作を試してみたので、そこから得られた知見を紹介します。

リリースアプリで実装しているハーフモーダルは、モーダルに見立てたViewを1つ用意し、次の機能を組み合わせて動作させています。

  1. UIPanGestureRecognizer
    • パンジェスチャーによる移動量の監視
  2. UIView.transform
    • 移動量をViewの拡大・縮小へ反映
  3. UIView.animate
    • ジェスチャーを止めた際のViewの拡大・縮小のアニメーション
  4. UIGestureRecognizerDelegate
    • モーダル内のCollectionViewのスクロールとジェスチャーを同時に認識させる
    • スクロールを用いたモーダルの拡大・縮小

iOS 15からは、UISheetPresentationControllerを使用することで、1.〜3. を自前で実装することなく、ハーフモーダルを実現することが可能になりました。4. は、スクロールのタイミングでanimateChanges(_:) を使って拡大させることが可能です。こちらを使用することで、スクロールに限らずボタンをタップしたら拡大させるなど、様々なアクションと紐づけることができそうです。

まだ、一部のみを試した段階ではありますが、スワイプの制御を意識する必要がなくなるだけでも非常に負担が軽減されたと感じます。

dyld3時代でも「Static Frameworkをマージする」手法は有効性を持つか

memoji_gen

ZOZOアプリ部のげんです。ラボ(C, C++, Obj-C, compiler, analyzer, debugger, and linker lab)に参加し、「Dynamic Frameworkをstaticにビルドしてマージする手法」がdyld3時代となった現在でも有効性をもつのか、をAppleのエンジニアに確認しました。 結論は「少し効果あり」でした。

同時に「Xcodeからアプリをロードする場合はdyld2が使われる」ということと、Umbrella Frameworkに対して私はAppleのエンジニアと異なる認識を持っていることも分かりました。AppleがUmbrella Frameworkで指し示すものは「Frameworkの中にFrameworkが入っているもの」であり、「Static Frameworkをマージしたもの」ではないとのことでした。

そして、「Framework周りはストア審査の際に見られるので注意した方が良い」というアドバイスも頂きました。リジェクトされる可能性を考えると、確かに事前に注意した方が良さそうです。

テストでキーボード入力をいい感じにしたい

memoji_banjun

テックリードの@banjunです。InternationalizationとClockKitとCamera Captureのラボに行ってきました。そのうちのひとつをご紹介します。

ZOZOTOWNのテストケースではKIFを使ってタップやキーボード入力をシミュレートしていました。そのときのキーボードは目的の言語のものである必要があります。今まではテストコードということもありプライベートなAPIを活用していたのですが、最近は失敗することもあったため、ラボで聞いて解決を図ることにしました。

ラボでは、より安定しそうな公開APIの手法をいくつか提案して頂きました。例えば .asciiCapable をセットする、 insertText: で入れる、ペーストしてしまう、などです。それぞれの不利な点もあるそうですが、必要なシミュレートの粒度に応じて適切なものを選択できます。これでUIなんとかImplクラスを直接触らなくて済む日が来るかもしれません。

まとめ

以上、WWDC21の参加レポートでした。

カンファレンスのオンライン開催が当たり前の時代になっていることを実感できたWWDCでしたね。「Digital Lounges」や「Challenges」など新しい仕組みを導入して、Appleはオンライン開催でも開発者がより情報をキャッチアップできるような試みを実施していました。弊社も昨年のオンライン参加の経験を活かして、Miroなどの活用で情報共有がよりスムーズにできました。

WWDC21の最後に、Appleから「2年連続でオンライン開催を実施してみたがどうだったか」「オフラインイベントに参加したいのか」のアンケートがありました。みなさんはいかがですか?

さいごに

ZOZOテクノロジーズでは、一緒にモダンなサービス作りをしてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください!

tech.zozo.com

【オンラインMeetup イベントレポート】After iOSDC Japan 2020

f:id:ikenyal:20200902183707j:plain

こんにちは、ZOZOテクノロジーズ CTO室の池田(@ikenyal)です。

ZOZOテクノロジーズでは、9/29にAfter iOSDC Japan 2020を開催しました。 zozotech-inc.connpass.com

本イベントは、Sansan、note、ZOZOテクノロジーズの3社による合同イベントです。9月19日〜9月21日に開催されたiOSDC Japan 2020について、各社の社員によるLT、パネルディスカッションを行いました。本イベントには、ZOZOテクノロジーズの技術顧問でもある岸川氏も登壇しました。

登壇内容 まとめ

Sansan、note、ZOZOテクノロジーズよりそれぞれ1名ずつ、合計3名がLTで登壇し、ZOZOテクノロジーズ 技術顧問の岸川 克己氏が特別講演を、その後パネルディスカッションも実施されました。

  • LT1:iOSアプリの起動時間短縮にむけて (株式会社ZOZOテクノロジーズ 元 政燮)
  • LT2:Firebase In-App Messaging で過去バージョンのユーザーへ更新を促したい! (Sansan 中川 泰夫 / @ynakagawa33)
  • LT3:note社でのMagic Pod活用事例 (note 植岡 和哉 / @fromkk)
  • 特別講演: SourceKit-LSPを使ってWebブラウザでSwiftの入力補完を実現する (ZOZOテクノロジーズ 技術顧問 岸川 克己 / @k_katsumi)
  • パネルディスカッション: 『初のオンライン iOSDC、どうでしたか?』 (モデレーター: note / 森口 友也、パネラー: Sansan / 栗山 徹(@kotetu)・note / 植岡 和哉・ZOZOテクノロジーズ / 西山 博貴)

最後に

ZOZOテクノロジーズでは、プロダクト開発以外にも、今回のようなイベントの開催など、外部への発信も積極的に取り組んでいます。

一緒にサービスを作り上げてくれる方はもちろん、エンジニアの技術力向上や外部発信にも興味のある方を募集中です。 ご興味のある方は、以下のリンクからぜひご応募ください!

tech.zozo.com

登壇者を5倍に増やした秘策 - iOSDC Japan 2020に向けたZOZOテクノロジーズの取り組み

image

こんにちは! ZOZOTOWNのiOSアプリ開発をしている林と松井です。先日、9/19から9/21までの3日間iOSDC Japan 2020が開催されました。

ブログを書くまでがiOSDC!#didyoublog?

今年はコロナ禍でオンライン開催となり、現地の盛り上がりを体感できませんでしたが、ニコニコ生放送の弾幕などオンラインならではの楽しみがありましたね。また、例年通り素晴らしい発表が盛り沢山でした!

オンライン開催においても弊社はスポンサーとして協賛し、10名を超えるメンバーが参加しています。うち5名はスピーカーとしての参加でした。この記事ではiOSDC Japan 2020で登壇するために行った弊社の取り組みと、登壇した社員の発表をご紹介いたします。

採択率向上と登壇内容ブラッシュアップのための取り組み

iOSDC Japan 2019ではZOZOテクノロジーズの登壇者が1名のみでしたが、今年はなんと5名も採択されました! 担当しているプロダクトとしてはZOZOTOWNから4名、WEARから1名が登壇しました。今回はZOZOTOWN iOSチームからの採択率を大幅に向上させた取り組みを大公開します。

ネタの整理

過去にCfPに応募する際に、「何を発表すればいいのかわからない」という悩みを持っているメンバーがいました。

ZOZOTOWN iOSアプリは10年以上、改善を続けて進化しているサービスです。レガシーからモダンへのリファクタリング、新しい技術を導入してファッション業界を盛り上げたサービスなど、普段の開発業務でも掘り下げてみると面白い内容がたくさんあったはずです。

それらを可視化するために、iOSチームではネタ表を作成し、整理してみました。まず、最近導入した技術・解決した問題・または興味があって研究したいことを定期的に記入します。そしてチーム内に共有することで、ネタがどんどん集まりました。

実際にこのネタ表から展開して今回のiOSDC Japan 2020の発表タイトルとなった内容がいくつも存在します。

作業時間の確保

ネタがあっても、なかなか調査や検証に着手できないことが多々ありますよね。そこで「もくもく会」を導入しました。もくもく会とは、ネタに対して集中して作業できる専用の時間です。毎週の通常業務として1時間を確保し、チームメンバーが黙々と各自のネタを検証し、最後に進捗を共有します。CfPの応募期限が直前となった時期には、1回のもくもく会を2時間へ増やしたこともありました。気を散らさず集中的に作業することで、スムーズに調査や検証ができました。

このような取り組みを通して、チーム全体の力を合わせてメンバー一人一人をサポートし合っています。その結果、ZOZOTOWN iOSチームがCfPに応募した7件のうち、レギュラートーク2件、LT2件の合計4件が採択されました。

技術顧問レビュー

そして採択された後も、当日を迎えるまでサポートは続きます!

たとえば、弊社では毎月iOS技術共有会を開催しています。この会は弊社のiOS開発者全員が集まり、それぞれのプロダクト開発で得た知見を共有する場です。共有会には技術顧問である岸川さんも出席しており、毎月様々なアドバイスを頂いています。iOSDC Japan 2020開催直前は、発表資料に対するレビュー会が行われ「iOSDC Japan殿堂入り」のスピーカーから貴重な意見をいただけました。チーム内の手厚いレビューを経て登壇資料が完成します。

iOSDC Japan 2020当日の工夫

今年のiOSDC Japan 2020は初のオンライン開催となりましたが、弊社ではすでにオンライン開催でのWWDC20参加を経験していたため、大きな混乱はありませんでした。WWDC20についてのまとめは他の記事にてまとめていますので、ぜひご覧ください。 techblog.zozo.com

ここでは、開催期間中の働き方、効率的にトークを見るためにチームで行った取り組みをお話します。

開催期間中の働き方

弊社はカンファレンスへの参加が推奨されており、今回のiOSDC Japan 2020も勤務扱いとし、3日間それぞれ振替休日をとる形となりました。

チーム内での効率化

どの発表も興味深い内容であったため、トークの選択に迷われた方も多かったのではないでしょうか。より多くのトークを効率よく視聴するために、チーム内で視聴予定を共有できる工夫をしました。

まず、Miroというツールで事前にどのトークを見る予定か、もしくは迷っているかを宣言するタイムテーブルを作成します。その上にみんなそれぞれ自分の名前ラベルを貼っていき、どの時間帯にだれがどのトークを見ているのかをわかるようにしました。

このようにすると、迷っていた方のトークを他の人が見る予定であれば、もう1つの方を視聴し、後で共有し合うなどといった選択も可能になります。

実際にMiroで用意した画面をご紹介します。

iPadでフルページスクリーンショットを3枚撮り、貼って置くだけの簡単運用です。フルページスクリーンショット便利ですね! image

拡大してみると、こんな感じです。 image

登壇内容の紹介

最後に、弊社のCTO今村からスポンサーセッション1件、採択された5件、技術顧問である岸川さん2件の合計8件のトークをご紹介します。

「ファッション業界を技術で変える、ZOZOの挑戦〜CTOが語る理想の組織像とは〜」

最初に紹介するのは、CTO今村(@kyuns)のスポンサーセッションです。

本セッションではiOSエンジニアの働き方やこの1年間の組織・プロダクトの変遷と改革について紹介しました。私たちiOSエンジニアが日頃どのように開発に取り組んでいるのか、組織やプロダクトはどのように進化しているのかを様々な数値やキーワードとともに説明しています。iOSエンジニアが日頃活用している開発環境や補助制度(iPad Proの貸与やApple Siliconの開発者Kit購入補助など)についても紹介しております。 fortee.jp

「iOSではじめるWebAR」

次は、@ikkouのレギュラートークです。

ARやVRといったXR領域が大好きなikkouは、ARKitに関する技術を定常的にキャッチアップしています。今回テーマとした選んだWebARは、Web技術ということもあり取っつきやすさはピカイチです。もっと多くの人にARについて知ってもらいたく、iOSDC Japan 2020に登壇しました。ARの基礎からサンプル付きで主流の技術まで、目的別で各技術を詳しく紹介しています。これからWebARをはじめたい方はぜひご覧ください!

fortee.jp

「iOSアプリのバッテリー消費を意識する」

次は、@arara_jpのレギュラートークです。

Appleやユーザーはバッテリーを非常に重視していますが、デベロッパーはクラッシュフリー率や起動速度など他の指標と比べると、バッテリーにそれほど関心を示していない傾向があります。

ですが、この発表の後、Ask the Speakerで「バッテリーの調査方法ありがとう!」とコメントをいただいたり、「自分のアプリも見てみよう」とツイートがあったりと、少しでもバッテリー消費に関心を持つきっかけを生み出せた気がします。ぜひご覧いただき、開発者の皆さんに少しでもバッテリー消費に関しての意識付けがされたら幸いです。

fortee.jp

「テストコードが増えるとバグは減るのだろうか?「0%→60.3%」で見えた世界の話」

次は、@ahiruのレギュラートークです。

テストコードを書く文化がなかったZOZOTOWNのコードにテストを導入し、この1年でテストカバレッジの割合を0%から56.4%(タイトルの60.3%から訂正)にまで増加させたお話です。

当日はトーク後のAsk the Speakerも大行列ができており、自動テストやQAチームとの勉強会の内容など、ZOZOTOWNの実情に関する質問が途切れませんでした。他社のテストコード導入事例に興味のある方が多いのではないでしょうか。本トークでは実体験を軸にしたメリット・デメリット、組織や開発体勢に適したテスト手法の一例も紹介しています。テストコードを導入した他社事例をはじめ、テストに興味がある方はぜひこのトークを見てください。

fortee.jp

「文字列をコピーできるスクリーンショットを作る」

次は、@re___youのLTです。

こちらのLTでは、文字列をコピーできるスクリーンショットの作り方について、コードやわかりやすい図を使って解説しています。画像を文字に起こす必要がなくなり、iOSエンジニアとしてもデザインデータの検索をする際など、とても便利だと思います。iOS 13からフルページのスクリーンショットが可能となったことについて「知らなかった!」という声も多くありましたが、フルページのスクリーンショットの作成については他の記事にまとめてありますので、ぜひこちらもご覧ください! techblog.zozo.com

fortee.jp

「100人以上の中高大学生にiOSアプリ開発を教えていて感じたこと」

次は、@tosh_3のLTです。今回、初めてのカンファレンス参加かつ登壇となりました。

新卒のtosh_3は、なんとすでに100人以上の中高大学生へ教える立場にあったんです! その驚異の経験から、発表中のニコ生コメントも「新卒とは」「新卒ですでに教える立場」とかなり盛り上がっていました。教え方の工夫だけではなく、iOSエンジニアとして考えるべき視点についても語っていますので、ぜひスライドを覗いて見てくださいね。

fortee.jp

「SourceKit LSPをブラウザでコードを読むために活用する」

ここからは岸川さんのレギュラートークを紹介します。

本トークは、岸川さんが今年仕事や趣味で携わってきた、SourceKit-LSPについての発表でした。ブラウザでGitHub上のSwiftコードを読むときに、Xcodeのようにメソッドの定義元にジャンプできるデモが行われた際には、ニコ生コメントで弾幕ができるほど盛り上がりました。SourceKit-LSPの基礎から活用まで一連の知識がわかりやすく紹介されて、コードレビューの効率が革命的に上がる技術になりますので、ぜひご覧ください。

fortee.jp

「400種類のアプリを毎日ビルドする自動化の技術」

次にも、岸川さんのレギュラートークとなります。

このトークでは、Slackbot・fastlane・Bitriseなどの機能を活用して、CIビルド用の情報の自動収集や更新・プッシュ証明書の自動更新・App Store Connectの申請情報の自動更新など、究極の自動化を追い求める技術が幅広く語られました。現状のZOZOTOWNアプリでも、究極を求めて改善できるところが多々あります。岸川さんにご協力を頂きながら、より自動化されたZOZOTOWNアプリを目指していきます。 fortee.jp

以上、セッションの紹介でした。

After iOSDC Japan 2020を開催

ブログを書くまでがiOSDC! と言いますが……いえいえ、iOSDC Japan 2020はまだ終わりませんよ!

今回開催されたiOSDC Japan 2020について、弊社ではAfter iOSDC Japan 2020を9月29日(火)に開催します。Sansan(株)、note(株)、(株)ZOZOテクノロジーズの3社による合同イベントです。

各社の社員によるLT、パネルディスカッションを行いますので、興味のある方はぜひご参加ください!

こんな方におすすめです。

  • iOS関連技術およびすべてのソフトウェアエンジニア
  • iOSDC Japan 2020の振り返りを一緒にしたい方
  • iOSDC Japan 2020には参加していないけど、情報が知りたいという方

本イベントには、ZOZOテクノロジーズの技術顧問でもある岸川さんも登壇します。

iOSDC Japan 2020に参加した人もそうでない人も、みんなで振り返りましょう!

イベント申し込みはこちらから!

zozotech-inc.connpass.com

さいごに

ZOZOテクノロジーズでは、一緒にモダンなサービス作りをしてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! tech.zozo.com

Appleのエンジニアに聞いた日頃の疑問とこれからの話 - オンライン開催のWWDC20まとめ

opg

こんにちは。ZOZOTOWN部の荒井です。

memoji-arai

先日WWDC20が開催され、今年も弊社iOSメンバーが参加してきました。Apple Siliconや各次世代OSなど面白い発表が目白押しでしたね。

カンファレンスの内容も非常に興味深いものでしたが、今年は諸般の事情を鑑みて、初のオンライン開催となったことも印象的でした。

本記事ではWWDC20オンライン開催にあたり、ZOZOTOWN iOS担当のメンバーがどう臨んだのか、参加して感じた現地開催との相違点をお伝えします。また、Developer Labsに参加し、Appleのエンジニアと日頃疑問に思ってる点について話をしてきました。可能な範囲で内容を公開しますので、是非最後までご覧ください。

WWDC?

WWDC(Worldwide Developer Conference)は、Appleが年に1度開催している開発者向けのカンファレンスです。ZOZOテクノロジーズでは海外カンファレンスへの参加が推奨・サポートされており、例年当選したメンバーが業務の一環として参加しています。現地の様子は昨年の参加レポートをご覧ください。

techblog.zozo.com

オンライン開催にどう臨んだか

初のオンライン開催ということで事前情報が少なく、働き方も非常に悩ましいものでした。ここでは開催期間中の働き方、Developer Labs参加への事前準備について紹介します。

開催期間中の働き方

ZOZOTOWNのiOSチームは話し合った結果、以下のような働き方にしました。

  • 現地時間に合わせた日本時間2:00 - 11:00での勤務
  • 6月27日(土)を出社とし、6月22日(月)を休みに振り替え
  • 各自自宅からの参加

Engineering Sessionsはいつでも視聴できることが想定されていましたが、Developer Labsへ参加することを中心に考えていたため、現地時間に合わせました。弊社はカンファレンスへの参加が推奨されており、フルフレックス制度といった働き方にも柔軟性があります。ただし深夜勤務は推奨されていないためWWDC期間中のみ例外的な対応をしました。なお、WWDC期間中の業務は事前に調整しています。オンライン開催に限らず、希望し当選したメンバーは業務として全員が参加予定でした。

Developer Labs参加への事前準備

Developer LabsはAppleのエンジニアやデザイナーに直接質問できる貴重な機会です。WWDCに参加する醍醐味でもあり、効率よく参加するために僕たちのチームでは事前準備を行っています。以下の内容をチーム全員で共有し管理しました。

  • 質問内容
  • 業務との関連性
  • ラボ名
  • 参加者
  • 英文
  • 結果

この管理は例年行っていることですが、オンライン開催にあたり「1-on-1」「録音禁止」「英語のみ」といった開示があったため、今年はその点を考慮した内容となっています。結果10程度のラボに参加でき、有益な情報収集ができました。「英語のみ」については通訳係として当選者以外のエンジニアも参加できたので、言語面での不安も軽減できました。

当日になって質問を考えると漏れが発生したり、間に合わず申請できないといったことがあるため、今後Developer Labsへ参加を予定している方は事前準備をおすすめします。

開催期間中の動き

効率的な情報共有をするため、ルールを2つだけチームで決めて動きました。

  • Slackの専用チャンネルにてテキストでの情報共有
  • 1日に1回通話での情報共有

基本的にコミュニケーションの場を整える目的で、日報のような目的ではありません。各々がEngineering Sessionsを観たり、Developer Labsへ参加したり、サンプルコードを書いたりと自由に行動していました。Engineering Sessionsは後日でも閲覧可能であると確認が取れたため、2日目以降はEngineering Sessionsの優先度を下げていたメンバーが多かったです。

現地開催との相違点

今回のオンライン開催は現地開催と比べてどうだったのでしょうか。参加メンバーが感じたことを環境、セッション、ラボの観点でオンライン開催のメリット・デメリットをお伝えします。

環境

はじめに、オンライン開催と現地開催でどのような環境面の差を感じたかを紹介します。

  • メリット

    • 現地よりもネットワーク環境が良く、βのダウンロードや検証がスムーズ
    • みんなオンライン参加なので、同じPDTでの活動がタイムラインに流れてきて、例年とにぎやかさが全然違う
  • デメリット

    • 深夜開催なので音声に配慮が必要
    • 現地で見る方がモチベーションアップに繋がる
    • 移動がなかったため、ラボでいきなり英語をしゃべる落差がある
    • 他社のエンジニアと情報交換が難しい
    • 物販がない

発表された技術の検証はオンライン開催の方がやりやすいという意見がありましたが、モチベーション面は現地開催の方があがるという意見が多かったです。

Engineering Sessions

次はEngineering Sessionsです。

  • メリット
    • いつでも閲覧可能なので、何回も巻き戻したり、技術について話しあったりしてセッションが身近に感じられた
    • 日本語・英語字幕に助けられた時もあった
  • デメリット
    • オンラインだとライブ感がなく、参加者が何に注目しているのか掴みにくい

当日に何回も巻き戻して確認できるのはオンラインならではのメリットです。ただし「拍手」などのリアクションがないため、盛り上がりポイントが分かりづらいという意見もありました。

Developer Labs

最後にDeveloper Labsについての相違点です。

  • メリット

    • 予約制なのでAppleエンジニアも回答準備をしており、参考サイトの共有などもあった
  • デメリット

    • ラボに入り浸れない
    • 混んでいるラボ、空いているラボが判断できず、予約状況も分からない
    • 予約制なので手を動かしてもう一度聞きたい時にいけない

オンラインではすべてのラボが予約制のため、ラボに対する自由度は少なくなりました。繰り返し行くということがしづらいため、限られた時間内で問題解決しなくてはいけないことがデメリットの意見として多かったです。

個人で作業する分にはオンラインの方が効率的な印象を受けました。ただ、やはり現地参加の方がモチベーションも上がりやすくエンジニアとのコミュニケーションも活発になるので、現地開催のメリットは大きそうですね。

Developer Labs

ここからはDeveloper Labsで聞いてきた内容の一部を各メンバーが紹介します。Developer Labsでは業務に直結することを優先的に質問していますが、エンジニアがそれぞれ疑問に思った業務外のことも時間の許す限り質問しています。内容はAppleとのNDAのため、可能な範囲での紹介となります。

UICollectionViewとUITableViewのこれから

tosh

こんにちは、ZOZOTOWN部の小松です。例年のWWDCはKeynoteしか見ていなかったのですが、今回はセッションやラボに参加し、初めてのWWDCの全参加となりました。

今回のセッションをみていく中で、とあることに気付きました。UICollectionViewに昨年同様新しい機能が追加されていて、その新機能追加により、UITableViewで実装していたレイアウトがUICollectionViewでも実装しやすくなるとのことです。

「そうすると、UITableViewとどのようにして使い分けの判断をするべきか?」その疑問をAppleのエンジニアにぶつけるべく、UIKit and Build for iPad labへ参加することに決めました。初めてのラボでしたが、Appleのエンジニアの方は大変優しく、質問に対してとても丁寧にお答えいただいたのが印象的です。

今回質問した内容は以下の3つです。

  • UICollectionViewとUITableViewのどちらを使用するべきなのか?
  • UITableViewはDeplicatedになるのか?
  • なぜ、既存でUITableViewがあるのに、UICollectionViewListを作ったのか?

結論として、もし既存のアプリとしてUITableViewを使用しているのであれば、そのままUITableViewを使用し続けることには問題ない。しかし、新しいアプリを作るのであれば、UICollectionViewListが推奨なようです。今後はおそらく、UICollectionViewには追加されるが、UITableViewには追加されないであろう機能が増えていくであると思われるそうです。UICollectionViewListを使用するメリットは、複雑なレイアウトを容易に組むことができるようになることだそうです。たとえば、App Storeのアプリを参考にみてもらうとわかりやすいとのことでした。

以上を踏まえると、現状UITableViewを使用し続けることに問題はないが、より簡単にそして最新の機能を使うためにも徐々にUICollectionViewへと移行していくのが良さそうです。ただ、UICollectionViewListはiOS 13以上対応なので、今すぐにとはいかないところが難しい点ですね。

Appleのエンジンニアにxcresulttoolの使い方を教えていただきました

rin

ZOZOTOWN部の林です。

Xcode11からxcresultファイルを解析できるxcresulttoolコマンドが推奨されています。

View and share test results

ZOZOTOWNアプリでも今後テストを充実していくために、xcresulttoolを利用することが避けられないでしょう。xcresulttoolをもっと理解するために、Testing and Continuous Integration lab(25分)に申請を出して当選しました。

事前にサンプルアプリのUIテストを用意して、達成したいことを申請時に伝えておりました。xcresultからUIテストで撮ったスクリーンショットを取り出して、指定したフォルダに保存することを目標としました。そして、ラボ当日にAppleのエンジニアの指示を聞きながら、用意したxcresultの解析を行いました。

xcresulttoolでxcresultファイルから変換されたjsonファイルの確認方法、xcresultのツリー階層など、xcresulttoolについてドキュメントに記載されていない内容もたくさん教えていただきました。

情報を聞くだけではなく、コマンドの操作も教えていただいたので、欲しかった結果を得られた瞬間、一緒に仕事ができた気分になりました。自分にとって貴重な経験でした。

SideBarはハンバーガーメニューと違うのか?

edm

ZOZOTOWN部のえんどうです。

iPadのナビゲーションにSideBarが推奨されるようになりましたね。

しかし、WWDC 2014の「Designing Intuitive User Experiences」のセッションでiOSにはハンバーガーメニュー(a.k.a SideBar)は推奨しないとありました。ではなぜ、2020年では推奨されるようになったのでしょうか? そもそも、「ハンバーガーメニューとAppleのいうSideBarは違うものなのか?」ということが気になりラボで質問をしてきました。

結論として、SideBarとハンバーガーメニューは違うものという回答でした。その違いは「常に表示されているか」です。

ハンバーガーメニューは非表示のところからユーザーがハンバーガーメニューを表示しなければなりません、SideBarは常に表示されています。そのため、ユーザーはいつでもどこにいるのか見失うことはありません。

ナビゲーションでは、「何が見つけられるか」「どこにいるか」「どこにいけるか」が大切だと教えてもらいました。この考えは画面遷移を考える上でとても重要なことなので意識して気をつけていきたいと思いました。

Interface Builder and Auto Layout lab

ring

ZOZOTOWN部の名取です。

ZOZOTOWNではInterface Builderを多くの画面で使用しているため、特定のStoryBoardを複数人で開発する際のtipsを教えてもらいました。

複数のViewControllerが配置されたStoryBoardにおいてはXcodeのEditor→Refactor To StoryBoardを選択することで特定のViewControllerのみを切り出したStoryBoardを作成できます。

この機能によりコンフリクトの発生を幾ばくか抑えることができるため複数人で開発する際は推奨とのことでした。

また近年SwiftUIが大きな盛り上がりを見せていますが、Interface Builderが将来的になくなることはなくそれぞれが違う技術として共存していくだろうという話もされていました。

NotarizationとWatch FaceとXcode 11.4の静的リンクについてラボで聞いてきました

ZOZOTOWN部自称macOS担当の@banjunです。今年は10年に1度のウニ(・∀・∀・)バーサルyearでしたね(注:PPC→Intel移行のときの流行語)。私はAquaSKKなどのOSSのmacOSアプリをメンテしていることもあり、Universal App Quick Start Programにも参加しています。

ラボでは「アプリとCLIではNotarizationチェックの仕組みと検証方法が異なること(= 具体例ではSwiftBeakerを検証する方法)」「Watch FaceでサポートされているLive Photosのfps上限や、Watch Face Sharingのシリアライズフォーマットが公開仕様か」「Xcode 11.4で導入されている静的リンクのビルド前チェックとApple Siliconとの関連や今後のライブラリーの管理はどう変わっていくのか」などを聞いてきました。いずれの知見も次の開発に活かせそうです。

まとめ

今回はWWDC20の参加レポートをお伝えしました。オンラインは初の試みでしたが、チームとしては参加して成功だと感じています。リアルタイムで情報をキャッチアップすると共に、すでにZOZOTOWNではiOS 14への調査・対応も進めています。来年は現地でWWDCが開催されると良いですね。

さいごに

ZOZOテクノロジーズでは、一緒にモダンなサービス作りをしてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください!

tech.zozo.com

ZOZOMATのクロスプラットフォーム3D

1720_1140_2

ZOZOMATとは何でしょうか?オンラインで靴を購入する際に、サイズが合わないという問題を解決する仕組みです。1台のスマートフォンと紙製のZOZOMATだけで、正確に足のサイズを測れます。足をスキャンすると、高精度の3Dモデルが生成されます。最適なサイズの靴も表示されるので、すぐに靴を購入できます。

zozomat_pr_001

こんにちは!ZOZOテクノロジーズの@kapsy1312です。ZOZOMATプロジェクトの一員として、スキャン結果を3D空間に表示するビューの開発を担当しました。プロトタイプでは、Appleの標準3Dフレームワーク、SceneKitを使用していました。しかし、全く同じ機能とデザインをAndroidで再現するにはコストがかかるため、さらに適切なソリューションを検討しました。

この記事では、スキャン結果の3DビューをAndroidとiOSデバイス向けに開発した際の課題と解決策を説明します。プラットフォーム依存のシーングラフフレームワークではなく、C++とOpenGLを選択した理由も説明します。付属のサンプルプロジェクトも用意してあり、後半で解説します。

続きを読む

iOS 13から追加されたフルページのスクリーンショットの機能と対応方法の紹介

f:id:vasilyjp:20200331150230j:plain

こんにちは! ZOZOTOWN部の遠藤です。
iOS 13がリリースされて半年が経ちましたね。iOS 13といえばダークモード機能が注目を浴びましたが、それ以外にもたくさんの新しい機能が追加されました。
本記事では新しく追加されたフルページのスクリーンショットについて書いていきます。

続きを読む

ZOZOTOWN iOS にスナップショットテストを導入して開発速度を劇的に向上させた話

f:id:vasilyjp:20200123114510j:plain

こんにちは! 開発部の@ahiru_starrrです。

本稿では、ZOZOTOWN iOSにSnapshotTestを導入したのでその経緯や導入方法、導入するメリット・デメリット、どんな場面で役に立つのかなどについて書いていきます。

SnapshotTestがどのようなものかよく分からない方や導入を検討している方々のお役に立てれば幸いです。

SnapshotTestとは

構成済みのUIViewまたはCALayerからスナップショットを生成し「正しい状態との比較・差分」を検出するためのテストです。

開発を進める中で、「意図せずにデザインやレイアウトが崩れてしまう」ようなケースはしばしば起こるかと思いますがそれを防ぐためのテストとして大変効果的です。

SnapshotTest導入の背景

きっかけとなったのは、iOSDC Japan 2019のスナップショットテスト実戦投入 / Practical Snapshot Testingのセッションです。
このセッションで実際に導入した話を聞いたことで、ZOZOTOWNのプロジェクトに導入するメリットが鮮明になりました。

現状のZOZOTOWN iOSの開発には2つの課題がありSnapshotTestを導入することでそれらの解決・緩和が見込めると考えたのです。

2つの課題

エンジニア ↔︎ デザイナー間のコミュニケーションコスト

レイアウトに関連する変更を加えた場合、ZOZOTOWNでは例え小さな変更であったとしても必ずデザイナーにデザイン確認を行なうフローがあります。
いわゆるデザインのリグレッションテストです。

ZOZOTOWNはデザインに強いこだわりのあるサービスのため、このフローは欠かすことができませんでした。

f:id:vasilyjp:20200122104937p:plain

しかしながら、より効率的でスピーディーな開発を目指していく上でこのフローが無駄なコストであることはいうまでもありません。少なくとも「開発の前後でデザインに差分がないことを確認する」だけであればもっと機械的にできるはずです。

またZOZOTOWNは主に千葉と東京、福岡の3拠点で開発が行われています。
そのためデザイナーとエンジニアのコミュニケーションはSlackやテレビ会議で行うことが多いのもコミュニケーションコストの増加に拍車をかけていました。

レガシーからモダンへの取り組み

ZOZOTOWNは長い歴史のあるサービスです。

iOSアプリは2010年にリリースされており、iOSアプリだけでみても約10年の歴史があります。 2010年というと、iPhone 4が発売された時期ですね。

長い年月とともに開発環境や開発言語は大きな進化を遂げましたが、一方でプロジェクトのソースコード量は膨大し、一部の取り残されたObjective-Cは大きな態度で居座り続けています。

この半年〜1年でチームメンバーは大きく変わりSwift化をはじめとするコードのモダン化への取り組みが活発に行われるようになりました。
リファクタリングも日常的に行われているため、その際のデザインのリグレッションテストをもっと機械的に効率よく行いたいと模索をしているところでした。

ソースコードは大きく変更したいものの、それに伴う画面のUIは変更したくない状況がZOZOTOWNでは頻繁にありました。

上記2つの課題を解決するためにSnapshotTestを導入しました。

導入方法

ZOZOTOWNでは「iOSSnapshotTestCase」×「OHHTTPStubs」の組み合わせで導入をしています。

iOSSnapshotTestCaseはUberがFacebookから引き継ぎ今も継続的にメンテナンスが行われているOSSです。
併せてスタブ用のレスポンスを返すためにOHHTTPStubsも導入しました。

ほとんどの画面はAPIからのレスポンスによって動的に構成されるため、SnapshotTestとの相性が非常に良いです。staticなjsonファイルをアプリに持たせておくことで常に同じ表示条件にてSnapshotTestができるのも大きなメリットだと思います。

上記2つのOSSはCocoaPodsにて導入しましたが、予めSnapshotTestを行うためのターゲットを作成しておきそのターゲットのみに導入をしました。

target 'SnapshotTests' do
    inherit! :search_paths
    pod 'iOSSnapshotTestCase'
    pod 'OHHTTPStubs/Swift'
end

環境変数を設定

OSSを導入したら環境変数の設定を行います。

これによりSnapshotTestを行った後の差分画像とリファレンス画像の保存先が設定されます。

XcodeのEdit SchemeからRunの項目を選択して、Environment Variablesに2つの環境変数を設定します。

IMAGE_DIFF_DIR $(SOURCE_ROOT)/$(PROJECT_NAME)SnapshotTests/FailureDiffs
FB_REFERENCE_IMAGE_DIR $(SOURCE_ROOT)/$(PROJECT_NAME)SnapshotTests/ReferenceImages


f:id:vasilyjp:20200114121624p:plain

FailureDiffsディレクトリにはSnapshotTestを行った後の差分の画像が保存され、ReferenceImages_64ディレクトリにはリファレンス画像が保存されるようになります。

実装方法

下記はUIViewControllerのSnapshotTestの実装例です。

import FBSnapshotTestCase
import OHHTTPStubs

@testable import ZOZOTOWN

class CartStockViewControllerTest: FBSnapshotTestCase {

    override func setUp() {
        super.setUp()

        folderName = "CartStockViewController"
        fileNameOptions = [.device, .OS, .screenSize, .screenScale]

        recordMode = false
    }

    func testCartSnapshotTest() {
        stub(condition: isPath("/stocklist.json")) { _ in
            let stubPath = OHPathForFile("stocklist_test.json", type(of: self))
            return fixture(filePath: stubPath!, headers: ["Content-Type": "application/json"])
        }

        let cartStockViewController = CartStockViewController()
        cartStockViewController.view.layoutIfNeeded()

        let exp = expectation(description: "Screen Loaded")
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.FBSnapshotVerifyView(cartModalStockViewController.view)
            exp.fulfill()
        }

        wait(for: [exp], timeout: 10.0)
    }

}

順番に説明していきます。

テストクラスを作成

はじめにテスト用のファイルを作成し、上記で導入した2つのOSSをimportしてFBSnapshotTestCaseを継承したクラスを作成します。

import FBSnapshotTestCase
import OHHTTPStubs

@testable import ZOZOTOWN

class CartStockViewControllerTest: FBSnapshotTestCase {
}

SnapshotTestではすでに実装済みの画面に対してテストを行う場合がほとんどのため
@testable importを記述します。これにより、プロダクトコードが記述されているターゲット内のinternalで宣言されたソースコードにアクセス可能となります。

recordModeを設定

SnapshotTestを行う際はrecordModeの設定が必要となります。

recordMode 説明
true リファレンス画像を生成
false SnapshotTestを行う

リファレンス画像すなわち正しいUIの状態のスナップショットを生成するときはrecordModeをtrueにセットしてSnapshotTestを実行します。
コードのリファクタリングなどを行った後でデザインのリグレッションテストを行う際にはrecordModeにfalseを設定してSnapshotTestを実行します。

テストコードを実装

フォルダ名を設定

folderName = "フォルダ名"

folderNameに適当なフォルダ名をセットします。

これを設定するとReferenceImages_64/フォルダ名/以下にリファレンス画像が出力されるようになります。

ファイル名を設定

fileNameOptions = [.device, .OS, .screenSize, .screenScale]

fileNameOptionsにてファイル名を設定します。

上記のような設定をした場合は「ファンクション_識別子_デバイス名_OSバージョン_スクリーンサイズ_倍率」のような画像名になります。

stubの設定

APIモックを用意する主な目的は最初の方でも述べましたが、APIのレスポンスによってテスト結果が変わるのを防ぐためです。

APIのレスポンスから動的に構成される画面のSnapshotTestを行う場合、リファレンス画像生成時のAPIのレスポンスとSnapshotTest実行時のAPIのレスポンスは同一である必要があります。
これを実現するために、OHHTTPStubsを導入しています。

stub(condition: isPath("/stocklist.json")) { _ in
    let stubPath = OHPathForFile("stocklist_test.json", type(of: self))
    return fixture(filePath: stubPath!, headers: ["Content-Type": "application/json"])
}

isPathの引数には、画面の表示に必要な情報を取得しているAPIのエンドポイントを指定します。

OHPathForFileの第一引数には予め用意していおいたテスト用のjsonファイル名を指定します。

FBSnapshotVerifyView

let cartStockViewController = CartStockViewController()
FBSnapshotVerifyView(cartStockViewController.view)

SnapshotTest対象のViewController(View)を生成し、FBSnapshotVerifyViewに渡してあげます。

CartStockViewControllerの内部ではViewのライフサイクルイベントをトリガーにAPIの通信処理がはしり、そのレスポンスを受け取って画面が組み立てられる実装になっています。

APIのレスポンス取得からレイアウト作成までは少し時間がかかるためexpectationを使って非同期処理を行なっています。

ZOZOTOWNでは極力既存の実装を変更しないことを優先してテストコードを書きましたが、この部分の実装に関しては

  • 予めモックとなるモデルを用意しておきViewControllerの初期化時に渡す方法
  • ViewControllerの初期化後にテストFunc内からAPIをコールし画面のレイアウトを行う方法

など様々な実装が考えられると思います。
各プロダクトに応じて最善の実装をしていただければと思います。

SnapshotTestのユースケース

ZOZOTOWNでのユースケースを例にSnapshotTestをしてみます。

テスト対象の画面はZOZOTOWNのカート画面です。

f:id:vasilyjp:20200115144125p:plain

コミットログを見る限り、最後に変更が加えられたのはもう何年も前でObjective-Cで記述されている画面です。

今回はSwiftで書き直し、さらにViewの構成も変更しました。

リファレンス画像を生成

まずはリファレンス画像を生成します。いわば、カート画面の模範(正しい状態)となる画像です。

recordModeをtrueに設定してテストを実行することで、ReferenceImages_64/以下にリファレンス画像が生成されます。

f:id:vasilyjp:20200115144208p:plain

SnapshotTest!!!

リファレンス画像を生成後、カート画面のリファクタリングを行なったブランチにてSnapshotTestを実行します。

recordModeをfalseに設定してテストを実行し、リファクタリングの前後でUIに差分がなければテストは成功し、差分がある場合にはテスト失敗のアラートが表示されます。

実際にテストを実行したところ下記の画像がFailureDiffs/ディレクトリに出力されました。

f:id:vasilyjp:20200115144013p:plain

リファクタの前後の画像を並べて比較すると差分がないように見えるのですが、実際には一部フォントサイズが違っていたりレイアウトが多少ずれていたりしました。

リファクタ前 リファクタ後
f:id:vasilyjp:20200115144208p:plain f:id:vasilyjp:20200115144325p:plain

SnapshotTestにより、これら差分の検出がとても簡単に行えます。

諸々修正して再度SnapshotTestを行うと成功しました。

f:id:vasilyjp:20200114124511p:plain

今までの開発では、リファクタリングが終わったタイミングでデザイナーにデザインの確認を依頼してエンジニアが再度修正、修正したら再度デザイナーに依頼....というフローがありましたが、SnapshotTestを導入したことによりこのフローが不要となりました。

デザイナー抜きでは実現できなかったタスクがエンジニアのみで完結できるようになったのです。

もちろんテストコードを書く必要はあるためその分のコストはかかってはしまいますが、それを差し引いても圧倒的に開発速度は向上します。

SnapshotTest導入のメリット・デメリット

ここまでSnapshotTestのメリットを挙げてきましたがデメリットもあると思います。

当然ながら、デザインに意図的な変更を加えた場合SnapshotTestは失敗となってしまいます。 その時は再度SnapshotTestでリファレンス画像を生成し直したり、あるいはstubを修正するなどの必要が出てくる場合もあり運用していく上でのコストがかかってきます。

デザインのほんの一部を修正したいだけなのにSnapshotTestのテストの方も修正する必要が出てきて全体としての開発速度が落ちてしまっては本末転倒です。

注意点としては、プロダクトに導入することで本当に恩恵を受けることができるのかよく考える必要はあるかなと思いました。

  • メリット

    • 導入コストが低い
    • デザインのリグレッションテストをエンジニアのみで行える
    • 開発速度の向上・開発の効率化が期待できる
    • 意図しないデザイン・レイアウト崩れを自動で簡単に検出できる
  • デメリット

    • 運用コスト

まとめ

SnapshotTestは、その性質を理解し上手に使うことで高い費用対効果を得ることができます。

実際にSnapshotTestを導入してみて、「効率的なチーム開発」「開発速度の向上」を実現するための選択肢の1つとして大きな力を発揮すると感じました。

今回は局所的にSnapshotTestを利用するユースケースをご紹介しましたが、CI/CD環境にSnapshotTestのWorkflowを組むことでデザインのリグレッションテストの自動化も可能です。

この記事がSnapshotTest実装の一助になれば幸いです。

ZOZOテクノロジーズでは、iOSエンジニアを募集しています。
興味のある方はこちらからご応募ください!

www.wantedly.com

iPadOS新機能「Multiple Windows」をWEARに仮実装してみた

iPadOS Multiple Windows

はじめに

こんにちは。WEAR iOSチームの坂倉 (@isloop) です。

この間リリースされたiPadOSはかなり盛りだくさんの内容でしたね。

個人的には、1つのアプリで複数のウィンドウを開ける「Multiple Windows」機能が一番気になりました。

この記事では、WWDC 2019のセッション Introducing Multiple Windows on iPadArchitecting Your App for Multiple Windows を参考にしながらWEARへの仮実装を通して「Multiple Windows」を解説します。

解説

条件

Multiple Windowsは以下の条件で動作します。

  • Xcode 11以降に含まれるiOS 13.0 SDK以上でビルドされたアプリ
  • iPadOSがインストールされているiPad

Supporting Multiple Windows on iPad | Apple Developer Documentation

WEARにMultiple Windows機能を実装する

今回、WEARに以下の5つのMultiple Windowsの機能を実装してみました。

  • 複数のウィンドウを開く
  • ドラッグ&ドロップで新しいウィンドウを開く
  • ロングタップからのコンテキストメニューから新しいウィンドウを開く
  • 以前開いていたすべてのウィンドウの状態を復帰させる
  • 現在開いているすべてのウィンドウのUIを更新する

これらすべてを実装するのはなかなか大変そうに思えますが、意外に少ないコードで実装できます。

その1. 複数のウィンドウを開けるように、Xcode 11以前で作ったアプリをMultiple Windowsに対応させる

実は、Xcode 11で作ったプロジェクトならば設定画面の「Supports multiple windows」にチェックを入れるだけで対応できます。

Supports multiple windows

しかしながら、Xcode 10以前に作られたプロジェクトの場合はコードを足さないと対応できません。

WEARは現在、Xcode 10.3で開発しているのでコードを足す必要がありました。

と言ってもほんの少しのコードで対応できます。以下の3つを足すだけです。

(1)AppDelegate.swiftに以下のメソッドを追加します。

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
  return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

(2)Info.plistに以下のコードを追加します。

<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <true/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>Default Configuration</string>
        <key>UISceneDelegateClassName</key>
        <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
        <key>UISceneStoryboardFile</key>
        <string>BrowserViewController</string> // 最初に表示するStoryboard名
      </dict>
    </array>
  </dict>
</dict>

(3)SceneDelegate.swiftを新規作成して下のコードをそのまま貼り付けます。

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
      guard let _ = (scene as? UIWindowScene) else { return }
  }
}

これだけです。アプリを起動し「Dockのアイコンを長押し」してみましょう。

これまで表示されなかった項目「すべてのウィンドウを表示」が現れるようになり、1つのアプリで複数のウィンドウを開くことができます。

Multiple Windowsに対応してDockから複数のウィンドウを開く

ちなみに、これまでUIApplicationDelegateで使用していたライフサイクルメソッドは使えなくなります。そのあたりの処理はUISceneDelegateのsceneDidBecomeActive / sceneWillResignActive / sceneWillEnterForegroundなどに移管する必要があります。

その2. ドラッグ&ドロップで新しいウィンドウを開けるようにする

次は、「ドラッグ&ドロップで新しいウィンドウを開く」を実装してみましょう。

ドラッグ&ドロップで新しいウィンドウを開く

流れとしては、NSUserActivityの中へ新しいウィンドウに必要な情報を入れて生成しUIDragInteractionDelegateを利用してSceneDelegateの「scene(_:willConnectTo:options:)」を呼び出し、新しいウィンドウを作ります。

(1)info.plistに有効なNSUserActivityTypesを定義します。

<key>NSUserActivityTypes</key>
<array>
  <string>wear.multiple.windows.coordinateDetail</string>
</array>

(2)UICollectionViewの場合は「viewDidLoad()」あたりに以下のコードを追加します。

collectionView.dragDelegate = self

(3)UICollectionViewDragDelegateの「collectionView(_:itemsForBeginning:at:)」 で、新しいウィンドウを開くのに必要な情報をNSUserActivityのuserInfoに入れてUIDragItemに渡します。(NSUserActivityのactivityTypeは、先ほどinfo.plistで指定した文字列にすること)

extension ViewController: UICollectionViewDragDelegate {
  public func collectionView(_: UICollectionView, itemsForBeginning _: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    let userActivity = NSUserActivity(activityType: "wear.multiple.windows.coordinateDetail")
    activity.userInfo?["Info"] = items[indexPath.item] //新しいウインドウを開く際に必要な情報をuserInfoに入れる
    let itemProvider = NSItemProvider(object: url)
    itemProvider.registerObject(userActivity, visibility: .all)
    let dragItem = UIDragItem(itemProvider: itemProvider)
    return [dragItem]
  }
}

UITableViewもUITableViewDragDelegateがあるのでほぼUICollectionViewと同じように実装できます。UIButtonやUIViewの場合はUIDragInteractionとUIDragInteractionDelegateを使えば可能です。

ここで1つ注意ですが、NSUserActivityのuserInfoに入れてもよいのは以下のタイプのみで、それ以外はアプリが落ちてしまうので気をつけてください。

Each key and value must be of the following types: NSArray, NSData, NSDate, NSDictionary, NSNull, NSNumber, NSSet, NSString, or NSURL. The system may translate file scheme URLs that refer to iCloud documents to valid file URLs on a continuing device.

(4)SceneDelegate.swiftに新しいウィンドウを開くための処理を追加します。

タブをウィンドウ外にドラッグ&ドロップした際にSceneDelegateの「scene(_:willConnectTo:options:)」が走るので、そこに新しくウィンドウを開く処理を追加すれば実装完了です。

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
  if let userActivity = connectionOptions.userActivities.first {
    if userActivity.activityType == "wear.multiple.windows.coordinateDetail", let info = userActivity.userInfo?["Info"] as? NSString {
      let vc = BrowserViewController.instantiate()
      vc.info = info
      window?.rootViewController = vc
    }
  }
}

その3. ロングタップからのコンテキストメニューから新しいウィンドウを開く

ボタンのタップをきっかけに新しいウィンドウを開く動きを実装したいという需要も多いと思います。この場合も少しのコードで実装できます。

ここでは、UICollectionViewのセルをロングタップしてコンテキストメニューを出し「新しいウィンドウを開く」ボタンを押して開く実装例をご紹介します。

ボタンタップで新しいウィンドウを開く

(1)UICollectionViewの長押しをハンドリングしてコンテキストメニューを表示させます。(触覚タッチを使った例)

extension ViewController: UICollectionViewDelegate {
  open override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point _: CGPoint) -> UIContextMenuConfiguration? {
    let actionProvider: ([UIMenuElement]) -> UIMenu? = { _ in
      let openNewWindowAction = UIAction(title: "新しいウインドウで開く", handler: { [unowned self] _ in
        //新しいウインドウを開く処理を
        openNewWindow(indexPath: IndexPath)
      })
      return UIMenu(title: "コンテキストメニュー", image: nil, identifier: nil, children: [openNewWindowAction])
    }
    return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider)
  }
}

(2)新しくウィンドウを開くのに必要な情報をNSUserActivityに追加して、UIApplicationの「requestSceneSessionActivation:userActivity:options:errorHandler:」を実行します。

func openNewWindow(indexPath: IndexPath) {
  let activity = NSUserActivity(activityType: "wear.multiple.windows.coordinateDetail")
  activity.userInfo?["Info"] = items[indexPath.item] //新しいウインドウを開く際に必要な情報をuserInfoに入れる
  UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil)
}

(3)UIApplicationの「requestSceneSessionActivation(:userActivity:options:errorHandler:)」を実行するとUISceneDelegateの「scene(:willConnectTo:options:)」が走るので、NSUserActivityから情報を取得し、それを元に画面を生成させます。

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
  if let userActivity = connectionOptions.userActivities.first {
    if userActivity.activityType == "wear.multiple.windows.coordinateDetail", let info = userActivity.userInfo?["Info"] as? NSString {
      let vc = BrowserViewController.instantiate()
      vc.info = info
      window?.rootViewController = vc
    }
  }
}

その4. 以前に開いていたすべてのウィンドウの状態を復帰させる

これで大体のMultiple Windows機能は実装できましたが、今のままだとアプリを閉じてしまったら閉じる前の状態を復旧できず毎回初期値の状態で起動してしまいます。

以下のコードを足すことで以前開いていたウィンドウの状態を復帰できます。

(1)SceneDelegate.swiftに以下のメソッドを追加します。

@available(iOS 13.0, *)
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
  return scene.userActivity
}

(2)ViewControllerにウィンドウの状態を保存したいタイミングに以下のコードを追加します。

let userActivity = NSUserActivity(activityType: "wear.multiple.windows.coordinateDetail")
userActivity.title = webView.title
userActivity.userInfo?["Info"] = info
view.window?.windowScene?.userActivity = userActivity

(3)SceneDelegate.swiftの「scene(_:willConnectTo:options:)」にウィンドウの状態を保存している「session.stateRestorationActivity」から情報を取得するコードを追加します。

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
  if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
    if userActivity.activityType == "wear.multiple.windows.coordinateDetail", let info = userActivity.userInfo?["Info"] as? NSString {
      let vc = BrowserViewController.instantiate()
      vc.info = info
      window?.rootViewController = vc
    }
  }
}

早速、複数のウィンドウを開き一旦閉じてもう一度起動してみましょう。ウィンドウの状態が復帰できます。

開いていたウィンドウの状態を復帰させる

その5. 現在開いているすべてのウィンドウのUIを更新する

現在開いているすべてのウィンドウのUIを変更したい場合は当然出てくると思います。例えば、Safariならば新しくブックマークを追加した場合、すべてのウィンドウにそれが反映されますよね。

これまでならViewControllerに付き1つのウィンドウだけ管理しておけば問題ありませんでした。しかし、Multiple Windowsは、同じViewControllerのウィンドウが複数並ぶことになります。アクティブなウィンドウだけが更新されるのはまずいわけですね。

すべてのウィンドウを更新する方法として、Introducing Multiple Windows on iPadArchitecting Your App for Multiple Windowsで「UserDefaultsによるKVOで行う方法」と「NotificationCenterで行う方法」の2つの方法が紹介されていました。

ケースバイケースでどちらの手段を使うか決めましょう。

UserDefaultsを用いたKVOの場合

UserDefaultsを拡張しKVOで値の変更を監視して全ウィンドウを更新する方法です。

(1)KVO用のKeyPathを作るExtensionを用意します。

extension UserDefaults {
  private static let isToolbarHiddenKey = "isToolbarHiddenKey"

  @objc dynamic var isToolbarHidden: Bool {
    get { return bool(forKey: UserDefaults.isToolbarHiddenKey) }
    set { set(newValue, forKey: UserDefaults.isToolbarHiddenKey) }
  }
}

(2)設定画面で、isToolbarHiddenを変更する処理を追加します。

UserDefaults.standard.isToolbarHidden = !sender.isOn

(3)NSKeyValueObservationをインスタンスで保持して、ViewControllerの「viewDidLoad()」に以下のコードを追加します。

private var observer: NSKeyValueObservation?
observer = UserDefaults.standard.observe(\UserDefaults.isToolbarHidden,
                                                 options: .initial,
                                                 changeHandler: { [weak self] (_, _) in
  self?.navigationController?.setToolbarHidden(UserDefaults.standard.isToolbarHidden, animated: true)
})

NotificationCenterの場合

ViewControllerが受け取ったイベントをModelControllerに送り、ModelControllerがModelを更新したら、各ViewControllerに通知してUIを更新する方法です。

(1)イベントを更新した際に通知をPostするデータ型を用意します。

enum UpdateEvent {
  case DeleteFavorite

  static let DeleteFavoriteName = Notification.Name(rawValue: "DeleteFavorite")

  func post() {
    switch self {
    case .DeleteFavorite:
      NotificationCenter.default.post(
        name: UpdateEvent.DeleteFavoriteName,
        object: self
      )
    }
  }
}

(2)ModelControllerのメソッド内で処理を完了際に通知を送信する処理を追加します。

final class ModelController {
  func deleteFavorite() {

    // 削除処理

    let event = UpdateEvent.DeleteFavorite
    event.post()
  }
}

(3)ViewControllerにModelControllerに追加したメソッドを実行する処理を追加します。

extension ViewController {
  private func deleteButtonAction() {
    modelController.deleteFavorite()
  }
}

(4)ViewControllerに通知を受信する処理と通知を受信した際に行う処理を追加します。

extension ViewController {
  private func setupNotification() {
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(reload),
      name: FolderDetail.UpdateEvent.DeleteFavoriteName,
      object: nil
    )
  }
}
@objc private func reload(notification: Notification) {
  // 通知を受け取った際に行いたい処理を追加
}

実行すると、すべてのウィンドウにUIの変更が反映されます。

設定したUIの変更をすべてのウィンドウに反映させる

まとめ

Multiple Windowsは、UIの実装はそれほど難しくないですが、データ保存などの設計をMultiple Windowsありきで考える必要があると感じました。

考えられるケースとしては、AとBのウィンドウで使用しているデータの保存先が同じでそれぞれリアルタイムに更新している場合ですね。

どちらを優先させるのか。またはウィンドウ別に保存させるようにするのか。アプリによってどれが最適なのかを考える必要があり、なかなかすぐに対応するのは難しいのではないかと感じました。

ただ、やはりMultiple Windowsが使えるようになれば使い勝手がとても良くなるのは間違いありません。WEARであれば、コーデの検索画面と詳細画面を一度に確認できたり、お気に入りフォルダの中身を確認しながらコーデを追加できたりと、アプリを巡るスピードが飛躍的に上がりましたので。

最後に、Multiple Windows機能をWEARに仮実装して感じたポイントを3つにまとめました。

  • Multiple WindowsのUIの実装はそれほど難しくない。ただし、1つのViewControllerを複数のウィンドウで開くことになるので、UIの更新やデータの保存には注意する必要あり。
  • NSUserActivityに必要な情報を渡し、それを用いて新しいウインドウを開く。ただし、NSUserActivityに渡せるタイプは限られているので注意すること。
  • AppDelegateのライフサイクルメソッドは使えなくなるので、SceneDelegateのライフサイクルメソッドを用いる必要がある。

この記事がMultiple Windows実装の一助になれば幸いです。

参考

さいごに

ZOZOテクノロジーズは、iOSエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください!

tech.zozo.com

カテゴリー