水無月の余韻 開発Sc.

プログラミング関連の雑記

iPadに最適化したビューにトライした

こんにちは。 加賀ゆびぬきシミュレータアプリのiOS版を、iPadで実行するときにより見やすいように改良しました。v1.3.4 大きな変更になったので、いくつかの気づきを記録します。

表示する情報量を増やしたい

加賀ゆびぬきシミュレータのiOS版は、およそ8割がiPhone、2割がiPadで利用されています。(2018年7月実績) 特別な対応をしていないiOSアプリは、iPadで表示したとき、iPhoneで表示しているものをそのまま引き伸ばしたように表示されます。

せっかく画面が大きいのにそれを活かせていないし、拡大されることで間抜けに見えてしまう画面もありました。

f:id:ichiko_revjune:20180909193640p:plain:w300
iPadで表示した場合 v1.3.3

ではどうするか。 単純に情報量を増やすためには、画面を分割して表示する項目を増やしていくことになります。 iPhoneで表示するときとまったく別の実装にはしたくなかったので、UISplitViewControllerを使って両環境での表示をひとつの実装で切り替えることを試しました。

画面分割による複雑さの上昇

f:id:ichiko_revjune:20180909193752p:plain:w300
iPadで表示した場合 v1.3.4

UISplitViewControllerを使うことで、ゆびぬき作成手順の一覧と管理をmasterView、各手順の詳細の表示と編集をdetailViewに表示できるようになりました。UISplitViewControllerの機能に任せることで、iPhoneでは1カラム、iPadでは2カラムの表示切り替えをやってくれます。

表示を切り替えて、中身をそろえていく過程では特に問題に気づきませんでした。しかし、編集機能を追加していったところ問題が見えてきました。

iPadで表示しているときには2カラム表示なので、手順の数を増減しつつ、同時に手順の中身を変更することが可能になります。これらが同時に操作可能であると、手順の中身を編集している最中にその手順を一覧から削除可能です。そうした操作によって不具合が生じやすくなりました。

階層構造を持つデータの親と子を同時に編集できることで、データの整合性を維持するためにロジックの工夫が必要になってしまったのです。

だいたい出来上がってからテストをする段階で、2つのデータ(親と子)の状況の組み合わせで、アプリの状態が決まることに気づき、想定するべき状況の多さに頭を抱えました。

最終的に、親データ(手順の一覧)の編集中には、子データ(手順の中身)の編集をできないようにすることで、アプリの状態を減らして対処しました。

UISplitViewControllerの代表的な使い方は、iOSの設定アプリでしょう。このアプリでは、データの親にあたる一覧(iPadで開いたときに左側に表示される部分)は追加は削除をユーザーが行うことはありません。 もともと想定されている使い方が設定アプリのようなものであれば、今回の加賀ゆびぬきシミュレータでの実装は、想定と違う使い方をしたことで複雑さが増してしまったのかと思います。

そもそも画面分割することで、ViewControllerが増えていくと、各ViewControllerごとにとりうる状態が増え、かつそれらの連携の組み合わせの考慮が必要になるようです。UISplitViewControllerでなかったとしても、画面分割することで、状態の複雑化は発生するので、複雑なアプリになっていくことは避けられないのでしょうか。

画面サイズへ対応して変化するレイアウト

iPhone/iPadの両方で使いやすい画面にするために、それぞれの状態でレイアウトが切り替わるようにしました。

iPhoneでは上部に製図を固定し、スライダーと手順の一覧がスクロール可能になっています。 iPadでは上部に製図とスライダーを固定で表示し、手順の一覧とその詳細がそれぞれ別にスクロール可能にしています。

f:id:ichiko_revjune:20180909193752p:plainf:id:ichiko_revjune:20180909193909p:plain
iPad/iPhoneで表示したときにレイアウトが切り替わる (v1.3.4)

レイアウトの切り替えは、一部はStoryboardで設定するのみで済ましました。SizeClassによって自動的にHideの有無を切り替えるよう指定しています。

当たり前なのかもしれませんが、初めは気づかなかったのが、各ViewControllerが受け取るSizeClassがそのVewControllerが扱うViewのみを対象に計測されているということです。アプリの実行されている画面サイズによるものと考えていたため、UISplitViewControllerの中に分割して表示されるViewControllerが受け取るサイズがRegularではなく、compactである理由がわかりませんでした。

