こんにちは、Megabits です。今年の二月に STORES ブランドアプリ の iOS チームでインターンに参加しました。この一ヶ月でやったことと感想を皆さんに紹介したいと思います。
この後特にアクセシビリティ関連の私の経験談もいろいろ書きました。インターンの話に興味がある方も、技術に興味がある方も楽しく読めると思いますので、ぜひ最後まで読んでいただければと思います。
私がインターンに来ました
プログラミングの仕事をしに来ましたが、実は私はデザインの学科に所属しています。そして、留学生です。
日本に来る前に、高校生の時から自分でアプリ開発を独学して、個人開発者として活動してきました。個人開発なので、デザインも自分でしなければなりません。それでだんだんデザインにも興味を持つようになりました。美大は流石に入れないので、普通の大学のデザイン関連の学科に行きました。大学でデザインを勉強しながら、自分でプログラミングを独学しました。そもそも美大ではないので、デザインの仕事を探すよりもアプリの仕事に自信があります。なのでやっぱりエンジニアとして働くことに決めました。
STORES を知ったきっかけは iOSDC でした。私は勉強会が大好きなので、よくカンファレンスに行きます。なので、仕事もカンファレンスで探したほうが早いかなぁと思いました。カンファレンスに参加している企業だと、レベルが保証できますし、その場で社員と話す機会もあります。なので、前回の iOSDC で STORES のアプリを触りました。学校が忙しいので、すぐではないですが、後で応募することにしました。
STORES の採用ページを見たとき、この会社が持っている価値観がとても良いと思いました。私は自由と平等をかなり大事にしているので、ダイバーシティを目標として採用ページに出しているのがとても良いと思いました。Just for Fun のミッションと三つのバリューも自分に響いてるので、試してみようと思いました。卒業したら何年も働く会社なので、直接行くよりはインターンに参加した方が会社の雰囲気を確認できるので、インターンに応募しました。
受け入れ
初日恵比寿にある東京オフィスに来ました。ちょっと早く来たので、まだ誰もいません。なので空いてるところに座りました。今回インターンで来たのは私だけらしくて、後で来た人は全員新入社員です。受け入れの人が来る前に、皆ちょっと自己紹介して、所属するチームとかいろいろ話しました。その中に同じ大学の人も何人かいるのは意外でした(違う専門ですが)。
受け入れに来たのはPXチームの皆さんでした。オフィスに移動して、会社の説明していただきました。初日の PC 設定とか、アカウントの設定とかもやりました。説明の中に、取締役の佐俣さんもきて、経営と企業文化について話いただきました。皆さんがとても親切で、楽しく働いてる印象がありました。
私のチーム
新入社員の皆さんは入社に関する作業があるため、その必要がない私はチームの方に移動しました。そこで待っているのは STORES ブランドアプリ の iOS を担当している @marcy731 さんと @enomotok_ さんでした。そして Android を担当している @tomorrowkey さんもいました。
仕事の準備をする時、ちょっと面白い話がありました。
私のパソコンに関して、最初慣れない日本語配列のものをもらいました。以前他社でインターンする時、配列の指定ができなかったので、チームと会う前に、何も言わなかったです。その後指定可能って聞いて、交換したいと言いました。なので、朝から PC を設定したのに、またゼロからやらないといけない状況になりました🤣。幸い、IT チームの皆さんが爆速で対応していただいて、新しいパソコンほぼすぐ来ました。私も爆速で PC を設定し、残業なしで初日の設定を完成しました!
最初の仕事
私の最初の仕事は、ポイントカード画面の説明アラートをモーダルに改修する仕事でした。まだチームの習慣がわからないので、存在しているコードを読みながら、自分の勘で作りました。会議でリリースの話もありましたので、今回のバージョンに間に合ったらいいなと思って、爆速でやりました。
しかし、まだそこまでコードを読んでいなかったので、レビューでいろいろ修正点がありました。ちょっと迷惑かけて申し訳ないですが、これは決して悪いことではないと自分の中で認識しています。やっぱり一番早い勉強法は失敗から勉強することなので、たくさん修正したら、注意すべきところを早めにわかるようになります。これでプロジェクトで使用しているデザインパターンとか、コードの習慣を大体理解することができました。
成果はこれです:
before | after |
---|---|
こういう感じで、 marcy731 さんと enomoto さんが私に Issue を渡して、私がやって、又お二人とデザイナーの @_matsu0ka さんがレビューする形で、仕事を続けました。
それでは、インターンの期間中に私の出した成果を紹介します。
SwiftUI への移行
インターンに参加する前、会社で何をやりたいか聞かれました。正直私もそんなに考えていませんが、SwiftUI をやりたいことは確かです。なので、SwiftUI へ移行する仕事をいろいろしました。
チームの皆さんは、新機能の実装に時間を使っていたので、SwiftUI 化にかける時間はあまりありませんでした。なので、SwiftUI 化をするのは、私と私のチームにとってかなりやりがいのある仕事かもしれません。
現状のコードは、RxSwift を使用した MVVM でした。昔 RxSwift を使用したことがありますが、二、三年前の話になるので、思い出すにはちょっと時間かかりました。
私が移行した画面は以下になります:
- お知らせ
- プロモーションコード入力
- パスワードリセット
- ログイン
- 新規登録
現時点でインターンはまだ終わってないので、また増えるかもしれません。
移行する際、既存の実装と最新のデザインと違う場所もあったので、できるだけ最新のデザインに沿って書きました。
昔 UIKit で簡単で作れるが、SwiftUI で同じものがないものもありました。例えばキーボードの部分で UIPicker を表示する API が SwiftUI でありません。私は、無理やりに UIKit と同じ感じで作るより、実装しやすい新しいデザインにしたらどうかなって思いました。なので新しい Picker を作ってみてデザイナーの matsu0ka さんと相談しました。
matsu0ka さんはテキストフィールド全体のデザインをあらかじめ決めたいので、待ってほしいと言いました。なので、現在私は自分が作った Picker を別のブランチに置いて、これからデザインが決まったら簡単で使えるようにしました。
String Catalog
SwiftUI へ移行すると同時に、String Catalog への移行もしました。そこでかなり微妙な状況が発生しました。皆さんが知っている通り、String Catalog の項目は、ビルド中に自動で生成される仕組みになります。しかし、SwiftPM だと生成されません。
もっと微妙なのは、ビルド中生成されないけど、何か知らないうちに、Git の差分が出ちゃいます。その原因と差分が出るタイミングが、今でもわかりません。
String Catalog のため、現在の SwiftUI では、LocalizedStringKey というものが導入されました。私たち普段 Text("内容")
を書く時、実は init(_ key: LocalizedStringKey) を使用しています。なのでよく要らないものが String Catalog に追加されます。例えば "%@"、"%@:%@" とか、かなり邪魔です。
実は、それを消す方法があります。Text を使用するとき、Text(verbatim: "内容")
を使えば大丈夫です。verbatim をつけたら、「内容」が LocalizedStringKey ではなく、String になります。他の文字列が使用している View でも、ラベルの中に Text を使用することで除外できます。
Toggle(isOn: $state) { Text(verbatim: "Title") } .accessibilityLabel( Text(verbatim: "内容1, 内容2") ) //アクセシビリティについて後で説明します。
しかし、これを全部やったとしても、最後絶対どうしても見つからないものが残します。String Catalog からの逆引き機能を Apple が早く開発してほしいですね。
アクセシビリティの改善
marcy731 さんと 1 on 1 する時、アクセシビリティの話に触れました。チームがアクセシビリティ関連を改修する時間がなかなか取れなくて、私もめっちゃやりたいので、いろいろ改善してみました。
私は個人開発していますので、さまざまなユーザーからフィードバックを受けています。その中に目の不自由な方もいます。なので、私は結構前から、自分のアプリのアクセシビリティを頑張って改善しています。目の不自由の方専用のアプリも作ったことがあります。もし STORES ブランドアプリ のアクセシビリティを改善したら、全てのお客様のアプリに適用されますので、多くの人に影響します。なので、絶対これやりたいと思いました。
Dynamic Type
簡単にいうと、アプリ全体の文字サイズを拡大する機能です。iOS の環境設定で一回設定したら、対応している全てのアプリの文字が大きくなります。これは、目が完全に見えない方ではなく、ある程度見えるが、見づらい方のための機能です。年配の方がオンにしていることが多いでしょう。
実は SwiftUI の Dynamic Type を触るのが初めてなので、いろいろ調べました。まずはシステムで設定するとき、アプリの内部で即反映するため、環境変数の追加が必要です。この変数直接使わないけど、追加するだけで、OS の設定を反映できます。
@Environment(\.dynamicTypeSize) var dynamicTypeSize
そしてフォントを設定します。UIFontMetrics.default.scaledValue
を使って、拡大したサイズを計算します。
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 12)))
STORES ブランドアプリでは、フォント設定をある Modifier で統一しているので、一回追加するだけで、全ての SwiftUI View に適用されます。これから SwiftUI 化の進みとともに、アプリのアクセシビリティも改善されます。
off | on |
---|---|
VoiceOver
皆さん VoiceOver を日常で使ったことはありますか?多分使ったことがある人は少ないと思います。VoiceOver は画面上のものを読む機能です。ほぼ見えない方が使っています。
Xcode では Accessibility Inspector がありまして、アプリが読ませる時の様子をシミュレーションできます。しかし、より良い VoiceOver の体験を作るには、自分で使ってみる必要があります。
目が見えない状態で使う UI を作るので、目が見えない状態でテストする必要があります。自分のアプリを触ってみると、いろんな問題が発見できます。なので今回私もとりあえず VoiceOver の状態でいろいろ触りました。
VoiceOver でよくある問題は大体これらのものになります:
- 読めない画像ボタンがある。
- 読んだものの役割がわからない。
- バラバラで読んで関連性と順番が分からない。
- 内容がないけど、フォーカスできるものがある。
- 数値と日付の読み方が理解しづらい。
これからその解決策を説明します。
適切なラベルをつける
この三つは多分アクセシビリティで一番よく使うものになるでしょう。
.accessibilityLabel("内容1, 内容2")
まず、Label は View の内容を示しています。フォーカスするとき一番最初に読むものです。複数の内容があるとき、コンマで分けると良いでしょう。コンマをつけると、VoiceOver が読む時、ここで区切りをつけるようになります。
.accessibilityHint("内容の役割")
そして Hint は内容だけを見ると役割が不明の場合、役割の追加説明を書くものです。普段読まれなく、ユーザーが View で長く止まったとき読まれます。止まったのは、ユーザーが戸惑ったからという認識ですね。
.accessibilityElement()
そもそも VoiceOver が読めるものとして認識していないものにつけます。追加するとフォーカスできるものになります。
.accessibilityHidden(true)
逆にこれを付けると、VoiceOver に無視させることもできます。
.accessibilityAddTraits(.isButton)
View の特性を示す必要がある時もあります。例えば、自作のタブバーを使用した時、ボタンではない View にタップのアクションを付けた時。VoiceOver から見ると、普通の文字に見えますので、これを追加しましょう。できれば、iOS の標準 View を一回体験して、同じ動作をするように追加しましょう。
関係のある View を同時に読ませる
例えばこの View で、複数の文字があります。しかし、文脈が一緒なので、ただ見やすいために配置しただけです。見る人には親切ですが、VoiceOver で読む時その関係性がわからない場合があります。その時、二つのやり方があります。
.accessibilityElement(children: .combine)
まずは、View の内容を合成する方法です。これで View の内容が連続で読ませることになります。
.accessibilityElement(children: .ignore) .accessibilityLabel("\(内容1)", 内容を繋がる文言, "\(内容2)"))
しかし、これだけでわからない場合もあります。その時 View の内容を無視し、あらかじめ説明を付ける方法もあります。
日付の読み方を正しくする
日付を書くとき、私たちはよく 2024/2/1 みたいな書き方をしています。見る時は普通ですが、読むと理解しずらいです。「二千二十四、スラッシュ、二、スラッシュ、一」になります。なので、別でラベルを指定した方が良いでしょう。
DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: .autoupdatingCurrent)
Swift では、こういうテンプレートで DateFormatter を作ると、読ませるための日付を返してくれます。つまり「二千二十四年、二月、ついたち」になります。
時間の読み方は一定の決まりはないので、API がありません。日付以外、時間も必要な場合、自分で文字列を構造する必要があります。
その他
大きな変更以外、細かい変更もいろいろありました。既存の実装では、改善できるところがありますが、皆見慣れているので、変だと感じないです。逆に私は触ったことないから、細かい変なところに敏感ですね。なので変だと思ったところあれば相談して、空いてる時間にいろいろ修正しました。
インターンで勉強になったこと
私は今まで大体個人または案件でアプリを作りました。なので、企業のアプリがどういう構成なのかよく知らなかったです。なので、今回インターンを参加する一つの目的は、規模が大きい、そして長くメンテナンスする必要があるアプリの作り方を見ることです。特に SwiftUI 関連は、まだ一定のルールがないものが多く、皆さんはどういう感じで SwiftUI を使用しているかも見たかったです。
なので、作りながらいろいろ観察しました。
- 動的情報を外した TemplateView を作ってプレビューで使用する。
- 複数のターゲットがある時、差分のないコードを全部 SwiftPM で管理する。
- 共通のラッパー(HostingController)を用いて、SwiftUI から UIKit のナビゲーションを参照する。
- ...
これらの知見を将来自分のアプリにも使ってみたいと思います。
技術の話以外、プロジェクト管理の知識も勉強しました。スクラムという概念は全然知らなかったので、とても勉強になりました。
最後の感想
インターン来てよかったです。STORES ブランドアプリチームでの成果は、これからすべてのお客様のアプリに適用されますので、とても達成感がありました。チームの皆さんは私の質問に詳しく回答していただいて、何か間違いがある時も優しく指摘していただいて、スムーズに開発できました。
一ヶ月とても早いですね。私は大体 25 PR/ブランチ 出しました。
これから三月の try! Swift もありますので、皆さんまた会いましょう!