「読む側」からのコミットの絵文字👍ルールの話

はじめに

Gitのコミットをわかりやすくする為の手段の一つとして、コミットの頭にその種類を示す絵文字等の記号をつけるというルールがあります。

弊社 iOSチームではこの絵文字ルールを導入してから5年程が経過していますが、単に導入するだけでもメリットがあると思う一方で、細かい部分で特に意識してもらう事でよりわかりやすくなるようなポイントもあり、それはこのルールの導入意図や背景を含めた形で伝えた方がより理解がされやすいと思った為、改めてその辺りについて簡単にまとめようと思います。

絵文字ルールの方法自体は先人のわかりやすい記事がたくさんありますので、そちらを参考にして頂けると良いと思います。

モチベーション

コードレビューをするのって大変だし難しいですよね?(少なくとも僕はそうなのですが)

チームで開発する上で、多分?誰もが意識しているであろう「コミットをなるべく細かく行う」という指標があります。
これはチーム開発だからこそ特に求められるもので、一人で開発するのならばそんなに強くは意識しないですし、必然性も無いと思います。
例えば1プルリクエスト1コミットで200行変更とかでも、一人の開発ならばまあ特に問題にはならないでしょう。(もちろん細かい方が、revertとか cherry-pick出来るとかのメリットを享受出来ますが)

ここで僕が言いたいのは、ことこのルールをチームメンバーに求めるというのは、コードをレビューする側「読む側」にとってそうして欲しいからだという事です。

そして、それを端的に実現しているルールとして、コミットの絵文字👍付与ルールがあります。

背景

当初、例えばメンバーのコミットで、「〜の機能変更」と書かれた数十行にわたる変更がありました。
しかし実際に変更があった箇所を注意深く探していくと、結局の所特定の1行だけがその挙動変更を実現するものであり、それ以外はリネームや並び替え等、「〜の機能変更」とは直接関係ないものだった・・・
というような事が割と日常的にありました。

この状況を改善すべく、各コミットの適切な分割単位を意識する事を出来るようにする為に、絵文字ルールが導入されました。

どの絵文字をつけるのが適切?

先人の絵文字ルール系の記事を見て貰えば絵文字の種類は10種程度はありますし、どんなコミットにもどれかが適切に当てはめられそうな気がします。

しかし実際に運用してみると、割とどの絵文字を割り当てるべきか迷う事も多いです。
実際メンバーによって、特定の種類の変更に対する絵文字のつけ方が異なる例も多くあります。

ただ、それがことさら問題だと言いたいわけではありません。
目的は「コミットが適切に分けられて」いる事によって「読みやすい」変更になっている事であり、実際につけられた絵文字が適切なのかというのは二の次なのです。(もちろん適切である方がより良いですが、これの正誤で議論するのは行き過ぎると不毛になりかねない部分です)

とはいえ、数ある絵文字の中でも大雑把に性質の異なる区分があり、そこの違いはなるべく厳密に分けられていて欲しい、という事をここでは説明したいと思います。

「読みやすい」という事

しかし、実際のところ「コミットが適切に分けられて」いて「読みやすい」というのはどういう事なのでしょう。
極端な事を言えば、読む人が個別のコミットを見ないで、プルリクエスト丸ごとの差分を見るレビュースタイルならば、コミットを細かく分けているかどうかなんて全くどうでもいい事なわけです。

つまり、「読む側」の状況を知らない限りは、「書く側」にとって「読みやすい」という事はわからないし、細かく分ける意義もわからないという事でもあります。

「僕」にとっての「読みやすい」

ここまで「読む側」が云々と書いてきましたが、要するにここで書いている事は「僕」にとっての「読みやすい」であり、詰まるところ「俺の読みやすいようにコミットを書けコノヤロウ!」という暴論でしかない内容でもある事を断っておきたいです。

実際に僕の基本的なレビュースタイルがどういうものかというと

  • 1コミットずつ全部の変更を見る
  • 書き手の変更意図を把握し、それに沿った変更であるかを見る

という形です。
これを前提に、この見方をする上で絵文字ルールで特にメリットが大きい点がどういう所にあるのか、どうしてこれを厳密に分けて欲しいのか、という事について伝えていきたいと思います。

絵文字👍ルール

絵文字ルールでは沢山の絵文字が定義されていますが、その分類は大きく3種類に分ける事が出来ます。

  • 👍: アプリ挙動が変わる(機能追加, 変更)
  • 🎨: アプリ挙動変更無いが、コード上のロジックに変更がある(リファクタリング)
  • ✏️: コード上のロジックに変更が無い(並び替え, リネーム)

その上で、これらの変更がなるべく分けられている事で、読みやすいコミットとする事が出来ます。

他にもバグ修正、新規機能追加、削除、テスト追加等、いろいろな絵文字定義がありますが、これらは大きな意味では上の3つのいずれかに分類する事が出来ると思いますし、それぞれ変更としては割と特徴が強いので、絵文字の設定が適切でなくてもまあ見ればわかる、というようなものが多いと思っている為、今回はそれらの区別は割愛します。

レビューの視点と難易度

例えば、「背景」に書いたような例ならば、あまり深く考えなければ「アプリの挙動が変わっている」という事実を元に

  • 👍: ~の機能変更

という1コミットで表現する事も出来てしまいます。
しかし、実際は以下のように変更を分けて欲しいです。

  • 👍: ~の機能変更 (差分は1行のコミット)
  • ✏️: hogehogeを hugahuga にリネーム
  • ✏️: ~の処理の記述位置を移動

