【Swift】UITableViewでTwitterのタイムラインを実装してみた（その１）

#UITableViewでタイムラインを作る。
UITableView自体を使うのは慣れれば簡単だと思いますが、
Cellをカスタムで作成するのは少し大変だと思います。

今回は、作成したばかりのプロジェクトで１からタイムラインを実装してみます。

また、私が説明下手なのもあり記事がかなり長くなってしまいました。
なので全部で３記事に分けております。

軽い気持ちで流しみしていただけると助かります。

GitHubリポジトリは[**コチラ**](https://github.com/onishi-app/SampleTableView)になります。


##Table Viewを作る
まず最初に行うこととしては
**・シンプルなTableViewを定義する
・カスタムセルを作成する
・TableViewとカスタムセルを連携させる**
でいきたいと思います。

最初にUITableViewクラスをインスタンス化します。
サイズはViewの幅いっぱい、スタイルはplainを選択します。

```swift:ViewController.swift

let tableView = UITableView(frame: self.view.bounds, style: .plain)
```

次に、プロトコルを２つ追加します。
追加するプロトコルは`UITableViewDelegate`と`UITableViewDataSource`になります。

```swift:ViewController.swift

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { ・・・ }
```

二つのプロトコルに準拠させるとエラーが発生しますのでFIXします。
すると下記のメソッドが定義されると思います。

```swift:ViewController.swift

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        <#code#>
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        <#code#>
    }
```

１つ目のメソッドは**セクションの中に何個のセルを配置するか**を指定します。
返り値の型がInt型でセルの数を返します。

今回は仮で`item`という配列を宣言しました。
返り値は配列itemの要素数を返しています。

```swift:ViewController.swift
    let item = ["Cell 1", "Cell 2", "Cell 3", "Cell 4", "Cell 5"]

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return item.count
    }
```
２つ目のメソッドはセルを構成する際の情報を定義します。

今は仮で配列itemの要素をセルのtextLabelに追加したいと思います。

```swift:ViewController.swift

    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`を記述します。

これを記述することにより、
最初に準拠したプロトコルの処理を自分自身に委譲することができます。

```swift:ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView(frame: self.view.bounds, style: .plain)
        tableView.delegate = self
        tableView.dataSource = self
    }
```

最後に、viewに反映させる必要があるのでview.addSubviewを記述します。

```swift:ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()

        ・・・

        view.addSubview(tableView)
    }
```

この時点で一度シミュレータを起動してみます。

すると配列のアイテムが順にセルに格納されていることがわかります。
![スクリーンショット 2021-01-01 10.27.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/6b1b231d-53e6-de48-1618-bbd12fb1b54f.png)

今回はTwitterのタイムラインなのでカスタムセルを作成する必要があります。

カスタムセルの作り方ですが、
**直接ViewControllerに記述**するか**.xibを使用**する方法があります。

今回は後者の.xibを使用する方法でカスタムセルを作成します。

いつも通りファイルを作成するのですが、
Subclassを`UITableViewCell`にし、Also create XIB fileにチェックを入れます。
![スクリーンショット 2021-01-01 10.51.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/425f9873-be7d-329a-c275-2f74621b2a82.png)

すると二つのファイルが作成されます。
![スクリーンショット 2021-01-01 10.53.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/51937ae8-78a6-528f-860a-177a0d368639.png)

画面左の拡張子が.xibのファイルにオブジェクトを配置し、
画面右の拡張子が.swiftのファイルに処理を記述します。

少し雑ですがUIを実装していきます。
アイコン、名前、ツイートの時間が何分前か、ツイートの内容、下のボタン４つを実装しました。

下のボタン４つを押した時の処理は今回は実装せずに、
それ以外のところを実装していきたいと思います。

ただ、押されたことはわかるように後ほど`print()`で出力する仕様にします。

新しく作成したクラスに定義されている、
`awakeFromNib()`は
Storyboardまたはnibファイルからロードされた直後に呼ばれるます。

`setSelected(_ selected: Bool, animated: Bool)`は
選択状態と通常状態の状態アニメーション処理の処理を行います。
![スクリーンショット 2021-01-01 11.15.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/03897350-f35f-617c-4507-168bceeeead5.png)

`ViewController.swift`のセルを生成するメソッドで、
このカスタムセルを元にセルを生成するように指定します。

まず、カスタムセルを参照できるように`Identifier`を設定します。
CustomTableViewCell.xibのセルを選択すると右のエディタにIdentifierが表示されます。

こちらのIdentifierに任意の名前を設定します。
![スクリーンショット 2021-01-01 12.35.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/a616e92e-8205-183c-d39f-eb13af592788.png)

identifierを設定した後にViewController.swiftのviewDidLoad内で下記を記述します。
(絶対viewDidLoad内に記述しなければいけない訳ではありません。)
forCellReuseIdentifierに先ほど設定したidentifierを記述します。

```swift:ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "customCell")
        
    }
```

次にcellForRowAt内を修正します。

先ほどまではUItableViewCellクラスをインスタンス化していましたが、
customCellを参照する様に変更します。

`as!`以降は自分のカスタムセルのファイル名にしてください。

```swift:ViewController.swift

    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
    }
```

ここまで記述するとシミュレータは次のようになります
カスタムセルの内容が反映されていることがわかります。
![スクリーンショット 2021-01-01 12.48.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/7f806eae-5641-9be3-141a-5067992189ed.png)

ただ、現在のままだと下画像のようにツイートの内容が多くても全て表示されません。
Twitterだったらツイートの内容に合わせて縦幅が増えると思います。
![スクリーンショット 2021-01-01 12.54.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/f7d1fb94-c9f5-cfd5-5cc5-33780f5d196e.png)

次は高さの自動調整を行います。
各オブジェクトに制約をつけておきます。

ラベルは高さの自動調整を行うので上下左右に制約をつけます。
![スクリーンショット 2021-01-01 13.00.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/22cc6dd8-f9bf-0496-df7e-a641400f2023.png)

その後、Labelの行数をデフォルトの１から０に変更します。
![スクリーンショット 2021-01-01 13.01.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/6edf5305-146e-4828-9a2a-843c98a92dc2.png)

そうすることで文字数によって高さを自動で調整します。
![スクリーンショット 2021-01-01 13.05.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e1dddd47-5c5d-c72f-9684-47845a1ff8eb.png)

結構Twitterぽくなってきました！

残りは、下記の機能を追加できたらなと思います。
**・何分前にツイートされたかを表示
・各ボタンを押した時に、押されたことをログに出力
・ツイートをタップした時にそのツイートの詳細画面を開く機能を追加**

最初に、「**何分前にツイートされたかを表示**」を実装したいと思います。
実装方法としては、現在時刻からツイートされた時刻を引き、何分前か出したいと思います。

今回は配列itemを少々いじってツイートされた時刻を明示的に定義しようと思います。
またついでに名前も格納できるようにしました。

```swift:ViewController.swift

    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()`で配列内を逆順しています。
最新の内容が末尾に追加されるので先頭が最新になる様に逆順にしています。

```swift:ViewController.swift

    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は○○前のように抽象的に表現します。
なのでそれに適した表現で返せるようにしました。

```swift:ViewController.swift

    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)秒前"
        }
        
    }
