本日は、ひと通り見てきた個人的にお気に入りな技術ブログ「その Swift コード、こう書き換えてみないか」の中で紹介されていた UIKit
にまつわるチップス集的な技術ブログと思われる「同じような処理だけどこっちの方がいいよってやつ」を眺めていきますね。Swift 言語とは分野が違ってきますけれど、普段のアプリ作りの中で有用と思われるのと、もしかすると Swift 言語の話と絡めて観察できるところもあるかもしれないので、そんな気持ちで読み進めていこうと思います。よろしくお願いしますね。
——————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #286
00:00 開始 00:31 今回は UIKit のチップスの話 01:35 標準で提供されている機能を使うメリット 05:38 ボタンを角丸で表示するには? 08:26 自身の型を返す静的プロパティーの活用例 10:29 角丸には言及されていないことに注意 13:18 主アクションに対してアクションを行う 16:07 構造体を拡張可能な列挙型のように扱える 19:43 インデックスを適切に判定する 21:33 インデックスの範囲を判定する最適解 24:02 ArraySlice のインデックス範囲に注意 25:47 配列はたまたま count が endIndex になっているだけ 26:48 クロージング ———————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #286
では、始めていきますね。以前まで見ていた田中洋子さんのブログの提案、Swiftコードの書き換えについて面白く見させていただいていました。その中で紹介されていたものを、今日はまた見ていきたいと思います。
この提案は主にUIキットを使うときに便利なもので、のっぺーさんによる指定に基づいて紹介されています。この勉強会はSwiftの勉強会なので、少し古い情報でも差し支えないかなと思います。実際、最終更新が2022年なので、そこまで古くはないですね。
SwiftでAppleのプラットフォーム向けにコードを書くときには、用意されたAPIを使うとアップデートや対応が楽になるケースが多いです。特に、これが大事なのは、OSのアップデート時に追加される機能が変更なしで動くようになることが多いためです。もちろん、APIの変更に伴う若干の手直しが必要な場合もありますが、基本的には対応が楽になることが多いです。
また、マルチプラットフォームでの修正が少なくなり、より標準に近い見た目にすることができます。最近のUIキットでは、標準機能を使うことで表現力が高まり、開発の効率も良くなります。具体的に言うと、昔の方法でカドマルのボタンを作るには、ボタンのレイヤーのcornerRadius
に設定する方法がありましたが、今ではボタンのコンフィギュレーションフィールドを使ってこれを実現することができます。
例えば、昔は以下のように書いていました:
button.layer.cornerRadius = 10
でも今は、以下のように書くことで、カドマルのボタンを作成できます:
var configuration = UIButton.Configuration.filled()
configuration.cornerStyle = .medium
button.configuration = configuration
このように、現在のSwiftではコンフィギュレーションを使って複雑なカスタマイズができるようになっています。これにより、コードがよりシンプルかつ保守性の高いものとなります。例えば、UIButton.Configuration
内では、たくさんのプロパティをカスタマイズできるようになっており、非常に柔軟です。
では、UIボタンのコンフィギュレーションについてさらに詳しく見ていきます。過去にはタイプを使ったカスタマイズが一般的でしたが、今はコンフィギュレーションを使います。例えば、UIButton.Configuration.plain()
はシンプルなボタンを作成するために使われます。このように、新しいAPIを活用することで、コードの見通しが良くなり、機能追加にも対応しやすくなります。
このように、SwiftのUIキットに対する新しいAPIやコンフィギュレーションの使い方を学ぶことで、より効率的に開発を進めることができます。これからも、新しい技術や方法を取り入れながら、Swiftの理解を深めていきましょう。 このコンフィギュレーションでは、いろいろなものが用意されていてすぐに利用できるので、それを使いましょう。たとえば、fills
を使えば角が丸いボタンが使えるという話がありますね。ただ、fills
と言っても角が丸くなるとは明記されていないんです。そのあたりはどうするのだろうと思います。もし絶対に角が丸くないと嫌だという場合には、どうするのでしょうか。これについてはちょっと明確にはわからないですね。ただ、角が丸いボタンにしたい場合は、レイヤーの設定でカスタムするほうが確実かもしれません。
このようなUI要素については、どこまで気にするかがポイントです。UIKit
のUIボタンに関しては、fills
を設定しておけば標準で角が丸くなると書いてあるか確認してみましょう。標準パーツとして角が丸いボタンが使えるのなら、それで良いのかもしれません。ただし、OSのメジャーバージョンアップで見た目が変わる可能性もあるので、その点は注意が必要です。cornerRadius
等の設定によっては、OSアップデートでレイアウトが崩れることもありますので、こだわりがなければ標準に従うほうが無難かもしれません。
コンフィグレーションや標準ドキュメントで見てみると、例えばこのfills
はボタンの背景色を設定しているだけですので、角が丸いという仕様はたまたまの結果かもしれません。そのため、標準パーツとして角が丸いボタンが必要な場合には、あらかじめ設定を確認しておいたほうが良いでしょう。
次に、ボタンを押したときの処理についてです。ボタンにアクションを追加するには、addAction(for: .touchUpInside)
のように設定しますね。これは昔からの知識かもしれませんが、この書き方が基本です。しかし、最近ではprimaryActionTriggered
に反応させることが推奨されているのです。このprimaryActionTriggered
にすることで、Apple TVのようなデバイスでもうまく動作します。もはやタッチアップという発想がないデバイスでも、リモコン操作で自然に動作するのです。Apple TVアプリ自体を使ったことがないので細かいところはわかりませんが、このprimaryActionTriggered
は非常に便利そうです。
さらに、従来のaddTarget(_:action:for:)
の設定も、UIAction
に置き換わってきていますので、これについても注目しておくべきでしょう。私はまだUIAction
を使ったことはないのですが、これは新しい方法として覚えておいたほうが良さそうですね。 とりあえずコンパイルを通したいと思います。こちらにオールイベントやオールタッチイベントを追加したいのですが、そのためには static var
で primaryAction
を定義する必要があります。他に必要なものとしては、システムリザーブやタッチキャンセル、タッチダウンなどのイベントもお馴染みですよね。primaryActionTriggered
などもあります。これらはすべて構造体のUIコントロールイベントに対応しています。
そのため、複雑な選択肢を提供する際には、列挙型ではなく構造体を使って、適切なインスタンスを渡すと応用が効くようになります。必ずしもその方が良いというわけではありませんが、さまざまなタッチイベントを表現する際に柔軟であるという感じです。
例えば、私たちがオールエディティングイベントやオールタッチイベントを使いたいとき、昔ならビットフラグを使ってローバリューで管理していました。内部的には同じかもしれませんが、新しい方法ではより柔軟で扱いやすいコードが書けるようになります。
ビットフラグを使ってイベントを予約するのは大変で、メモリのビット数を事前に設定する必要があります。しかし、構造体や配列を使えば、メモリの予約を気にせずに済みます。その代わりメモリを多く使用しますが、現代のシステムではそれが許容範囲です。
セットを使ってイベントを判定するようにすれば、構造体の方が良いかもしれません。一時的に列挙型で対応して、後から構造体に差し替えることも可能です。その場合、APIの互換性を保つために修正が必要になります。
次に、インデックスが配列から溢れていないかチェックする必要がある場合、items.count
を使ったオブジェクティブC風の判定が見受けられました。田中さんのブログにもあるかもしれませんが、インデックスが配列の範囲内かどうかをチェックするのは一般的な方法です。インデックスがマイナスになった場合なども考慮する必要があるかもしれません。
最後に、インデックスとカウントを比較する方法ですが、これは慣例的なものです。こういったコードが読みにくいという意見もありますが、コメントでも指摘されたように、理想的なコードを書けるように心がけましょう。 ここから実際に書いていきましょうか。まず、テキストを読んでみましょう。読みやすいようにコードのカウントを持ち出すのは、インデックスとカウントの関係があまり深くないからですね。少なくとも、要素が含まれている場合はアイテムとコンテンツで比較可能なので、インデックスが配列から漏れていないかを確認する必要があります。
アイテム配列のインデックスが範囲内かどうかの確認方法ですが、配列の indices
プロパティを使うとインデックスの範囲が簡単に取れます。例えば、以下のコードです。
let index = 3
if items.indices.contains(index) {
// インデックスが範囲内
}
この方法が最も正確かつ意味的にわかりやすいです。昔のスタイルだと、以下のようにインデックスの範囲を手動でチェックしていました。
if index >= items.startIndex && index < items.endIndex {
// インデックスが範囲内
}
この場合、ちょっと頭を使わないといけないですし、バグを生みやすい原因になります。提供されている機能を使う方が安全です。配列のインデックスの型が将来変更されても、提供された方法なら対応しやすいです。
また、コメントで教えてもらったアレイスライスについても説明します。以下のようにサブアイテムを取り出す場合、インデックス範囲の変更が影響します。
let subArray = items[2..<4]
このとき、サブアイテムのスタートインデックスは2から始まります。これが例えば0からエンドインデックスまでと比較される場合、以下のような判定はバグを生みやすくなります。
if index >= subArray.startIndex && index < subArray.endIndex {
// 範囲内かどうかをチェック
}
スタートインデックス重要、インデックス範囲重要ということは覚えておく必要があります。このように新しいインデックス判定方法を使えば、バグを防ぎやすいです。
こんな感じで、とりあえずここまで読み進めました。また次回は続きから見ていこうと思います。今日はこれで終わりにします。お疲れ様でした、ありがとうございました。