正直このくらいの例では、「そりゃ分けた方がいいじゃろ」とみんな思う筈だとは思いつつ・・・
ここで言いたい事として、これら3種類の変更は、それぞれレビューの注視するべき視点や難易度が異なってくるという事です。

レビュー難易度の低い変更

例えば、「✏️: ~の処理の記述位置を移動」 について、この変更について1行ずつ、書かれたロジックについて理解しながら読むでしょうか?
多くの人はそうではないと思います。
移動したコードなのだから、その「移動の仕方」に挙動が変わるような点がなければ、挙動は以前と変わらない筈です。

もちろん、書かれたコードを都度見直すというのは、より良いコードにする事に繋がる可能性がある側面もあります。
そこにもしかしたらバグが元々含まれているかもしれません。
しかし、そのコードは過去に既にレビューを通っている筈であり、これまで動いてきたコードでもあります。
これを「記述位置が移動」した事を理由に再びじっくりレビューするというのは筋が通らないし、非効率的です。

つまり、「✏️: ~の処理の記述位置を移動」 はレビューの難易度としては低く、読み飛ばして良い変更だという事になります。

同じく、「✏️: hogehogeを hugahuga にリネーム」についても、「名前の変更」の部分だけを注視し、それ以外の変更が入っていない事が確認できれば、ほぼ読み飛ばす事が出来るでしょう。 これも同様に難易度としては低い変更です。

レビュー難易度の高い変更

一方で、「👍: ~の機能変更」についてはどうでしょうか。
これは挙動が変わっている変更なので、以下のような視点になります

  • 意図した通りの挙動変更が実現出来ているか
  • 意図した以外の挙動変更が起きていないか(この変更による副作用で他の挙動が壊れていないか)

この判定はなかなか難易度の高いものになります。
バグが仕込まれる可能性も高いでしょう。
この時に変更行数が1行であるならば、その実現方法が妥当で、バグがなさそうか、といった判断はそれほど難しくは無いでしょう。

しかし仮にリネームや並び替えのコミットが入っていて、挙動が変わっている1行の特定が困難であったら・・・?
レビュアーの視点からすれば、挙動が変わっているのが1行とは最初の時点ではわかっていないのだから、それを判断する事の難易度は高くなり、余計な時間と労力を使う事になるでしょう。

こうして欲しい

上記のように、変更の種類によって難易度が異なってくるので、なるべく差分としては難易度の低いものを多く、高いものを少なくして欲しいという事になります。
つまり、変更量としては

  • 「👍 < ✏️」 となるように、なるべく多くの変更を ✏️ に寄せていって欲しい

という事です。 

挙動変更の有無(👍 or 🎨)

難易度の高低について書いてきましたが、挙動変更の有無はこれにどう影響するでしょう。
実際のところ、これは「その変更内容による」という感じで、一概にどちらの方が難しいとは言えなさそうです。

しかし、挙動変更の有無それ自体は、レビューする時の視点を決める時の手がかりとしては大きく、これが区別されているかどうかはレビューの難易度に影響を与えます。

🎨: アプリ挙動変更無いが、コード上のロジックに変更がある

例えば、「🎨: アプリ挙動変更無いが、コード上のロジックに変更がある」の変更を見る場合、この絵文字によって、書く側が「挙動を変えていない」つもりで変更をしているという事がわかります。

つまり、レビューをする上で「挙動が変わっているかもしれない」ような変更があれば、それは書く側が意図していない変更である可能性が高いという事になります。
こういった変更はバグやそれに近い挙動の悪化に繋がっている可能性が高く、注意深く見るべきポイントとなります。

従って、「挙動が変わっていない事」を確認するというのが主なレビューの視点となります。

👍: アプリ挙動が変わる

一方で、「👍: アプリ挙動が変わる」の変更を見る場合、この変更は挙動が変わっているのですから、その「挙動変更を実現している部分」を注視する必要があります。
上でも書いたように、単にコードそのものの変更が問題ない事だけでなく、その変更によって実現されている挙動が想定通りであるかも考えなければなりません。

従って、レビューの視点としては「アプリの挙動の変更部分」に集中したいという事になります。

この時、「🎨: アプリの挙動が変わらない変更」が同じコミットに混ざっている場合というのはある程度仕方ない状況として起き得る事ですが、👍と🎨で見る時に必要な「視点」は異なってくる為、出来る限りこれが分けられている事でレビューの難易度は低くなり、精度をあげる事が出来るでしょう。

こうして欲しい

こういった点から、それぞれの難易度を下げる為に意識して欲しい分け方としては、

  • 「👍 < 🎨」 となるように、なるべく多くの変更を 🎨 に寄せていって欲しい

という事です。 

上の 「👍 < ✏️」  とも合わせると

  • 「👍: アプリ挙動が変わる < 🎨: アプリ挙動変わらない < ✏️: コード上のロジック変更無い」  という形で、左側の変更をなるべく少なく、右側の変更をなるべく多く、として欲しい

という事になります。

新規実装とリファクタリング

上で、👍か🎨かのレビューの難易度は「場合による」と説明しました。
これは特に、そのプルリクエストの種類によって違いが出てくる部分なので、それについて書きます。

新規実装

