TableHeaderViewのサイズをいい感じにする

UITableViewのtableHeaderView(セクションごとのヘッダーではない)は経験的にオートレイアウトが効かないので、状態によってTableHeaderViewの高さを変えたい厄介になります。この記事ではサイズをいい感じに調整する方法を紹介します。TableFooterViewも同様です。

やり方

TableHeaderViewには一つだけViewを置いてください。このVIewのことを便宜上BaseViewと呼ぶことにします。BaseViewはUIViewでもUIStackViewでも構いません。重要なことは1つだけにすることです。

BaseViewを上と左右に0でAutoLayoutを設定して、BaseViewの中身によってBaseViewの高さが決まるようにしてください。

ViewControllerに以下のコードを追加してください。

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let headerView = tableView.tableHeaderView,
        headerView.subviews.count == 1,
        let baseView = headerView.subviews.first
    {
        headerView.frame.size.height = baseView.bounds.size.height
        tableView.tableHeaderView = headerView
    }
}

終わりです。

なぜいい感じになるのか

厳密には違うかもしれないが、viewDidLayoutSubviewsはオートレイアウトによって調整が行われるたびに呼び出されるメソッドです。BaseViewの高さがオートレイアウトによって変更されるたびに呼び出されてBaseViewの高さとTableHeaderViewの高さを同じにすることで、TableHeaderViewの高さを調整しています。

BaseViewの高さがTableHeaderViewに影響されないように、下にオートレイアウトをつけないのはそのためです。

ソースコードをお焚き上げしました

エンジニアは意外にも信心深いところがある。サーバーをお祓いしたり、デバッグ神社を建てたり、リリースするときにお祈りする。

iOSAndroidアプリを全面リニューアルと一部サービス終了があったので、今までのソースコードへの感謝の気持ちを込めてお焚き上げをすることにした。

今まで稼いだコードへの感謝

弊社では、アプリを全面リニューアルすることにした。

外注を行っていたのを内製化するにあたって、中途採用が行われチームができた。人を雇って改善を行っていこうと思うほど、重要なアプリになっていた旧アプリはそれだけの価値があったということになる。

内製化を行ったチームは、初めから内製化をし始めたわけではない。初めにやったことは内製化の価値を示すためにチームは新しいサービスを立ち上げた。サービスは今までには考えられないほどのスピードで開発されチームの価値が示された。ただ、そのサービスも期間限定で好評のうちに終了した。

私たちは、今まで稼いでくれていたが不要になったソースコードに感謝の念を示したいと考えた。検討した結果、お焚き上げをすることにした。

準備

ソースコードの持ち出しの許可をとる

即座にお焚き上げをするとはいえソースコードを無断で持ち出すのは問題がある。書類整理破棄を名目でCTOに持ち出しを打診したところ「お焚き上げをアンオフィシャルでやるならいいよ」と許可をもらった。お焚き上げは有志で行うことになった。

場所決め

IT系の神社といえば神田明神だが、ホームページにはお焚き上げに関する記載がなかったのと、 神札·御守·熊手·破魔矢等のみとのツイート があった。優先順位をつけて探すことにした。

  • 目の前でお焚き上げをしてもらえること
  • 場所(都内〜遠くても関東)
  • 予算(〜数万円程度)

同僚に探してもらったところ、つてで 戸越八幡神社 で行うことになった。ダンボール1箱程度で10,000円だった。割り勘で払うことにした。

印刷

前日に印刷を行った。その辺にあったMacbook Proの箱に詰めた。 そこまで多いソースコードだと思っていなかったが4pt2ページづつ両面印刷して、ほとんど米粒程度のフォントサイズにで印刷されてなんとか見える程度だった。しかしそれでも500枚程度になったので、もしやる機会がある人は気をつけてほしい。Macbook proの箱はちょうどいいサイズの段ボールでできているので、燃えるしどこの会社にもあるのでおすすめです。

当日

10時に戸越八幡神社に集合した。眠い。昇殿参拝を行って継往開来で祈祷をおこなってもらった。お清めされたドラム缶でお焚き上げをおこなった。 燃えていくソースコードを眺めていると「終わった」という気持ちに満たされていった。

お清めされたドラム缶
炎上してる

おわりに

リニューアルやサービスの終了時にお焚き上げするのをお勧めします。

最後に、感謝の念を込めながらGithubアーカイブのボタンを押して幕を閉じた。

仕事しながら考えていること

プロダクトの価値

プロダクトの価値をできるだけ高速で高めることが大切だと思っている。プロダクトの価値を高めることができれば、手段を選ばない。違法なことや倫理に反すること以外はやる。
プロダクトの価値は、主な評価軸として主に3つあり上の方が重要だと思っている。 * 企業への利益 * ユーザーへの価値 * プロダクトのリリース速度