製図とスライダーの表示の仕方を決める条件が、アプリが実行されている画面サイズであったため、ここは実装するしかありませんでした。ViewControllerに用意されている traitChange ??? をRootViewControllerに実装し、そこで受け取ったSizeClassを子ViewControllerへ伝播して、レイアウトを切り替えるようにしました。子ViewControllerが受け取るTraitCollectionとは別の値を使用するために、切り替えも自前で実装することにしました。

まとめ

これまでまともに扱ってこなかった、サイズクラスと画面分割をフル活用してアプリを改善してみました。

目的通りに表示する情報量を増やすことはできましたが、構造的に高い複雑性を抱え込んでしまうという結果になりました。 実際に、使い勝手が向上したかどうかはユーザーのみなさんの反応を待ちたいと思います。

ReadableContentGuide を試してみた

ReadableContentGuide

iOS 9 から導入された、コンテンツ幅を端末や文字サイズに対応した、読み易い幅に設定するものだ。

Safe area とともに、WWDC 2018 のセッションでも触れられていた。

developer.apple.com

Xcode 9.4 を使った場合の動作を試したので記録する。

加賀ゆびぬき刺し模様シミュレータを題材にしたので、キャプチャにあるのは開発中の画像です。Readable Widthの適用がわかりやすいため、iPad 横向きを使用しています。

なお、ここで扱うTableViewでは、UITableViewCellとContentView の "Preserve Superview Margins" をONにしてある。

基本

Xib や Storyboard で、ViewのSize Inspector上で、”Follow Readable Width” のチェックを入れることで有効にできる。 有効にしたViewのMargin(内側)が、Readable width に則って設定されるようになる。

これを有効に使うには、Viewの子要素は、margin に対してconstraints を設定する必要がある。 constraints を作成するときに、“constrain to margins” にチェックを入れた上で親Viewとの位置関係を指定する。

UITableView

TableViewにReadable Widthを適用するとき、”Follow Readable Width” のチェックボックをONにするViewによって、レイアウトが変化する。

UITableViewの基本的な構造の内、ここでは上層の3つを扱う。コンテンツ依存になるContentViewの中身については、基本のルールに従うので触れない。

  • UITableView
    • UITableViewCell
      • ContentView
        • Label などコンテンツの中身

Case 1

Follow Readable Width の設定

  • UITableView: OFF
  • UITableViewCell: OFF
  • ContentView: ON

まず、Readable widthを設定しようとするのは、ContentViewになるだろう。 CellのContentViewにだけ、ContentViewにFollow Readable Widthを設定した。

f:id:ichiko_revjune:20180708200943j:plain

コンテンツ幅は、Readable Widthになり、それらしくなる。しかし、accessary を使用している場合には、accessaryが右端に表示されるため、コンテンツと離れてしまう。 また、2行目以降と見比べると、accessaryの有無で、コンテンツ幅が揃わないという問題もある。

Case 2

Follow Readable Width の設定

  • UITableView: OFF
  • UITableViewCell: ON
  • ContentView: ON

次に、UITableViewCell にも設定する。 これはひとつ前の状態と変化しなかった。

Case 3

Follow Readable Width の設定

  • UITableView: ON
  • UITableViewCell: ON
  • ContentView: ON

次は、UITableViewにも設定しよう。

UITableViewのSize Inspectorには項目がないのだが、UITableViewとUITableViewCellを同時に選択すると、”Follow Readable Width” のチェックボックスが出現する。

f:id:ichiko_revjune:20180708201037j:plain

チェックボックスをONにする。

f:id:ichiko_revjune:20180708201139j:plain

accessaryの位置がReadable Widthの中に収まった。 しかし、今度は、accessaryの左側の余白が気になる。

Case 4

Follow Readable Width の設定

  • UITableView: ON
  • UITableViewCell: ON
  • ContentView: OFF

ContentViewの"Follow Readable Width" を外した。

f:id:ichiko_revjune:20180708201117j:plain

accessary の左側の余白がなくなり、きれいにそろった。 いまのところ、これがベストの設定のようだ。

テストへの視野狭窄に気づいた話をしてきた

とちぎテストの会議 05 で、「テストへの視野狭窄に気づいた話」をしてきました。

発表では省略したこととか、自分用の追記情報を書いておこう。

不具合報告

すごく丁寧な不具合報告をいただきました。 この手順ではできるけれど、少し変えると製図がおかしくなる。という分析までしていただいて、画像つきで報告をいただきました。

画像つきで、報告いただけると、問題の把握が簡単で非常に助かります。

もちろん、画像なしでも、不具合報告いただけると嬉しいです。

アプリの課題としては、ご意見導線を用意しているけれど、活用しきれてないかもしれないです。 画像添付できる方がよいとか、あるかもしれません。

