Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@Kai_82

【Swift】UITableViewでTwitterのタイムラインを実装してみた(その1)

UITableViewでタイムラインを作る。

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

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

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

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

GitHubリポジトリはコチラになります。

Table Viewを作る

まず最初に行うこととしては
・シンプルなTableViewを定義する
・カスタムセルを作成する
・TableViewとカスタムセルを連携させる

でいきたいと思います。

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

ViewController.swift

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

次に、プロトコルを2つ追加します。
追加するプロトコルはUITableViewDelegateUITableViewDataSourceになります。

ViewController.swift

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

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

ViewController.swift

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        <#code#>
    }

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

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

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

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
    }

2つ目のメソッドはセルを構成する際の情報を定義します。

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

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

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

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

ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()

        ・・・

        view.addSubview(tableView)
    }

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

すると配列のアイテムが順にセルに格納されていることがわかります。
スクリーンショット 2021-01-01 10.27.23.png

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

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

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

いつも通りファイルを作成するのですが、
SubclassをUITableViewCellにし、Also create XIB fileにチェックを入れます。
スクリーンショット 2021-01-01 10.51.20.png

すると二つのファイルが作成されます。
スクリーンショット 2021-01-01 10.53.32.png

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

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

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

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

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

setSelected(_ selected: Bool, animated: Bool)
選択状態と通常状態の状態アニメーション処理の処理を行います。
スクリーンショット 2021-01-01 11.15.45.png

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

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

こちらのIdentifierに任意の名前を設定します。
スクリーンショット 2021-01-01 12.35.06.png

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

ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()

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

    }

次にcellForRowAt内を修正します。

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

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

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

ただ、現在のままだと下画像のようにツイートの内容が多くても全て表示されません。
Twitterだったらツイートの内容に合わせて縦幅が増えると思います。
スクリーンショット 2021-01-01 12.54.56.png

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

ラベルは高さの自動調整を行うので上下左右に制約をつけます。
スクリーンショット 2021-01-01 13.00.42.png

その後、Labelの行数をデフォルトの1から0に変更します。
スクリーンショット 2021-01-01 13.01.20.png

そうすることで文字数によって高さを自動で調整します。
スクリーンショット 2021-01-01 13.05.23.png

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

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

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

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

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

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

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

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

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

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
各ボタンを押すとそれに適したログが出力されます。
これでボタンを押した時の処理を実装できました!

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

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

ViewController.swift

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        <#code#>
    }

今回は画面遷移を行い別画面で詳細を表示したいと思います。
まずは別画面に画面処理を行う処理を記述します。

画面右上の+からオブジェクトの追加を行います。
ViewControllerと検索しドラッグ&ドロップを行うと新しいViewを追加できます。
スクリーンショット 2021-01-01 19.48.23.png

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

File > New > File > Cocoa Touch Classを選択します。
今回はDetailViewController.swiftというファイル名にしました。
スクリーンショット 2021-01-01 19.51.25.png

ViewControllerにDetailViewController.swiftを紐付けます。
画面右上のClassの部分に紐付けるファイル名を記載します。
スクリーンショット 2021-01-01 19.50.41.png

ViewControllerにNavigationControllerを紐付けます。
ViewControllerを選択 > Editor > Embed in > Navigation Controller をクリック
スクリーンショット 2021-01-01 20.39.00.png

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

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

するとメニューが出てきますのでshowを選択します。
2つのViewが矢印で繋がればOKです!
スクリーンショット 2021-01-01 20.41.27.png

次に、ViewControllerから画面遷移する際にidentifierを参考に画面遷移するので、
DetailViewControllerのidentifierを定義していきます。
スクリーンショット 2021-01-01 20.56.35.png

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

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

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

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

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

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

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

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

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

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

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

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

続きはコチラ
->【Swift】UITableViewでTwitterのタイムラインを実装してみた(その2)

3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Kai_82
プロフィールをご覧いただきありがとうございます。 独学でSwiftを勉強しており、学んだ事をどんどん記事にしていきますので もしお時間ありましたらご覧ください! 学習しながら更新していくので間違いが多々あると思います。 その時はご指摘いただけますと幸いです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?