#UITableViewでタイムラインを作る。
UITableView自体を使うのは慣れれば簡単だと思いますが、
Cellをカスタムで作成するのは少し大変だと思います。
今回は、作成したばかりのプロジェクトで1からタイムラインを実装してみます。
また、私が説明下手なのもあり記事がかなり長くなってしまいました。
なので全部で3記事に分けております。
軽い気持ちで流しみしていただけると助かります。
GitHubリポジトリはコチラになります。
##Table Viewを作る
まず最初に行うこととしては
・シンプルなTableViewを定義する
・カスタムセルを作成する
・TableViewとカスタムセルを連携させる
でいきたいと思います。
最初にUITableViewクラスをインスタンス化します。
サイズはViewの幅いっぱい、スタイルはplainを選択します。
let tableView = UITableView(frame: self.view.bounds, style: .plain)
次に、プロトコルを2つ追加します。
追加するプロトコルはUITableViewDelegate
とUITableViewDataSource
になります。
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { ・・・ }
二つのプロトコルに準拠させるとエラーが発生しますのでFIXします。
すると下記のメソッドが定義されると思います。
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
<#code#>
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
<#code#>
}
1つ目のメソッドはセクションの中に何個のセルを配置するかを指定します。
返り値の型がInt型でセルの数を返します。
今回は仮でitem
という配列を宣言しました。
返り値は配列itemの要素数を返しています。
let item = ["Cell 1", "Cell 2", "Cell 3", "Cell 4", "Cell 5"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return item.count
}
2つ目のメソッドはセルを構成する際の情報を定義します。
今は仮で配列itemの要素をセルのtextLabelに追加したいと思います。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = item[indexPath.row]
return cell
}
要求されたメソッドに処理を記述しましたが、
現在のままではデータを反映させることができません。
viewDidLoad内にtableView.delegate = self
と
tableView.dataSource = self
を記述します。
これを記述することにより、
最初に準拠したプロトコルの処理を自分自身に委譲することができます。
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView(frame: self.view.bounds, style: .plain)
tableView.delegate = self
tableView.dataSource = self
}
最後に、viewに反映させる必要があるのでview.addSubviewを記述します。
override func viewDidLoad() {
super.viewDidLoad()
・・・
view.addSubview(tableView)
}
この時点で一度シミュレータを起動してみます。
すると配列のアイテムが順にセルに格納されていることがわかります。
今回はTwitterのタイムラインなのでカスタムセルを作成する必要があります。
カスタムセルの作り方ですが、
直接ViewControllerに記述するか**.xibを使用**する方法があります。
今回は後者の.xibを使用する方法でカスタムセルを作成します。
いつも通りファイルを作成するのですが、
SubclassをUITableViewCell
にし、Also create XIB fileにチェックを入れます。
画面左の拡張子が.xibのファイルにオブジェクトを配置し、
画面右の拡張子が.swiftのファイルに処理を記述します。
少し雑ですがUIを実装していきます。
アイコン、名前、ツイートの時間が何分前か、ツイートの内容、下のボタン4つを実装しました。
下のボタン4つを押した時の処理は今回は実装せずに、
それ以外のところを実装していきたいと思います。
ただ、押されたことはわかるように後ほどprint()
で出力する仕様にします。
新しく作成したクラスに定義されている、
awakeFromNib()
は
Storyboardまたはnibファイルからロードされた直後に呼ばれるます。
setSelected(_ selected: Bool, animated: Bool)
は
選択状態と通常状態の状態アニメーション処理の処理を行います。
ViewController.swift
のセルを生成するメソッドで、
このカスタムセルを元にセルを生成するように指定します。
まず、カスタムセルを参照できるようにIdentifier
を設定します。
CustomTableViewCell.xibのセルを選択すると右のエディタにIdentifierが表示されます。
identifierを設定した後にViewController.swiftのviewDidLoad内で下記を記述します。
(絶対viewDidLoad内に記述しなければいけない訳ではありません。)
forCellReuseIdentifierに先ほど設定したidentifierを記述します。
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "customCell")
}
次にcellForRowAt内を修正します。
先ほどまではUItableViewCellクラスをインスタンス化していましたが、
customCellを参照する様に変更します。
as!
以降は自分のカスタムセルのファイル名にしてください。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomTableViewCell
cell.content.text = self.item[indexPath.row]
return cell
}
ここまで記述するとシミュレータは次のようになります
カスタムセルの内容が反映されていることがわかります。
ただ、現在のままだと下画像のようにツイートの内容が多くても全て表示されません。
Twitterだったらツイートの内容に合わせて縦幅が増えると思います。
次は高さの自動調整を行います。
各オブジェクトに制約をつけておきます。
その後、Labelの行数をデフォルトの1から0に変更します。
結構Twitterぽくなってきました!
残りは、下記の機能を追加できたらなと思います。
・何分前にツイートされたかを表示
・各ボタンを押した時に、押されたことをログに出力
・ツイートをタップした時にそのツイートの詳細画面を開く機能を追加
最初に、「何分前にツイートされたかを表示」を実装したいと思います。
実装方法としては、現在時刻からツイートされた時刻を引き、何分前か出したいと思います。
今回は配列itemを少々いじってツイートされた時刻を明示的に定義しようと思います。
またついでに名前も格納できるようにしました。
var item = [
["content": "2020年\nが\n終わります。\n今年も\n良い\n一年\nでした。", "date": Date().timeIntervalSince1970 - 313123123, "name": "斎藤"],
["content": "良いお年をお過ごしください。", "date": Date().timeIntervalSince1970 - 123212312, "name": "本田"],
["content": "新年明けましておめでとうございます。", "date": Date().timeIntervalSince1970 - 20000000, "name": "鈴木"],
["content": "お年玉を1万円あげます。", "date": Date().timeIntervalSince1970 - 2323232, "name": "川崎"],
["content": "2021年はもっと勉強を頑張ります。", "date": Date().timeIntervalSince1970 - 13213, "name": "三菱"],
["content": "今年は本厄なので気を引き締めます。", "date": Date().timeIntervalSince1970 - 3333, "name": "豊田"],
["content": "今年もよろしくお願いします。", "date": Date().timeIntervalSince1970 - 10, "name": "武田"]
]
次にセルを形成する際のメソッドに修正を加えました。
let now = Date().timeIntervalSince1970
で現在時刻を取得しています。
let past = item[indexPath.row]["date"] as! TimeInterval
は
配列itemのセルの行と同じ番号にある配列のキー値:"date"
の値を取得しています。
cell.***.text =
の部分は、
カスタムセルのプロパティに値を代入しています。
またviewDidLoad内に下記を定義しました。
item = item.reversed()
で配列内を逆順しています。
最新の内容が末尾に追加されるので先頭が最新になる様に逆順にしています。
override func viewDidLoad() {
super.viewDidLoad()
item = item.reversed()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomTableViewCell
let now = Date().timeIntervalSince1970
let past = item[indexPath.row]["date"] as! TimeInterval
cell.time.text = timeCheck(now: now, past: past)
cell.content.text = (item[indexPath.row]["content"] as! String)
cell.name.text = (item[indexPath.row]["name"] as! String)
return cell
}
先ほどのコードで出現したtimeCheck(now:, past:)
メソッドですが、
私が作成したオリジナルのメソッドになります。
このメソッドは、引数に投稿時の時間と現在の時間を指定しています。
現在の時間から過去の時間を引いて出た差分からどのくらい前かを計算します。
Twitterは○○前のように抽象的に表現します。
なのでそれに適した表現で返せるようにしました。
func timeCheck(now: TimeInterval, past: TimeInterval) -> String {
let timeDiff = Int(now - past)
let year = timeDiff / 31104000
let month = timeDiff / 2592000
let day = timeDiff / 86400
let hour = timeDiff / 3600
let minute = timeDiff / 60
let second = timeDiff
if year != 0 {
return "\(year)年前"
} else if month != 0 {
return "\(month)ヶ月前"
} else if day != 0 {
return "\(day)日前"
} else if hour != 0 {
return "\(hour)時間前"
} else if minute != 0 {
return "\(minute)分前"
} else {
return "\(second)秒前"
}
}
シミュレータは次のようになります。
高さも自動調整されていますし、時間も○時間前のように表示されています。
では、次に「各ボタンを押した時に、押されたことをログに出力」を実装していきます。
各ボタンのタグに値を指定して、どのボタンが押されたかタグで条件分岐をします。
@IBAction func actionButton(_ sender: Any) {
guard let button = sender as? UIButton else {
return
}
switch button.tag {
case 1:
print("リプライボタンが押されました。")
case 2:
print("リツイートボタンが押されました。")
case 3:
print("いいねボタンが押されました。")
case 4:
print("共有ボタンが押されました。")
default:
print("Error:どのボタンが押されたかわかりません。")
}
}
実行結果は次のようになります。
各ボタンを押すとそれに適したログが出力されます。
これでボタンを押した時の処理を実装できました!
次に「ツイートをタップした時にそのツイートの詳細画面を開く機能を追加」を実装します。
セルをタップした時にどのセルがタップされたかを感知するメソッドは
didSelectRowAtを使って行います。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
<#code#>
}
今回は画面遷移を行い別画面で詳細を表示したいと思います。
まずは別画面に画面処理を行う処理を記述します。
画面右上の+
からオブジェクトの追加を行います。
ViewControllerと検索しドラッグ&ドロップを行うと新しいViewを追加できます。
次にファイルの新規作成を行います。
File > New > File > Cocoa Touch Classを選択します。
今回はDetailViewController.swift
というファイル名にしました。
ViewControllerにDetailViewController.swiftを紐付けます。
画面右上のClassの部分に紐付けるファイル名を記載します。
ViewControllerにNavigationControllerを紐付けます。
ViewControllerを選択 > Editor > Embed in > Navigation Controller をクリック
するとViewControllerの左にNavigation Controllerが追加されます。
次にViewControllerとDetailViewControllerを紐付けます。
ViewControllerの左のマークをcontrolキーを押しながらドラッグ&ドロップします。
するとメニューが出てきますのでshow
を選択します。
2つのViewが矢印で繋がればOKです!
次に、ViewControllerから画面遷移する際にidentifierを参考に画面遷移するので、
DetailViewControllerのidentifierを定義していきます。
では、先ほど紹介したdidSelectRowAtに画面遷移の記述していきます。
let detailVC = ・・・
の処理は、
StoryboardのインスタンスからDetailViewControllerを取得する処理になります。
どのViewControllerがDetailViewControllerか分からないので、
(identifier: "detailView")
の部分でDetailViewControllerを識別しています。
navigationController?.pushViewController(detailVC, animated: true)
は、
画面遷移を行う処理になります。第一引数で画面遷移先を指定します。
tableView.deselectRow(at: indexPath, animated: true)
は、
セルをタップするとそのセルの色が変わるのですが、
画面遷移後に色を元に戻す処理を行なっています。
記述した場合としていない場合を試してみれば違いがわかると思います!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailVC = storyboard?.instantiateViewController(identifier: "detailView") as! DetailViewController
navigationController?.pushViewController(detailVC, animated: true)
tableView.deselectRow(at: indexPath, animated: true)
}
ここまでの処理を実行すると画面遷移できていることが確認できると思います。
その1はここまでで終了にしたいと思います。
最低限のTwitterの実装はこの時点でできているので
是非ここから個人的に修正を加えてみてください!
また、より良いコードがありましたらご指摘よろしくお願いします。
最後までご覧いただきありがとうございました。