過剰なテストを書かないためには

どうしたら有効なテストだけにできるかな、と考えていたのだけれど、基本的に未来のことはわからないという前提にたつと、はじめから完璧にするのは無理とあきらめたほうがよさそう。 気づいたときに、整理したり消したりできるだけの、柔軟性をもっていられる環境を作る方が有効に思います。

今回話したのは、ひとつの実話でした。 言語化できないけれど、似たようなことにひっかかっていた人たちが、言語化して発信して、行動する助けになればうれしいです。


1年近くブログ書いてなかった。かなしみ。

iOS版の加賀ゆびぬきシミュレータを書き換えているはなし (自分メモ)

(今日は自分メモなので、文章は素です。)

いろいろ集ってきた要望を取り入れて、機能を増やすために、ひさしぶりに加賀ゆびぬきシミュレータをがりがりと書いている。 今回、大きく書き換えをしているので、その経過を残したい。

課題

このアプリのアーキテクチャはほぼMVCである。 そして一番重いのが、いい感じにシミュレート結果を描画する機能である。

もともとEntityとModelがべったりくっついていて、配列の二重管理が発生していたために問題が多かった。 まずは、そこを解消した。

次に、Modelに描画のためのプロパティと処理がかなり含まれていて見通しが悪いのが問題になっていた。 ここはテストが書きにくく、複数の責任を持っていると感じていたところである。

はやりのクリーンアーキテクチャの勉強をしていたのもあり、全面的に書き換えるという妄想も持ったが、問題になっているところだけエッセンスをもらうことにした。 クリーンアーキテクチャでは、ModelをViewのためのViewModelへ変換する層がある。 描画のために必要な情報だけを含むViewModelを作れば、Modelのごちゃごちゃを減らせそうであると考えた。

したがって、今回は、Modelの見通しの悪さを解消するために、描画のためのModelを新たに作ることにした。 データ編集のためのModelと、描画のためのModelを分けるのである。 描画のためのModelはViewModelと呼ぶことにした。

実装

これまでの構造では、データの流れは以下のようになっていて、ViewControllerはModelを書き換えながら描画をしていた。 描画のためのプロパティを Model が持っていたのである。

ValueObject -> Model <--> ViewController

新しい構造では、データの流れが一方通行になって、ViewControllerはViewModelを参照するが、書き換えをしないで描画できるようになった。 描画のための一時的な状態は、ViewController内で完結した。

ValueObject -> Model -> (translater) -> ViewModel -> ViewController

ModelからViewModelへの変換は、translaterで行うことにした。 ViewModelは、状態が変わらないオブジェクトになり、ViewControlelr内の描画処理も複雑さが減った。 ViewController内の処理が簡潔になったことで、描画の不具合は、ほぼModelからViewModelへの変換部分の問題であると限定できるようになった。 ModelからViewModelへの変換のテストを書けるようになったので、問題の検証が容易になった。

その後

現在は、既存機能の書き換えが終わって、予定していた機能追加ができる状態に辿りついたところである。 リリースまではまだ少し時間がかかる。

手続き構造が変わったこと

(ここはコードなしで伝えるのがむつかしかったので、自分向けです。)

今回の書き換えで、手作業をそのままコードにしたような手続きそのままの実装が消えた。

これまでは、手作業に準じていたので、割と条件判断が複雑だった。 人間の作業は、意外に複雑なようだ。

// 描画の疑似コード
- 手順があるだけ繰り返す
   - 糸の色を選択
   - 一段終わるまで繰り返す
      - 一針分すすめる(描画) → 段を変えるべきか?

描画の部分だけで言えば、条件判断が減った。

// 描画の疑似コード
- 描画設定があるだけ繰り返す
   - 一段分の設定を取り出す (色、方向、位置を持つ)
   - 設定に従って描画する

複雑な分岐は、ModelからViewModelへの変換部分へ移動したが、考え方自体が大きくかわった。 行ってみないとわからない、という状態から、未来が予測可能になったような大きな変化だと思った。

// 変換の疑似コード
- 手順があるだけ繰り返す
   - その手順で刺す回数を計算する
   - 糸の色の段数を決める
   - 刺す回数分くりかえす
      - 描画設定をつくる (決めた段数に応じて色を変える)

加賀ゆびぬきシミュレータ Android版をリリースしました

こんにちは。ひさびさにブログを書きます。

Androidユーザーの方々、お待たせしました! mm 加賀ゆびぬき刺し模様シミュレータのAndroid版をリリースしました。