新規実装は、これまでに無かった挙動を追加するわけですから、必然的に「 👍: アプリ挙動が変わる」の変更が多くなります。
ここでさらに新しいコードが、純粋に新しい挙動を追加するものなのか、既存の挙動を変えながらの新しい挙動なのか、によってレビュー難易度は変わってくる部分ではありますが、この点の違いは実際のところ差分を見ればどちらの変更かはわかりやすい部分で、適切にコミットが分けられていればわかる部分です。

ここでは、👍 で表現されるコミットの多くは新しい部分で、既存への影響はあまりなく変更されている部分が多い筈であり、その手の変更では過去の実装に対する副作用等の心配はあまりありません。
なので、もちろん内容によりますが、👍 で表現されるコミットは難易度の低い変更である事が多くなります。

一方で、🎨で表現されるコミットは既存のロジックを今回の実装の為に書き換えている部分であり、なぜその変更が必要になったのかという背景も含めて、ある程度注意深く見るべき内容になる事が多くなります。

リファクタリング

リファクタリングは、基本的にはその作業全般でアプリの挙動は変わらない前提の作業です。
なので、その中で「 👍: アプリ挙動が変わる」の変更は、予期しない変更が含まれてしまう可能性のある注意深く見るべき変更となります。

また必然的に 「🎨: アプリ挙動変更無い」の変更が多くなる・・・
と言いたい所ですが、実際のところはその内容や手順によってこれは大きく変わってきてしまう部分でもあります。
というのも、例えばとある機能の設計を丸ごと差し替える、というような変更である場合、その変更が1コミットで収まるという事はほぼ無いでしょうし、むしろ収まっているような差分では巨大なコミットになり過ぎているだろうと予想されます。

しかし、そうでないリファクタリングも多くあり、手順を工夫する事でなるべく 「 👍: アプリ挙動が変わる」の変更を少なくして、「🎨: アプリ挙動変更無い」の変更を多くする事が出来ないかを考えてみて欲しいです。
これは実際に意識してやってみると、余計な手間が増えたり、非効率的と感じられる事が多く出てくると思います。
しかしレビュー難易度は明らかに下がるでしょうから、レビューまで含めた効率はむしろ上がると考える事が出来ます。
また書いている本人としても、作業手順としては安全側に倒れたものになるメリットもあります。

ただこの辺りの解決策としては別の手段として、テストを先に書いておいて変更するという方法があり、むしろその方が挙動の確かな証明が出来るので理想的です。
この場合は、あまりコミット手順等に拘らなくても良いでしょう。

余談ですが、僕はコミット説明文を書く事は、数学の証明問題を回答するような事だと思っています。
要するに結果を書くだけというのは不十分であり、何かしらの手順でその変更が妥当である事を証明する必要があるという事です。

まとめ

いろいろ書きましたが、伝えたいこととしては大した事はなく

  • 「👍: アプリ挙動が変わる < 🎨: アプリ挙動変わらない < ✏️: コード上のロジック変更無い」  という形で、左側の変更をなるべく少なく、右側の変更をなるべく多く、として欲しい

という点に集約されます。

その上で、そのモチベーションが伝わらず「見る側」(と言いつつ「僕」ですが)が何を分けて欲しいと思っているのか、何故そうして欲しいのがが分からないと、特に区別して欲しい部分や、そもそも区別するモチベーションも出てこなくて、絵文字ルール自体が形骸化してしまうので、その点について伝えようと思ったという次第でした。

職場 Slack での TIL (Today I Learned) のススメ


こんにちは! with で iOS エンジニアをしている中村です。

iOS エンジニアを名乗っているものの、 iOS 開発経験はまだ半年足らずで、それまでは with の Android エンジニアをしていました。
2022/10 に iOS チームに異動させてもらったものの、ほぼゼロからのスタートだったので成長を目に見える形に残したく TIL を続けていました。

TIL とは、 Today I Learned の頭文字を取ったもので、その日学んだことを Github などに記録していくことらしいです。
今回は職場の Slack の iOS チームのチャンネルで、 TIL を放流していくことにしました。
以下のような形で [TIL] のタグを付け、些細なことでも良いので基本毎日その日学んだことを投稿していました。

TIL例

チームのチャンネルでやるとチャンネルが汚れてしまうかな、とも思いましたが、私の承認欲求(モチベ継続)のためにこのチャンネルでやらせてもらうことにしました。 実際始めてみると、チームメンバーがスタンプでリアクションしてくれたり、スレッドで意見をくれたり、補足してくれたり、訂正してくれたりと、やっていて良かったなと思いました。 チームメンバーからの意見をもらえると、より理解が深まりますし、記録を残していくことで自分の知識の定着にも役立ったと感じました。

そんな TIL ですが、 2022/11/28 から始めて、 2023/03/10 現在 90 個を超えました!
出勤日が約 60 日くらいだったので、 1 日に約 1.5 個の投稿ペースだったようです。 この 3 ヶ月で日々どんなことを学んだのか、一部ではありますが、以下カテゴリ別に紹介しようと思います。 ぜひ見ていってください。

Swift 言語

構造体 struct 関連の TIL

struct はイニシャライザ定義しなくても、プロパティの初期化できることを知った。便利そう。

Swift の struct はイニシャライザを定義しなくても、初期化できるんですよね。

struct Hoge {
  var a: Int
  var b: Int
  // init (a: Int, b: Int) を書かなくても…
}

var hoge = Hoge(a: 100, b: 200) // 初期化できる!