企業の利益がなければプロダクトが継続できず、ユーザーへの価値がなければ企業の利益が得られない。両方を高めるために、プロダクトのリリース速度を高めます。

企業の利益

ようわからん。雰囲気でやってる。

ユーザーへの価値

全てのユーザーがどう感じるか。仮想のユーザー10人がどう見るか想像しながら作っている。実際、出してみないとわからないところがあるので、さっさと出すことを考えている。事前にユーザーへの価値が高いことがわかっている方が好ましい。事前調査やスモールスタートして、大きくする前に調べるべきだと思う。

プロダクトのリリース速度

チーム開発の本を読んだ方がいいとは思うが、書いておく。

失敗することはどうでもいい

失敗は誰にでもあるし、自分もする。失敗したことをくどくど言ってもしょうがない。重要なのは、できるだけ高速で治す事と失敗を繰り返さないことである。怒ったとしても今困っているユーザーは困りっぱなしなだけです。

失敗しても直せればいいと考えているので、他人が失敗しそうになっていてもほっといたりします。失敗しないと聞かないし。

人間が頑張らない

途中で人間が挟まるから失敗するのであって、できるだけ挟まないようにしたり、ツールによるチェックを挟むことで失敗をできるだけ避けるようにするべきです。考えるべき順番としては、

  • 自動化
  • チェックツールを導入する
  • チェックリストを使う
  • 気持ち(気をつけるとかそういうの)

です。気持ちで解決しないようにする。

自分一人で頑張らない

自分一人で頑張っても割とどうしようもないことが多々ある。たとえ、毎日4時間残業したとしてもせいぜい1.5倍にしかならない。休日出勤したとしても2倍程度だろう。なら、会社としては2人に増やして分担した方が一人の負担は少ない。分担できるように考えた方が得策なので、一人では頑張らないし、時間的に不可能を可能にしようとしないように考えている。チームの仲間や外注する等をしていった方が良い。

頑張らないためには、自分の実力を把握する。限界や単位時間で自分ができる作業量を把握しておく。

他人に頑張らせない

ほとんど「自分一人で頑張らない」と同じだが他人にも頑張らせないように気を使う。分担できるように考えさせる。一人でやりたいというならほっとく。

作業を分割する

作業の総量の把握、分担、順番に片付けるために分ける。
可能な限り分割して順番に行っていく、一つ一つ片付けで手戻りを少なくする。
手戻りしても、深呼吸して忘れる。

アジャイルソフトウェア開発宣言の左側もちゃんとやる

アジャイルソフトウェア開発宣言

左側をしないとか、重要視しないとか勘違いされがちだが、重視しないだけでしないわけではない。 プロセスやツールで解決できることがあるのであれば、プロセスやツールで解決すればいい。
ドキュメントを書くべきであれば書く。

チーム

チームは自分たちが担当しているプロダクトに責任をもつ必要がある。プロダクトに責任を持っていることによって権利を得るべきだと思う。

チームはいくつかの権利を持つべきである * 独立した意思決定をできる * 管理できるプロダクトの個数や大きさを保つ * プロダクトを全て開発・運用できる

トラックナンバーを気にする

自分が癇癪起こして辞める 誰かが病気になる可能性があるので、作業途中でも引き継げるようにする。チームで作業するのも同様に作業途中で引き継げるようにするため。少なくとも引き継いだ時の作業ロス時間を1時間未満にしたいと考えていて、少なくとも1日未満を保った方がいい。

定期的な作業は少なくとも2人以上できるようにして、ドキュメントを書いておく

iOSアプリをリニューアルした時のアーキテクチャ

最近、iOSアプリをリニューアルしたのでその時に工夫した内容を書いておこうと思う

前提

  • 仕様書はない
  • iOSアプリでObjective-Cで書かれている
  • Swiftに全てのコードを書き換える。SwiftUIは使わない。
  • 複数アプリが一つのソースコードでプロジェクトで管理されている

最後の部分はあまりないがほとんど同じアプリがコピペで別のリポジトリで管理されるよりはマシな状況である。二つのBuild SettingsでTarget Membershipで分けるのは、読み取りづらいものになっていた。

解決したい問題

  • 典型的なBaseViewController 問題がある
  • FatViewControllerでもある

巨大なViewControllerにベタが記されていて、何が行われているか見通しが悪くなっていた。

解決方法

ViewControllerから処理を剥がすためにフレームワークに分けて、それぞれの役割をはっきりとさせたかった。単純に分けただけでは役割とは異なる箇所にコードを書いてしまうことができるため、フレームワークごとに依存するライブラリを限定して、役割に合っていないフレームワークで書くことを難しくした。さらに、フレームワークごとの依存関係を限定することで、間違った使い方をできないようにした。

