前置き
Swiftをちゃんと業務で書き始めて、やっと1年くらいたったのですが、、、
いつの間にか自分の中でよく使う書き方になっていたので、記録的な意味も込めて書いておきます。
こういう記事もありますし、
もっとこうしたらとかあればご指摘お願いいたします。
一元管理
【UIView / UITableviewCell】
「脱StoryBoardはしているが、CustomViewは使っている」なんて人は多いんじゃないかと思います。
そんな人にオススメです。
① ベースとなる共通プロトコル作成
protocol BaseViewType {
var name: String { get } // クラス名 and セル再利用ID
var nib: UINib { get } // UINib参照
var view: UIView? { get } // View呼び出し
}
② カスタムビューのタイプごとにプロトコルの実装
例としてわかりやすい適当なクラス名をつけています。
/* UIView のタイプ */
enum UIViewType: BaseViewType {
case header
case footer
var name: String {
switch self {
case .header: return "HeaderView"
case .footer: return "FooterView"
}
}
var nib: UINib {
return UINib(nibName: name, bundle: nil)
}
var view: UIView? {
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
/* UITableViewCell のタイプ */
enum UITableViewCellType: BaseViewType {
case article
case movie
case sns(type: SnsType) // ・・・・・・・・・注①
var name: String {
switch self {
case .article: return "ArticleTableViewCell"
case .movie: return "MovieTableViewCell"
case .sns(let type): return type.name
}
}
var nib: UINib {
return UINib(nibName: name, bundle: nil)
}
var view: UIView? {
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
enum SnsType { // ・・・・・・・・・注①
case facebook
case instagram
var name: String {
switch self {
case .facebook: return "FacebookTableViewCell"
case .instagram: return "InstagramTableViewCell"
}
}
}
/* 以下、必要なタイプごとに増えていく */
enum UICollectionViewCellType: BaseViewType {
// 略
}
// ・
// ・
// ・
// ・
どのレイヤーでビュータイプを定義分割するかは個人の判断によるかと思いますが、
自分が行なっているプロジェクトでは、例えばテーブルだけでも
- テーブルセル
- テーブルヘッダー
- テーブルフッター
- テーブルセクション
と細かく分けています。
タイプごとでさらに階層構造になる場合は、注①のようにすることで対応可能です。
(わかりやすくあえてenumを外だししていますが、enum内のenumで定義した方がより堅固なコードになります)
使用例
例1 UITableView Header / Footer のセット
TableView
のHeader
やFooter
をセットするときなど、以下のように1行でスッキリ書くことができる。
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return UIViewType.header.view as? HeaderView
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIViewType.footer.view as? FooterView
}
Viewの中身を動的に何か操作する場合は、少し操作が必要になるが、それでも2、3行で済む、、、はず笑
例2 UITableViewCell 呼び出し
以下、呼び出すのに必要最低限のみコードのみ記載しています。
/* ①テーブルにセルの登録 */
@IBOutlet fileprivate weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCellType.article.nib, forCellReuseIdentifier: UITableViewCellType.article.name)
}
/* ②セルの呼び出し */
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: UITableViewCellType.article.name,
for: indexPath
) as! ArticleTableViewCell
// do some setup
return cell
}
ポイント
- 事前に定義しておく
UINib
を呼び出す際のクラス名のタイポを防げる - 新たにカスタムビューを作成しても、タイプを増やすだけですぐに適応し呼び出せる
【Firebase Analytics】
Firebase
に限らずログを複数送る系はだいたい当てはまるかと思います。
① ベースとなる共通プロトコル作成
protocol BaseEventModel {
var eventName: String { get } // ログの送信するイベント名
var eventParameters: [String: Any] { get } // ログの送信するパラメーター
func logEvent() // ログ送信の実行メソッド
}
eventParameters
を[String: Any]
(バリューをAny型でセットできるよう)にして、どんなパラメーターが来ても対応できるようになっている。
② ログごとにプロトコルの実装
例としてわかりやすい適当なパラメータやキー名をつけています。
1つ目のイベントは「記事の操作イベント」
2つ目のイベントは「タップの検知イベント」
上記のようなログを取りたい想定として、以下を見ていただけると。
/* 1つ目のイベントタイプ */
enum ArticleEvents: BaseEventModel {
case searchArticle(searchTerm: String)
case shareArticle(articleId: Int, articleTitle: String)
var eventName: String {
switch self {
case .searchArticle: return "search_article"
case .shareArticle: return "share_article"
}
}
var eventParameters: [String: Any] {
var params: [String: Any] = [:] // [キー: バリュー]
switch self {
case .searchArticle(let term):
params[term] = term
case .shareArticle(let id, let title):
params[article_id] = id
params[article_title] = title
}
return params
}
func logEvent() {
Analytics.logEvent(self.eventName, parameters: self.eventParameters)
}
}
/* 2つ目のイベントタイプ */
enum TapEvents: BaseEventModel {
case tapNavigation
case tapNotification(isTapped: Bool)
var eventName: String {
switch self {
case .tapNavigation: return "tap_navigation"
case .tapNotification: return "tap_notification"
}
}
var eventParameters: [String: Any] {
var params: [String: Any] = [:]
switch self {
case .tapNavigation:
break
case .tapNotification(let isTapped):
params[is_tapped] = isTapped
}
return params
}
func logEvent() {
Analytics.logEvent(self.eventName, parameters: self.eventParameters)
}
}
/* 以下、必要なイベントごとに増えていく */
enum MoveEvents: BaseEventModel {
// 略
}
// ・
// ・
// ・
// ・
使用例
ログを送りたい箇所に、以下のようにする。
ArticleEvents.searchArticle(searchTerm: "検索ワード").logEvent()
ArticleEvents.shareArticle(articleId: 12345, articleTitle: "タイトル").logEvent()
TapEvents.tapNavigation.logEvent()
TapEvents.tapNotification(isTapped: true).logEvent()
ポイント
- パラメーターのセットからログの送信までワンライナーで簡潔に書ける
備考
GoogleAnalyticsで、過去に同じようにやられてる方を見つけたので参考までに
【UserDefaults】
今回は以下4つを保存する例を記載します。
- email (String型)
- id (Int型)
- isRegistered (Bool型)
- date (Date型)
呼び出し関数が多かったので、複数定義しておらずプロトコルを作成していませんが、プロジェクトなどでお好みで個々に作成していただければと思います。
enum UserDefaultsType {
case email
case id
case isRegistered
case date
// UserDefaultsの呼び出し
private var ud: UserDefaults {
return UserDefaults.standard
}
// Keyの設定(キーはなんでも良い。一度設定してしまえばタイプミスも防げる)
private var key: String {
switch self {
case .email: return "user_email"
case .id: return "user_id"
case .isRegistered: return "user_isRegistered"
case .date: return "user_date"
}
}
// Set Method
// ジェネリクス関数で型に左右されず一括でセットでできる
func setItem<T>(with item: T) {
ud.set(item, forKey: key)
}
// Get Methods
// 型ごとにゲットメソッドを定義。必要な型に応じてここは増やしてください。
func getString() -> String? {
return ud.string(forKey: key)
}
func getInt() -> Int {
return ud.integer(forKey: key)
}
func getBool() -> Bool {
return ud.bool(forKey: key)
}
func getDate() -> Date? {
return ud.object(forKey: key) as? Date
}
// Remove Method
// 削除メソッド
func removeItem() {
ud.removeObject(forKey: key)
}
}
使用例
// set
UserDefaultsType.email.setItem(with: "xxx@xx.jp")
UserDefaultsType.id.setItem(with: 42543)
UserDefaultsType.isRegistered.setItem(with: false)
UserDefaultsType.date.setItem(with: Date())
// get
UserDefaultsType.email.getString() // 注: Optional
UserDefaultsType.id.getInt()
UserDefaultsType.isRegistered.getBool()
UserDefaultsType.date.getDate() // 注: Optional
// remove
UserDefaultsType.email.removeItem()
UserDefaultsType.id.removeItem()
UserDefaultsType.isRegistered.removeItem()
UserDefaultsType.date.removeItem()
ポイント
- キー設定しておけるので、使用するさいのタイプミスを防げる。
- setItemがジェネリクス関数なので型を気にする必要がない。
備考
古い記事として
などありますが、個人的に呼び出し元で
- メソッドごとに
UserDefaults.standard
を書いてる - 呼び出し元でキャストしてる
しているのがスマートじゃないと思ったので、上記のようにしています。
あとがき
enum
でただ列挙するのではなく、 Protocol
である程度縛りのある一元管理ができたのではないかと思います。
一元管理するための定義コードは若干長くなりますが、最初にちゃんと定義しておくことで、あとあと呼び出し元で綺麗で簡潔に呼び出せるコードになっているかと!
もっとこういうのあるぜ、っていうの教えていただけると嬉しいです。