チームメンバーから「デフォルトのイニシャライザは internal なので、別のターゲットからアクセスするためには、結局イニシャライザを定義しなければいけない」という話も聞けました。

public struct Hoge { // 構造体を public にするなら…
  var a: Int
  var b: Int

  public init (a: Int, b: Int) { // public なイニシャライザを書こう!
    // ...
  }
}

似たような話で private なメンバ変数を定義すると、イニシャライザは自動的に private になるんですね。メンバ変数に外側からアクセスできないので、考えてみれば確かに…という感じ。

構造体は値型なので、コピーコストの心配をしていたら、 Swift にはコスト削減の仕組みがあるよという話も聞きました。 Swift すごいですね。

qiita.com

struct は値型だということは理解していたつもりですが、長い間 Android アプリ開発をしてきたのでなかなかその癖は抜けません。配列の中の一要素を更新しようとして失敗しました。

struct Hoge {
    var number: Int
}

var list = [
    Hoge(number: 100),
    Hoge(number: 101),
    Hoge(number: 102),
]

if var target = list.first(where: { $0.number == 101 }) { // ここの target はコピー
    target.number = 1000 // ← なので、 list の Hoge(number: 101) は更新されていない
}

参照型の言語からの考え方の切り替えが必要だな、と思いました。

見慣れない記述方法の TIL

Swift には他の言語では見慣れない書き方がありますよね。 (私が他の言語をあまり知らないだけかもしれません)

if case .success(_) = result { ... }

このような書き方は、 if 文の中に、単体の = があるので、最初は記述ミスかと思いました。こんな書き方があるんですね。 TIL に上げたもののまだちゃんとわかっていません 😂

また ~= というパターンマッチング演算子というものもあるようですね。

koze.hatenablog.jp

TIL に自分で返信しましたが、次にこのコードを見たときに覚えていられる自信がありません。でも、覚えているうちはつい使いたくなります。

空の配列の作り方も「え?そんな風に書くの?」と驚きました。

変数側で型が決まっていれば [] で済むのですが、そのときは何故か生成する側で型を書こうとしていたんですよね。 String の空配列なら [String]() って、ちょっと思いつきませんでした。わかって見れば、型の後に () がついていてインスタンスを作ってるんだな、というのも理解できます。

Kotlin では Collection に色々と便利なメソッドがたくさん生えているのですが、 Swift はよく使われるようなメソッドでも代替できるならそっちを使ってネ、という感じのようです。

sum() すらないのは驚きました。でも引数として + を渡しているのがなんかおしゃれですね。

let sum = [1, 2, 3, 4, 5].reduce(0, +)

UIKit

Swift 言語は Kotlin と似ている部分も多く、最初からそれなりに読み書きできましたが、 iOSフレームワークとなるとそうはいきません。 知っていれば簡単なことでも、なかなか気づくのは大変です。

この TIL はほぼ愚痴ですね。 Interface Builder で UILabel を設置すると、Lines の初期値が 1 になっています。 設定される文字列によって自動で行数が増えて欲しいと考えたので Xcode のインスペクタにそのような設定項目がないかと探したのですが見つかりませんでした。 まさか Lines に 0 を設定すると自動改行になるなんて思わないじゃないですか。

Android では ListViewRecyclerView が出てほぼ置き換えられていきました。 同じように考えていたので、 iOS でも UITableView は古い UI で、新しく開発するものはすべて UICollectionView を使うものだと思っていました。しかし、 iOS ではそこは臨機応変に使い分けているようですね。

View の影について。 Android も最近は困ることがなくなったと思いますが、UIKit では昔から気軽に影を落とせるのがすごいですよね。

RSwift について

リソースを Android のように使える RSwift については、 Android よりも便利だと感じることがありました。

R の中にメソッドが定義されるので、変数を文字列に展開するのも簡単でした。 AndroidR.string.hoge はただの Int だったので、R だけではどうしようもなかったんですよね。

// Localizable.strings
"hoge" = "ほげ%d";

label.text = R.string.hoge(100) // "ほげ100"

また、 RSwift はファイルも扱ってくれるのが良かったです。

さいごに

学んだことは他にも些細なことから、ちょっと難しいことまで色々とあるのですが、さすがにブログには書ききれませんでした。 こうして改めて見返してみると、覚えたと思っていてもすでに忘れてしまっているものもありますね…。一度では覚えられれないので、何度も繰り返し学んで身につけていくしかありません。

この TIL をやってみて、自身の成長の記録として見返すことができることも良かったのですが、何よりチームメンバーとのコミュニケーションのハードルが下がったのではないかな、と感じられたことがとても良かったです。 with の開発はほぼリモートワークなので、チームメンバーとの接点は主に Slack になります。自分の仕事に集中していると、特に発言することもなく 1 日が終わってしまうということもありますが、 TIL を始めたことで気軽にスタンプやコメントを通じた交流が増えたと感じています。 また、この TIL は個人的に始めたことでしたが、時々チームメンバーもマネて投稿してくれたのも嬉しかったです。 やはり他の人からのリアクションが継続の力になりますね。

私は「心理的安全性が〜」とか「チームビルディングが〜」とかはあまり考えていなくて、どうしたら自分が楽しく開発できるかということを考えて TIL を始めました。 新しいことを覚えたら楽しい!誰かに話したい!それだけでした。 自分の知識の定着に役立ち、コミュニケーションも活発になる(こともあるかもしれない)。 そんな TIL を、これをお読みのエンジニアの皆さんも始めてみてはいかがでしょうか!