3つのフレームワークとアプリに分ける

DomainとInfrastructure、PresenterのフレームワークとそれをまとめるApplicationのアプリに分けることにした。

DomainとInfrastructure、Presenterはそれぞれの役割に集中できるようにフレームワークに分離して、依存する対象をはっきりさせるよにした。たとえば、InfrastructureからPresenterは依存してすることはプロジェクトの設定で絶対にできないようになっている。

Applicationは、複数のアプリの差分(アプリのアイコン等)を持っており、コードの量は最小限になるようにした。

Domain

ドメイン駆動設計で言うところのDomainのこと。ビジネスロジックを可能な限りModelに書くようにした。Respositoryはprotocolになっている。Respositoryを経由してModelを取得するようにしている。 DomainRegistoryは、全てのRepositoryのオブジェクトを持っているか生成するのが役割です。 DomainはRxSwiftのみに依存している。SwiftUIを採用していればRxSwiftに依存せずに別の設計になっていたかもしれない。

import RxSwift

public struct XxxModel {
    let value: String
}

public protocol XxxRepository {
    func load() -> Obserable<Model>
}

public protocol DomainRegistory {
    var xxxRepository: XxxRepository { get }
}

Presenter

Presenterは、主にDomainとUIKitに依存している。
DomainRegistoryが持っているRepositoryを使ってDBや通信の操作するようにしました。これによって、Presenterは本番環境から取得されているか、ステージング環境か、モックかを気にすることなくUIに集中できるようにしました。

Infrastructure

Infrastructureは、主にDomainとDB、API通信クライアントに依存している。
RepositoryImpがRepositoryの実装になっている。通信やキャッシュ、Entityへの変換を実装している。

Application

ApplicationはDomainRegistoryの実装であるDomainRegistoryImpを作成と、環境変数の注入が主な役割である。各アプリ特有の設定や本番とステージング環境の切り替えを行なって、InfrastructureやPresenter現在の実行環境がどうなっているかを気にしなくていいようする役割である。

実際どうだったか

全体の見通しは非常に良く、どこになくを書くべきかが明確なので、失敗しづらい構造になっていました。
今回のアプリはAPI通信で取得したデータをキャッシュして表示する画面がほとんどだったので、ほとんどがアーキテクチャに合わせて構築するだけの部分が多かったため、大きく問題になることはありませんでした。ユーザーの入力が複雑になった場合にRepositoryが複雑になるかはわからなかった。

やらなかったこと

  • 流行りDI コンテナ
    • ここには書かなかったが、RxSwift等を導入したため一同導入するものを減らしたかった
  • SwiftUI
    • iOS12をサポートすると決めていたため

Pull Requestに画像を貼るときはテーブルで書く

UIを変更したら、Pull Requestに変更前と変更後の画像を貼るとレビューしやすいが、非常に縦長になって逆に見辛くなりがち。特にスマートフォンのスクショ。

以下のように書くことで、MarkdownのTableレイアウトで画像を横に並べることができます。

|before|after|
|:--|:--|
|画像1|画像2|

なぜドキュメントを書くのか

なぜ書くのか

記憶は当てにならない

人間は忘れるし、記憶改変が起こるし、病気になったり、トラックにひかれたりする。トラックに引かれなくても、部署変更や転職によって知っている人がいなくなることが往々にして発生する。文章によって残してことによって、忘れたり記憶が改竄されたり伝わることはなくなる。 昔からある機能や成り立ちがわからなくなる。わからなくなったことによって、間違った方向に進んでしまったり、同じ失敗を何度も繰り返す。失敗したことややらないと決めたことを書くことで失敗を繰り返さないようにできる。

ドキュメントを書かないのがアジャイルではない

agilemanifesto.org アジャイルソフトウェア開発宣言では、「包括的なドキュメントよりも動くソフトウェアを」とありますが、ドキュメントを書かないのがアジャイルではない。アジャイルだからドキュメントを書かなくてもいいとは書いていない。ドキュメントもインクリメントに含まれる。

いつ書くのか

知らないことを聞いたとき

初めて聞いたことをドキュメント化していくことで、同じように知らない人がいたときに説明しやすくなる。

知っていることを聞かれたとき

自分が知っていることが、必ずしも他の人が知っているとは限らない。業務上で必要な知識であればリンクを貼っておくだけでもいいので、ドキュメントとして残してあると検索できり経緯がわかる。

貸金業務取扱主任者に合格しました

国家資格でそれなりに難しい。登録しないと名乗れないけど、あまり登録する気はないので名乗りません。

貸金業で開業とかできるようになったけれど、開業には少なくとも5000万円が必要なので、5000万円ください。