目的
独学でSwift学び始めて1年、実務で使い始めて数ヶ月たちました。最初に書いてたコードを見てみると、こうしたら良かったのにな〜みたいなところが多々あるので、昔の自分と同じようなSwift学びたての人が改善できそうなポイントについて記述します。
特に使い所が多いTableviewについて、今回はまとめていきます。TableViewとCollectionViewは似ているところも多いので、今回紹介するtipsはCollectionViewにもほとんど使えます。
環境
- mac OS Sierra
- Xcode 9.2
- Swift 4.0
セル、もしくはセクション数のカウント
基本的なとこ
これは多くの人が当たり前のようにやってると思いますが、基本的に配列のcountメソッドを使って次のように記述することがほとんどだと思います。
let animals = ["🐶","🐱","🐭","🐷","🐴"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count
}
要素の配列がオプショナルなとき
次にこの配列がAPIからのレスポンスを入れるなどしてオプショナルの場合どのように書きますか?
Swift習いたては自分は次のように書いてました。
let animals: [String]?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let animals = animals {
return animals.count
}
return 0
}
オプショナルバインディングしてanimalsが存在しているときに配列の数を返しています。nilであれば0で返しています(ちなみにこのように同じ定数名で行うオプショナルバインディングをシャドーイングという)。
現時点の私は次のような形で書きます。
var animals: [String]?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count ?? 0
}
1行で書けるので、こちらのほうが良いですね
条件によって表示数が変わる時
それでは次に、ある変数がtrueかfalseによって表示するセルの数がかわる場合はどのように書くでしょうか。例えば、課金しているユーザーとそうでないユーザとで分ける場合です。
var isFlag = false
var animals: [String]?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let animals = animals {
// Flag->true:配列の数を返す。
// Flag->false:1つだけ返す
switch isFlag{
case true:
return animals.count
case false:
return 1
}
} else {
return 0
}
}
Switchを使って処理を分けてます。
ちなみにSwiftのSwitch使ってから他の言語でSwitch使おうとするとbreakを都度書かなければならないので、Swift最高〜ってなります。
話がそれましたが、上のようなコードであれば、今だとこのように書きます。
var isFlag = false
var animals: [String]?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let animals = animals {
// Flag->true:配列の数を返す。
// Flag->false:1つだけ返す
return isFlag ? animals.count : 1
} else {
return 0
}
}
書き換えた所はswitchのところを三項演算子にしました。
独学のときは三項演算子使ってなかったんですが、これ使えるとif分の処理が結構書き換えれます。
これも1行で書けますし、他の言語でも使うので、是非マスターしましょう。
カスタムセル
作り方
Swift開発序盤のほうから標準のUITableViewCellでは表現できない自作カスタムセルを作る必要が出てくると思います。
自分の場合ですが、最初は
カスタムセルクラス作成→StoryboardのTableViewのセルにカスタムセルクラスを設定
という感じで作ってました。
しかし、小規模な個人開発以外はxibで作るのがベストだと今は考えてます。
xibで作るメリットは
- Storyboardの変更が生じない→Storyboardでのコンフリクトを防ぎやすい
- 個別のファイルなので、管理が楽
- Identifierをコードで一元管理(後述)
という感じです。
Swift習いたての個人開発だとxib恐い、ってなってたんですが、何回か使っていけば慣れるし、今後メインでiOS開発するならば避けては通れないので、早めに使っといたほうが良さげです。
CellIdentifier
カスタムセルを使う場合には、セルにIDを登録する必要があります。
storyboardでカスタムセルを設定した場合には、IDもStoryboardに登録することができます。しかし、呼び出すときに
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as! CustomTableViewCell
のように、直で文字打つともし間違ったときにクラッシュします。
他にもperformSegueでもIDを間違うとクラッシュするので、安定したアプリ開発の際には、ここらへんは注意すべきです。
今ではカスタムセルクラスにIdentifierプロパティを作って、それを参照してtableviewにセルを登録しています。
// viewdidloadとかで
tableView.register(nib, forCellReuseIdentifier: customCell.cellIdentifier )
static let cellIdentifier = "CustomCell"
ここを変数作らずに、クラス名を文字列にするExtension作って、セル登録に使う人もいます。
セルの情報
扱うデータが静的な場合
ここが今回一番伝えたいポイントです。
やってる人いないかもしれませんが、最初のころの私は次のようなコードを書いてました。
let titleList = ["Dog","Cat","Rat","Pig","Horse"]
let animals = ["🐶","🐱","🐭","🐷","🐴"]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
cell.titleLabel?.text = titleList[indexPath.row]
cell.animalEmojiLabel?.text = animals[indexPath.row]
return cell
}
セルに表示する各配列要素にindexPath.rowで情報取得してます。なかなか初初しさが醸し出されてます。
上記のコードの問題は、配列要素がちゃんと整合性がとれているか確認する必要があります。その分コストや安定性が損なわれます。
今だと↓のようになります。
enum AnimalType: Int {
case dog
case cat
case rat
case pig
case horse
var title: String {
switch self {
case .dog:
return "🐶"
case .cat:
return "🐱"
case .rat:
return "🐭"
case .pig:
return "🐷"
case .horse:
return "🐴"
}
}
var icon: String {
switch self {
case .dog:
return "Dog"
case .cat:
return "Cat"
case .rat:
return "Rat"
case .pig:
return "Pig"
case .horse:
return "Horse"
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: CustomCell.cellIdentifier) as! CustomCell
let animal = AnimalType(row: indexPath.row)
cell.titleLabel.text = animal?.title
cell.animalEmojiLabel?.text = animal?.icon
return cell
}
表示する内容をEnumで管理しています。
行数は多くなりますが、この分Swiftの恩恵であるSwitchの網羅性を享受できます。
もしSwitch Caseを網羅できてない場合は、errorがでるので、漏れが生じません。
このように予め表示するデータが決まっている場合は、Enumで処理するときれいに書けることが多いです。
また、最近発見したのは、タプルをつかってEnumでの複数プロパティをまとめる書き方です。
enum AnimalType: Int {
case dog
case cat
case rat
case pig
case horse
var prop: (title: String, icon: String) {
switch self {
case .dog:
return (title: "Dog", icon: "🐶")
case .cat:
return (title: "Cat", icon: "🐱")
case .rat:
return (title: "Rat", icon: "🐭")
case .pig:
return (title: "Pig", icon: "🐷")
case .horse:
return (title: "Horse", icon: "🐴")
}
}
}
これであれば、さらに扱うプロパティが増えても大きく記述量が増えることはないですし、可読性も上がります。
タプル最高!!
APIなどで表示するデータが変則的なとき
めちゃめちゃ便利なCodableを使って次のようなモデルにAPIのレスポンスをマッピングし、それを表示するというケースを考えます。Codableは最近でた新しめのものですが、初めてでも結構簡単に使えるのでおすすめです。
結構最近まで次のような感じで書いてました。
class Animal: Codable {
let type: String
let emoji: String
}
let animalList: [Animal]?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
guard let animal = animalList?[indexPath.row] else { return cell }
cell.titleLabel?.text = animal.type
cell.animalEmojiLabel?.text = animal.emoji
return cell
}
この書き方でも問題ないですが、他の場所でも同じような処理が合ったりした場合や反映する情報が多くなってくると、その分コードが増えていきます。
現在では、次のようにCellに扱うモデルをもつプロパティを用意して、セットされたらラベルにデータを設定しています。
var animal: Animal! {
didSet {
self.celltitleLabel?text = animal.type
self.animalEmojiLabel?.text = animal.emoji
}
}
let animalList: [Animal]?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
guard let animal = animalList?[indexPath.row] else { return cell }
cell.animal = animal
return cell
}
このようにすれば、セルに該当のクラスを渡すだけなので、ViewControllerの記述が簡潔になります。
その他
個人の好みかもしれませんが、周りで見てる感じだとTableViewのProtocol(UITableViewDataSource, UITableViewDelegate)はViewControllerにExtensionで分けて書いてることが多いです。
可読性のため、分けたほうがいいかなと個人的にも思ってます。
extension ViewController: UITableViewDataSource {
}
extension ViewController: UITableViewDelegate {
}
まとめ
今でも開発の中で新しい発見をしていて、そのたび「Swift最高〜」ってなってますので、現時点の書き方もまだまだ改善する部分があると思うので、他のやり方などあったらコメントください〜