iOS標準のセミモーダルを任意のサイズで表示するTips

前書き

こんにちは。withでiOSエンジニアをしている長尾です。

今回はiOSセミモーダルUIを、任意のサイズで表示する方法をご紹介します。

TL;DR

iOS標準のセミモーダルの背景を透過して、その上にビューを配置すれば、任意のサイズのセミモーダルを表示できます!

今回実装したいUI

今回実装したいUIはこんな感じです。

  • 直前に表示されていたコンテンツの一部を覆うセミモーダル
  • 背景のコンテンツは触れない
  • 引っ張って閉じられる
  • セミモーダルのサイズはコンテンツのサイズによって可変させる

ポイントは、最後の「セミモーダルのサイズはコンテンツのサイズによって可変させる」です。

何が難しいの?

iOSセミモーダルUIを実現したい場合、一番簡単なのは UIViewController.modalPresentationStyle.pageSheet に設定することです。

しかしこの方法だと、表示するセミモーダルビューのサイズを任意のサイズに変更することができません。常にUIViewControllerのルートのビューが画面いっぱいに(セミモーダル)表示されてしまいます。

別の案として、iOS15から使える UISheetPresentationController.detents を使用することで高さを可変させることができますが、detentsに指定できるのは .large().medium() のみで、コンテンツビューに合わせて任意のサイズにすることはできません。またiOS15未満をサポートしているプロジェクトでは使えません。

またOSSである SCENEE/FloatingPanelを使うという手もあります。こちらのOSSを利用すればコンテンツサイズによってハーフモーダルのサイズを可変させる事ができます。しかし、OSSを追加で導入しないといけないというハードルはありますし、遷移元の画面側からFloatingPanel、およびViewを生成する必要があり、遷移元の記述が複雑になりがちです。(専用のControllerを書くという手はありますが)

このように、サイズ可変のセミモーダルを表示するのは一筋縄ではいきません。そこで今回は、iOS標準のセミモーダルUIを、コンテンツサイズによって任意のサイズで表示する方法 をご紹介します。

実装

0. 完成イメージ

今回はサンプルとして、↓のような課金画面を作成してみます。

ビューのサイズによって、セミモーダルのサイズがうまく調整されています。

1. 表示元の実装

セミモーダルを表示する側の実装を見ていきます。

表示側としては、普通にUIViewControllerをセミモーダルで表示するようにUIViewController.present(_:, animated:)を利用します。

今回セミモーダルで表示する画面は PaymentViewController というUIViewController継承のクラスです。 普通にセミモーダル表示するように modalPresentationStyle.pageSheet (セミモーダル)に設定して表示するだけです。

@IBAction private func tappedCheckoutButton(_ sender: Any) {
    let paymentVC = PaymentViewController()
    paymentVC.modalPresentationStyle = .pageSheet
    paymentVC.tappedCancelHandler = {
        paymentVC.dismiss(animated: true)
    }
    present(paymentVC, animated: true)
}

2. 表示対象のView(PaymentView)の実装

表示対象のViewは、普通にAutoLayoutを利用して組みます。

幅によって高さが可変するように、制約を設定しましょう。

final class PaymentView: UIView {

    var tappedCancelHandler: (() -> Void)?

    // MARK: - initializer

    override init(frame: CGRect){
        super.init(frame: frame)
        loadNib()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        loadNib()
    }

    convenience init() {
        self.init(frame: .zero)
    }

    private func loadNib(){
        let className: String = .init(describing: type(of: self))
        let view = Bundle.main.loadNibNamed(className, owner: self, options: nil)?.first as! UIView
        view.frame = self.bounds
        self.addSubview(view)
    }

    @IBAction private func tappedCancel(_ sender: Any) {
        tappedCancelHandler?()
    }
}

xibを読み込むコードが大部分を占めています。特別な処理は必要ありません。

3. 表示対象のViewControllerを作成する

今回の話の肝はここです。

ViewControllerの背景色は透過に設定し、表示したいViewを画面下部にくっつけるような制約を設定します。

非常に単純ですが、これで評されるコンテンツのサイズによって、セミモーダルのサイズを可変させることができるようになります。

final class PaymentViewController: UIViewController {

    var tappedCancelHandler: (() -> Void)?

    override func loadView() {
        self.view = UIView()

        // 背景は透過する
        view.backgroundColor = .clear

        // 背景タップ時、キャンセル扱いにする
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedBackground)))

        // セミモーダル表示するビューを生成して、addSubviewする
        let paymentView = PaymentView()
        paymentView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(paymentView)
        view.addConstraints([
            paymentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            paymentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            paymentView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        // PaymentView自体のタップは処理しない
        paymentView.addGestureRecognizer(UITapGestureRecognizer())

        // 角丸設定
        paymentView.layer.cornerRadius = 10
        paymentView.layer.masksToBounds = true
        paymentView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]

        paymentView.tappedCancelHandler = { [weak self] in
            self?.tappedCancelHandler?()
        }
    }

    @objc private func tappedBackground() {
        tappedCancelHandler?()
    }
}

ビューの角丸はなくなってしまうので、自身で cornerRadiusmaskedCorners を設定してあげる必要があります。

課題

コンテンツのサイズによってセミモーダルのサイズを変える、というこの実装には、コンテンツサイズが画面サイズを越えてしまう場合に、画面の表示領域外にコンテンツがはみ出してしまうという課題があります。 必要に応じてUIScrollViewに載せたりしないといけないのですが、おそらく制御はまま大変かと思います。 採用するかは状況に応じてご判断ください。