結果的に、開発に一年以上かけてしまいました。 おとなとRubyで、「そろそろ1年経ってしまうので、年内にリリースしたい」と言いながら、1月末になりました。

iOSアプリの開発に比べると、いろいろと戸惑うことが多かった印象です。

  • そもそもちゃんと開発をしたことがなく、iOSアプリ以上に経験値がなかった
  • Theme, Styleの適用ルールを理解するまで時間がかかった
    • 特定にSupport Library から提供されるような、pre-set なThemeの使い方
  • Material Designは理解していないと、使いづらい
  • API versionごとに見た目が変わってしまうのが、どこに境界線があるのかわるまで時間がかかった

Material Design

特にMaterial Designにするのに苦労しました。

はじめはiOSアプリの見た目に揃えるつもりで、色を指定し、各所への適用を地道にやっておりました。 その結果、Material Designとはちょっと違う雰囲気の見た目にしあがっており、やぼったさが拭えません。

今年にはいってから、ちゃんとMaterial Designに乗るために、カラーパレットを設定しなおして、 Theme/Styleを再構築して、どうにからしいところまで持っていきました。

CI

開発を手伝ってくれた @slightair が、deploygateの配信まで繋げてくれるレーンを用意してくれました。ありがとう (-人-) それをベースに、テストやLicense Checkを追加したりして、いい感じに改造しています。

iOS版も簡単にCI配信するようにしたいと強く思いました。

リリース時期の計画

この週末まで、結の会 加賀ゆびぬき作品展が開催されていたので、それまでに出せているとベストだったのですが、 計画が足りていませんでした。

今後

Android版はiOS版に比べると、未実装の機能があるので、順次対応していきたいです。 ただ、iOS版でもあまり利用されていない機能などは対応しなくなるかもしれません。 アプリに対する要望などは、アプリ内から送れるようにしてあるので、遠慮なく送ってくださいね。

iOS版は、Android版の開発中に見つかったバグ修正を予定しつつ、ブラウザ版をどうしようかと悩み中です。

個人アプリで起動できない不具合を出した話

加賀ゆびぬき刺し模様シミュレータv1.2.2でアプリを起動できない不具合を出した。 反省として記録する。

経緯

リリース直後から、Crashlyticsの通知が来はじめて、悪い予感はしていた。 クラッシュ原因は、おそまつなぬるぽで、コードの修正はすぐにできるものの、ユーザー体験のなかでどのタイミングで発生するかはなかなかわからなかった。 実家に行く用事があって、母が「こうするとだめなんだけど」と教えてくれて、ようやくはっきりした。

この時点で、アプリを起動できない不具合であることがわかり、急いでツイッターへお知らせを投稿した。

不具合を修正して、特急審査を要請したところで、これを書いている。

不具合の原因

今回まずかった点は大きく3つある。

  • nullチェックが抜けた
    • APIの理解不足
  • データ読み込み処理が、フェールセーフでない
    • 意図していないデータが入ってきた場合を考慮していなかった
    • オートセーブデータのロードに失敗するのが今回の現象だが、ユーザーが意識しないものなので、なにかマズいときでもクラッシュしてはいけない
    • force castを使用している箇所があり、想定外の状態に弱い実装だった
      • 対策: swiftlintでまずそうな箇所を検出して対応した
  • 後方互換性のテストを欠いていた
    • 開発バージョンを間にインストールしていたため、リリース版v1.2.1 -> v1.2.2の状況を再現できていなかった
      • 対策: deploygate で各リリースバージョンを保持し、後方互換テストをできる環境を用意した

課題:「ここは何かおきても、例外を握りつぶしておきたい」という仕様に対する実装方法

今回の問題の箇所は、Objective-Cで実装していたため、汎用的な例外キャッチを局所的に適用することで、 「なにがあってもクラッシュしない」を実現した。

Swift実装の場合、Swiftの例外処理が、「例外を投げてくる処理」に対してのみ有効なので、 「なにがあってもクラッシュしない」を実現できないような気がしている。

加賀ゆびぬき 刺し模様シミュレータを更新しました。(v1.2.1 バグフィクス)

こんにちは。

iOSアプリ「加賀ゆびぬき 刺し模様シミュレータ」を更新しました。

このアプリは、加賀ゆびぬきの刺し模様をデザインするツールです。

どんなアプリかについては、加賀ゆびぬき 刺し模様シミュレータをリリースしましたをご覧ください。

アプリをダウンロード

リリースノート

今回は、アプリがクラッシュする問題に対応しました。

ご迷惑をおかけして申しわけありません。

アプリをアップデートしてご利用ください。