```

シミュレータは次のようになります。
高さも自動調整されていますし、時間も○時間前のように表示されています。
![スクリーンショット 2021-01-01 16.03.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/dd6a0d5a-bb12-f9f9-1fad-ed1c8bbba6b3.png)

では、次に「**各ボタンを押した時に、押されたことをログに出力**」を実装していきます。

各ボタンのタグに値を指定して、どのボタンが押されたかタグで条件分岐をします。

```swift:CustomTableViewController.swift

    @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:どのボタンが押されたかわかりません。")
        }
    }
```

実行結果は次のようになります。

![スクリーンショット 2021-01-01 16.18.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/1020b91d-d12e-712a-781d-8ca9717d4e77.png)
各ボタンを押すとそれに適したログが出力されます。
これでボタンを押した時の処理を実装できました！

次に「**ツイートをタップした時にそのツイートの詳細画面を開く機能を追加**」を実装します。

セルをタップした時にどのセルがタップされたかを感知するメソッドは
didSelectRowAtを使って行います。

```swift:ViewController.swift

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        <#code#>
    }
```
今回は画面遷移を行い別画面で詳細を表示したいと思います。
まずは別画面に画面処理を行う処理を記述します。

画面右上の`+`からオブジェクトの追加を行います。
ViewControllerと検索しドラッグ＆ドロップを行うと新しいViewを追加できます。
![スクリーンショット 2021-01-01 19.48.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/532d78bb-5793-7fdb-2fb0-d1363b0503e6.png)

次にファイルの新規作成を行います。

File > New > File > Cocoa Touch Classを選択します。
今回は`DetailViewController.swift`というファイル名にしました。
![スクリーンショット 2021-01-01 19.51.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e0ecf390-7434-591f-d40e-48b1ea4eea66.png)

ViewControllerにDetailViewController.swiftを紐付けます。
画面右上のClassの部分に紐付けるファイル名を記載します。
![スクリーンショット 2021-01-01 19.50.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/30b844a0-ac81-38e8-157c-6c075d477690.png)

ViewControllerにNavigationControllerを紐付けます。
ViewControllerを選択 > Editor > Embed in > Navigation Controller をクリック
![スクリーンショット 2021-01-01 20.39.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/59d62cad-7574-7a65-6356-84beab0479c3.png)

するとViewControllerの左にNavigation Controllerが追加されます。

次にViewControllerとDetailViewControllerを紐付けます。
ViewControllerの左のマークを**controlキー**を押しながらドラッグ＆ドロップします。

するとメニューが出てきますので`show`を選択します。
２つのViewが矢印で繋がればOKです！
![スクリーンショット 2021-01-01 20.41.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/89545022-e845-1ac4-c78e-8fdc09fe6ffb.png)

次に、ViewControllerから画面遷移する際にidentifierを参考に画面遷移するので、
DetailViewControllerのidentifierを定義していきます。
![スクリーンショット 2021-01-01 20.56.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e08364e6-c0d7-e61f-e3da-bd15ae1f7ba7.png)


では、先ほど紹介したdidSelectRowAtに画面遷移の記述していきます。

`let detailVC = ・・・`の処理は、
StoryboardのインスタンスからDetailViewControllerを取得する処理になります。

どのViewControllerがDetailViewControllerか分からないので、
`(identifier: "detailView")`の部分でDetailViewControllerを識別しています。

`navigationController?.pushViewController(detailVC, animated: true)`は、
画面遷移を行う処理になります。第一引数で画面遷移先を指定します。

`tableView.deselectRow(at: indexPath, animated: true)`は、
セルをタップするとそのセルの色が変わるのですが、
画面遷移後に色を元に戻す処理を行なっています。

記述した場合としていない場合を試してみれば違いがわかると思います！

```swift:ViewController

    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)
    }
```

ここまでの処理を実行すると画面遷移できていることが確認できると思います。

その１はここまでで終了にしたいと思います。

最低限のTwitterの実装はこの時点でできているので
是非ここから個人的に修正を加えてみてください！

また、より良いコードがありましたらご指摘よろしくお願いします。

最後までご覧いただきありがとうございました。

続きはコチラ
->[【Swift】UITableViewでTwitterのタイムラインを実装してみた（その２）](https://qiita.com/Kai_82/items/26ab28296b81f93101e6)