ソースコード

今回のソースコードは↓のリポジトリにアップしてあります。もしお手元で動かしてみたい方はご利用ください。

https://github.com/zrn-ns/FlexibleSemiModalUISample/

DroidKaigi 2022 参加レポート

こんにちは。with で Android エンジニアをしている 石田(@maxfie1d) です。 先日の 2022 年 10 月 5 日〜2022 年 10 月 6 日の 2 日間 DroidKaigi 2022 に参加しましたので参加レポートをお届けしたいと思います。

DroidKaigi 2022 とは

DroidKaigi 2022 会場へ到着!

DroidKaigi はエンジニアが主役の Android カンファレンスで、2015 年から毎年開催されています。2022 年はオフライン/オンラインで開催されました。

素敵な参加者カードをもらって入場!

入場してさっそく 「Welcome talk」 が始まりました。その中で DroidKaigi の公式アプリのコントリビューターが映し出されたのですが、私も載っていました。嬉しい...! そして600人超のオフライン参加者がいるということでとても驚きました(Android エンジニアは市場でも少ないと言われているので)。

スポンサーブースの様子

DroidKaigi ではセッションに加えて スポンサー企業様のブースがあり様々な展示、企画、ディスカッションを楽しむことができます。ノベルティも素敵なものばかり。スタンプラリーのスタンプを押してもらいながら全てのブースを楽しく回らせていただきました!

1 日目

印象に残ったセッションや DroidKaigi 中に頂いたランチを紹介したいと思います。

Jetpack Compose で Material Design 3 (Yuki Anzai)

droidkaigi.jp

Material 3 で導入された新しい概念や Material 2 からの実践的な移行手順等についてのセッションでした。

規模の小さなアプリであれば一括で Material 3 に移行することも可能だが、規模の大きなアプリ(with も含まれる)の場合はコンポーネント単位で移行するのがよいと紹介されていました。Material 2 → Material 3 の移行は大変骨が折れる作業になる予感がしていますが、しっかりと時間を確保して取り組みたいと思います。

Material 3 は Material 2 よりも実用的になった一方で、Deisgn tokens といった新規の概念が沢山あるので同時に理解度を上げていきたいと思います。

本セッションの翌日には androidx.compose.material3:material31.0.0-rc01 が出たので Material 3 もいよいよだなという感じがしています。

ランチ

会場近くのお店で石焼ビビンバをいただきました!

「周りの余計なものが映らないように寄りで撮ると綺麗に写るよ」と最近入社した同僚のエンジニアが教えてくれました。上手に撮れているでしょうか?

Context Receivers に思いを馳せる (uzzu)

droidkaigi.jp

Kotlin 1.6.20 にプロトタイプで登場した context receivers についてのセッションでした。

Kotlin が context receivers にたどり着くまでの歴史的経緯や、Android 開発におけるユースケースなどが詳しく紹介されました。登壇者の指摘の通り、context receivers は 堅実な言語仕様を保ってきた Kotlin にしては非常に自由度が高く乱用ができてしまうなといった印象で、この先安定版としてどのような形に落ち着くのか非常に興味が湧きました。

Context receivers を プロダクト開発で使える日が楽しみです。

2 日目

プロダクトで安全に DataStore 移行する (Go Takahana)

droidkaigi.jp

SharedPreferences を DataStore に移行する実践的な方法についてのセッションでした。

SharedPreferences との違いや、移行時のハマりポイントなど非常に網羅的で納得感のある解説でした。DataStore へ移行するとデータアクセス部分のシグネチャが変わるため変更箇所が大きくなりがちです。また DataStore へのマイグレーションが実行されると SharedPreferences のオリジナルのデータが削除されるという仕様には注意が必要です。登壇者の提案通り 「1. 一度に全てのキーを移行せず少しずつ移行する」「2. 切り戻しリスクの低いリリースで移行する」 の 2 点がポイントになると感じました。

ランチ

2 日目は会社で用事があったため、会場を一時的に離れ恵比寿で握り寿司をいただきました。

DroidKaigi 参加中に食べるお寿司は美味しいです

Deep dive into Jetpack Compose Text (Seigo Nonaka)

droidkaigi.jp

Android の Text についてのセッションでした。

Text は最も基本的な UI の 1 つですが、その実装は奥深いものです。Compose の Text で今後追加される API の紹介があったのですが、今後も TextView にない機能が続々と追加されるのだろうなと思いました。 ちなみに、従来の TextView と Compose の Text において Layout から下の実装は共通のものが使われているそうです。

おわりに

初めてのオフラインでの DroidKaigi 参加だったのですが、広い会場、たくさんのスポンサーブース、かっこい登壇者の方々...。とても感動しました。2 日目には Meetup(DroidKaigi 参加者同士の交流の場)に参加させてもらったのですが、Android エンジニア同士で有意義な意見交換をすることができ 2 時間があっという間に感じました。

来年の DroidKaigi にはぜひ登壇者として参加したいなと思っています!

来年こそは DroidKaigi のステージで登壇するぞ!

エンジニアのリモートデスク環境を調査してみた


こんにちは!
withでiOSエンジニアをしている吉澤です。

リモートワークが普及してから数年経ちましたね。
現在withでは、出社したい人は出社し、リモートで仕事をしたい人はリモートで仕事をするという自由な形態をとっており、ほとんどのエンジニアがリモートで仕事をしています。

そうなってくると重要なのが、自宅での開発環境です。
withのエンジニアはリモートワークを快適に過ごすために、どのような工夫をしているのでしょう?
今回はiOSエンジニアチームの皆さんにご協力いただき、調査してみます!

一人目は社内でiOSエンジニア歴最長のMさんにお聞きしました!

Mさんのデスク。インタビュー日は出社されていたので、オフィスでのデスクまわりの写真をいただきました。

よろしくお願いします!

早速ですが、Mさんはオフィス、自宅、実家と、色々な場所で仕事されていますよね。

それぞれどのような環境でお仕事されているのでしょうか?

モニターについては、どこで仕事をする場合も一緒で、メインのモニターを真ん中に置き、MacBookをサブモニターとして横に置いて使っています。

デスクについては色々試していて、
たとえば実家では、長めの木の板を膝の上において、その上にキーボードとトラックパッドをおいて作業していた時期がありました。

まさかのデスクを使わないスタイルですか!

机が高いと肩が凝ってしまって・・・。なるべく低くしようとした結果ですね!

たしかに、机が高いと肩こりに悩まされますよね。
とはいえ、膝の上にキーボードとトラックパッドを置く発想はなかったです!

椅子はどのようなものを使っているんですか?

椅子については、ペダルを漕ぎながら作業できる椅子などを使っていた時期もありました!
でも、今は一周まわって普通の椅子を使っています。
やっぱ背もたれがあったほうが良いなって(笑)

背もたれは大事ですね!

あとはイヤホンについてもこだわりを持っていますね。
昔のiPhoneについてきた有線のイヤホンを使ってます。

ワイヤレスではないんですね!珍しい気がします。

ワイヤレスだと、ミーティングが長い時にバッテリーを消耗しちゃうじゃないですか。
かといってイヤホンせずにスピーカーを使うと、他の人と同時に自分も話した場合に、ノイズキャンセリングで声が消されてしまうことがあって。

音も良いし、有線のイヤホンが一番良いなと思って使ってます。

なるほど。ミーティングだとその場から動かないし、長時間になることもあるので、有線のイヤホンを使うのもアリかもしれません!

 

二人目は最近AndroidチームからiOSチームに来てくださったNさんにお聞きしました!

Nさんのデスク。モニターアームでモニター下のスペースも活用しています。
左に置いてあるGoogle Nest Hubで音楽を聴いたり映画を観たりするのもお好きなんだとか。

よろしくお願いします!

早速ですが、Nさんがどのような環境でお仕事されているのか教えて下さい!

寝室に、デスクとバランスボールを置いて仕事をしています。

リモートワークを始めたばかりのときは、小さな台で仕事をしていたんですが、腰と背中が痛くて・・・。

新しくデスクと椅子を買ったんですけど、そのデスクも結局大きいものに買いかえて、椅子もバランスボールに変えました!

アップデートを繰り返して、今の仕事環境になったんですね!

椅子はどうしてバランスボールに変えたんでしょうか?

一時期、腰を悪くしていたんですけど、その時にバランスボールが腰に良いって聞いたんです。

ボールの上でブリッジするかんじで背中を伸ばしたりもできるし、便利ですね!

そうなんですね! 私もバランスボールに興味がでてきました!

他にデスク周りでこだわりのポイントはありますか?

モニターアームを使用するようにしています。
モニターの下にスペースができるのが便利で、私物のPCを置いたりしています。

デスク上のスペースは限られてますもんね!
一工夫するだけで、デスクを広々と使えそうです!

 

三人目は入社1ヶ月のAさんにお話をお聞きしました!

Aさんのデスク。PCとモニターの高さを合わせています。

よろしくお願いします!

早速ですが、Aさんがどのような環境でお仕事されているのか教えて下さい!

左側にモニター、右側にMacBookを置いて作業しています。

何かこだわりのポイントはありますか?

まず、PCはPCスタンドを使って、モニターと高さを合わせるようにしています。

あとは、ハブにこだわってますね!
MacBookから出るケーブルが1本になるようにしています。

Aさんの使用しているハブ。YubiKeyや有線LANなどをつなげています。

それは便利そうですね!

そうなんですよ。充電もできるし、有線LANもつなげます。

あとは、YubiKeyというものもハブに挿していて、二要素認証もできるようにしていますね!

YubiKeyは初めて知りました!
わざわざスマホを見る必要もなくて、わずらわしさが減りそうです!

このハブだけで、本当に色々なことができる。
なのにMacBookから出ているケーブルは1本で済んじゃうんです!

私も真似して買ってみようかな・・・

たしか5万くらいしますけどね!(笑)
でも、それだけの価値はありますよ。

 

最後に筆者のデスクを紹介したいと思います!

筆者のデスク。

こだわりポイントは、なんといってもオーダーメイドで作ったデスクです!

奥行きがあって広々と使えるデスクが良かったので、160cm × 80cmのサイズにしました。
また、配線孔を3つ作ることで、ケーブルをスッキリさせています。

あとは、地味に重要なのがデスクの高さです!
筆者は体型が小柄で座高も低いため、デスクの高さも一般的なものより数センチ低くしました。

 

まとめ

協力してくださったみなさん、ありがとうございました!
それぞれこだわりのポイントを持っていて、普段どのような環境で仕事をしているのかも知ることができて面白かったです!

改めてになりますが、リモートワークには、働きやすく過ごしやすい環境作りが重要となります。
リモートワークを活かして、各々の最高の環境を作ってください!

最後まで読んでいただき、ありがとうございました!

 

withにiOSエンジニアとして1ヶ月。これ便利!と思った3選!

はじめに

初めまして。
2022年9月よりiOSエンジニアとしてjoinしました、ashi_psnです。
私は2016年4月から新卒でエンジニアとなり、今年で7年目となります。

これまでに2016年4月から2017年8月までは、医療系スマートフォンアプリ/webアプリケーションの開発、 2017年9月から2022年9月まではグルメメディア系スマートフォンアプリの開発とモバイルアプリチームのリーダーをしてきました。

そして2022年9月、withにiOSエンジニアとして入社しました。

今回は入社してから約1ヶ月が経ち、まだまだ全体像が分かりきっていない状態ではありますが、withのiOSで用いられるツールで便利と思った点が多々あり、それを便利度、おすすめ度それぞれを5点満点で評価したものを3つご紹介いたします。

①fastlaneでの自動化

便利度 ★★★★★

おすすめ度 ★★★★★

【内容】

  • 証明書の更新(match)
  • アプリバージョン更新
  • リリースの自動化

【おすすめポイント】
証明書管理管理はmatchで行っていますが、更新と取得でそれぞれレーンが別れていて、更新処理をするとslackに他のエンジニアに証明書ダウンロードを促すチャットが流れます。
これによって証明書の更新が漏れが起きにくく、とてもいいと思いました。

また、AppStoreへのアップロードなどのリリース作業も、
プロジェクトのバージョン変更とデプロイがfastlaneで1コマンドでできます。

fastlaneも多くのiOSエンジニアに浸透したと思う一方、まだまだXcodeでバージョンやbuild番号を手動で変更してXcode上でArchiveしApp Storeへアップロードしている人も多くいるかと思います。
そんな中fastlaneのコマンドを叩くだけでバージョン設定とリリース作業ができるのは、コーディングに専念ができる時間が増え、生産性があがりとても捗りそうだなと感じました。

②テスト用バイナリ配布の自動化

便利度 ★★★★☆

おすすめ度 ★★★☆☆

【内容】

  • Pull Request上で「/deploy」とコメントを打つと、AdHocスキームでテスト用バイナリの作成とダウンロードリンクをコメントに記載してくれる

【おすすめポイント】
iOSチームではgitflowでgit運用をしており、新機能開発ではfeature/xxx のブランチで機能開発を行い、developブランチへPull Request作成してレビューを行い、developへマージ後、releaseブランチでQAをしてリリースする流れとなっています。

以前勤めていた会社では、QAのバイナリはTestFlight、もしくはエンタープライズアカウントでビルドしたものを用いていましたが、TestFlightの場合は都度ビルド番号やバージョンを更新してアップロード、エンタープライズアカウントでのビルドは証明書の付け替えが必要で、地味に手間がかかっていました。

弊社では、EMLancherというツールを用いてテスト用バイナリを配信しており、「/deploy」コマンドを叩くとCI上でAdHocビルドを行い、EMLancherへアップロードされ、ダウンロードリンクがコメントに残る仕組みがあります。 これによって、バイナリごとにどの改修が含まれているかわかり、気軽にレビュー中に動作テストも行えるため、とても便利です。

デバッグメニュー

便利度 ★★★★☆

おすすめ度 ★★☆☆☆

【内容】

  • ステージング環境ごとにビルドしなおす必要がなく向き先が変更できる。
  • ストアカウントへのログインが入力なしで行える。
  • カスタムスキームやモーダル、アラートなどの機能を単体で確認することができる。

【おすすめポイント】
サーバーは開発環境、ステージング環境、本番環境の3つがあるのは一般的かと思いますが、
多人数で開発してる場合はステージング環境も複数あることがあります。
その場合、サーバーの向き先ごとにプロジェクト内のスキームで定義していては、管理コストがあがり、向き先を変更するごとにビルドし直さなければなりません。

withではデバッグメニューからサーバーの向き先を変更できるため、ビルドも1回で済み、また設定ファイルもステージング環境用を共通で用いることができます。
そのため、QAの際にバイナリを何度も入れ直す必要がありません。

デバッグメニューの恩恵はそれだけでなく、テストアカウントへのログインがパスワードの入力など一切なくできたり、単体機能としてテストしずらいカスタムURLスキームやユニバーサルリンクも、デバッグメニューから確認することができるなど、多くのメリットがありました。

最後に

今回紹介した内容は、比較的知っているメジャーで、取り組んでいる企業や個人の方もいらっしゃるかと思います。 僕自身もfastlaneは導入経験があり、デバッグメニューは使っている企業も一部はあるというのは知っている程度でした。
ただ、その一方で、やはりあるとないとでは大きく違う、そんなことをwithに入社して1ヶ月ではありますが感じました。

今回は3つのことを紹介しましたが、他にもSwiftLintによる静的解析でコーディング規約に準拠させたり、ライブラリ更新の自動化なども取り組んでいます。

私たちiOSエンジニアは、iOSアプリの実装が主な業務となる一方、証明者管理やApp Storeへのアップロード、審査などコーディング以外のことも行わなければなりません。
そういったコーディング以外の部分をなるべく自動化してコーディング時間を作ることも、また一つ大事なことだと感じました。