見出し画像

【徒然iOS】気ままにUIKit25〜UITableViewDelegate〜

概要

このマガジンは四十を過ぎたおっさんが、

を参考にStoryboardでiOSアプリを完全に趣味で楽しんでいるだけな記事を気ままに上げてます。

今回

をやる〜〜〜!

前準備

  1. 念の為、バックアップ

  2. 新しいクラス

  3. ビューコントローラの追加

  4. イニシャルビューの変更

をいつも通りやってから本題へ💃

こんな感じ

本題

テーブルビューデリゲートってなに?

UITableViewDelegate(テーブルビューデリゲート)とは、
テーブルのデータが選択また編集されたときに呼び出されるメソッドが定義されたプロトコル
要は、書いてる通り、イベント処理ってことね。

メソッドはすべてoptional
👉必要なものだけ実装すればいい。

手順を実際に追いながらやってみよう!👀

⒈前回までと同様に、テーブルビューを配置して、datasource連携

連携しました〜〜〜

⒉もう一度テーブルビューを黄色い丸に運び、今度は「delegate」を選択

delegateを選択して〜〜〜〜
両方、設定できてることを確認👀

⒊Prototype Cellsを1に変更

テーブルビューセルが表示された🕺

⒋IdentifierにTestCellに

⒌ラベルを配置

こんな感じかな💦

⒍ラベルをアウトレット接続

接続中
接続後

⒎UITableViewDataSource,UITableViewDelegateを適用

プロトコルを適用
FIXをクリック
必要なメソッドが追加された

⒏JumptoDefinitionで、UITableViewDelegateプロトコルのメソッドを確認しとく

JumptoDefinitionをクリック

コードはこんな感じ

@MainActor public protocol UITableViewDelegate : UIScrollViewDelegate {

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int)

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int)

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int)

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int)

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat

    @available(iOS 7.0, *)
    optional func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat

    @available(iOS 7.0, *)
    optional func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat

    @available(iOS 7.0, *)
    optional func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath)

    @available(iOS 6.0, *)
    optional func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath)

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath?

    @available(iOS 3.0, *)
    optional func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath?

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)

    @available(iOS 3.0, *)
    optional func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)

    /**
     * @abstract Called to determine if a primary action can be performed for the row at the given indexPath.
     * See @c tableView:performPrimaryActionForRowAtIndexPath: for more details about primary actions.
     *
     * @param tableView This UITableView
     * @param indexPath NSIndexPath of the row
     *
     * @return `YES` if the primary action can be performed; otherwise `NO`. If not implemented defaults to `YES` when not editing
     * and `NO` when editing.
     */
    @available(iOS 16.0, *)
    optional func tableView(_ tableView: UITableView, canPerformPrimaryActionForRowAt indexPath: IndexPath) -> Bool

    /**
     * @abstract Called when the primary action should be performed for the row at the given indexPath.
     *
     * @discussion Primary actions allow you to distinguish between a change of selection (which can be based on focus changes or
     * other indirect selection changes) and distinct user actions. Primary actions are performed when the user selects a cell without extending
     * an existing selection. This is called after @c willSelectRow and @c didSelectRow , regardless of whether the cell's selection
     * state was allowed to change.
     *
     * As an example, use @c didSelectRowAtIndexPath for updating state in the current view controller (i.e. buttons, title, etc) and
     * use the primary action for navigation or showing another split view column.
     *
     * @param tableView This UITableView
     * @param indexPath NSIndexPath of the row to perform the action on
     */
    @available(iOS 16.0, *)
    optional func tableView(_ tableView: UITableView, performPrimaryActionForRowAt indexPath: IndexPath)

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle

    @available(iOS 3.0, *)
    optional func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String?

    @available(iOS, introduced: 8.0, deprecated: 13.0)
    optional func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?

    @available(iOS 11.0, *)
    optional func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?

    @available(iOS 11.0, *)
    optional func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath)

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?)

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath

    @available(iOS 2.0, *)
    optional func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int

    @available(iOS, introduced: 5.0, deprecated: 13.0)
    optional func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool

    @available(iOS, introduced: 5.0, deprecated: 13.0)
    optional func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool

    @available(iOS, introduced: 5.0, deprecated: 13.0)
    optional func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?)

    @available(iOS 9.0, *)
    optional func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool

    @available(iOS 9.0, *)
    optional func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext) -> Bool

    @available(iOS 9.0, *)
    optional func tableView(_ tableView: UITableView, didUpdateFocusIn context: UITableViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator)

    @available(iOS 9.0, *)
    optional func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath?

    /// Determines if the row at the specified index path should also become selected when focus moves to it.
    /// If the table view's global selectionFollowsFocus is enabled, this method will allow you to override that behavior on a per-index path basis. This method is not called if selectionFollowsFocus is disabled. 
    @available(iOS 15.0, *)
    optional func tableView(_ tableView: UITableView, selectionFollowsFocusForRowAt indexPath: IndexPath) -> Bool

    @available(iOS 11.0, *)
    optional func tableView(_ tableView: UITableView, shouldSpringLoadRowAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext) -> Bool

    @available(iOS 13.0, *)
    optional func tableView(_ tableView: UITableView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool

    @available(iOS 13.0, *)
    optional func tableView(_ tableView: UITableView, didBeginMultipleSelectionInteractionAt indexPath: IndexPath)

    @available(iOS 13.0, *)
    optional func tableViewDidEndMultipleSelectionInteraction(_ tableView: UITableView)

    /**
     * @abstract Called when the interaction begins.
     *
     * @param tableView  This UITableView.
     * @param indexPath  IndexPath of the row for which a configuration is being requested.
     * @param point      Location of the interaction in the table view's coordinate space
     *
     * @return A UIContextMenuConfiguration describing the menu to be presented. Return nil to prevent the interaction from beginning.
     *         Returning an empty configuration causes the interaction to begin then fail with a cancellation effect. You might use this
     *         to indicate to users that it's possible for a menu to be presented from this element, but that there are no actions to
     *         present at this particular time.
     */
    @available(iOS 13.0, *)
    optional func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?

    /**
     * @abstract Called when the interaction begins. Return a UITargetedPreview to override the default preview created by the table view.
     *
     * @param tableView      This UITableView.
     * @param configuration  The configuration of the menu about to be displayed by this interaction.
     */
    @available(iOS 13.0, *)
    optional func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?

    /**
     * @abstract Called when the interaction is about to dismiss. Return a UITargetedPreview describing the desired dismissal target.
     * The interaction will animate the presented menu to the target. Use this to customize the dismissal animation.
     *
     * @param tableView      This UITableView.
     * @param configuration  The configuration of the menu displayed by this interaction.
     */
    @available(iOS 13.0, *)
    optional func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?

    /**
     * @abstract Called when the interaction is about to "commit" in response to the user tapping the preview.
     *
     * @param tableView      This UITableView.
     * @param configuration  Configuration of the currently displayed menu.
     * @param animator       Commit animator. Add animations to this object to run them alongside the commit transition.
     */
    @available(iOS 13.0, *)
    optional func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating)

    /**
     * @abstract Called when the table view is about to display a menu.
     *
     * @param tableView       This UITableView.
     * @param configuration   The configuration of the menu about to be displayed.
     * @param animator        Appearance animator. Add animations to run them alongside the appearance transition.
     */
    @available(iOS 14.0, *)
    optional func tableView(_ tableView: UITableView, willDisplayContextMenu configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?)

    /**
     * @abstract Called when the table view's context menu interaction is about to end.
     *
     * @param tableView       This UITableView.
     * @param configuration   Ending configuration.
     * @param animator        Disappearance animator. Add animations to run them alongside the disappearance transition.
     */
    @available(iOS 14.0, *)
    optional func tableView(_ tableView: UITableView, willEndContextMenuInteraction configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?)
}

めちゃくちゃ長いね💦

⒐今回追加するコードを組み込んでいく

今回追加する部分

//データ
    var dataList = ["青山","阿部","加藤","神田","佐藤","坂田"]
    
    //データを返すメソッド
    func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell {
        
        //セルを取得する。
        let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath:indexPath) as UITableViewCell
        
        cell.textLabel?.text = dataList[indexPath.row]
        return cell
    }
 
    
    //データの個数を返すメソッド
    func tableView(tableView:UITableView, numberOfRowsInSection section:Int) -> Int {
        return dataList.count
    }
 
    
    //データ選択後に呼び出されるメソッド
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
    {
        let selectData = tableView.cellForRowAtIndexPath(indexPath)!.textLabel!.text
        testLabel.text = "\(selectData!)さんが呼び出されました。"
    }

メソッドが違ったりするので、ゆっくり処理コードや赤色警告を補正しながらやると、

前回までと同じところはこんな感じ

データ選択後に呼び出されるメソッドを追加すると、警告エラー発生なので、一個一個直していく

上から1つ目

黄色エラーはこんな感じ

privateにしろってことみたいだから一旦後回し👀
2つ目を見ると〜〜

FIXをクリック
再度、FIXをクリックして
これでエラーはひとつ消えた

3つ目のエラーは?

単純にそんな名前のラベルは存在しないってことみたいなので〜〜〜
ラベル名を変更して、エラーを消す

黄色エラーは一旦無視して、どうなるか

10 シミュレータを実行

反応しないので

黄色エラー対処

FIXをクリックして
private化しても
変化なし
元に戻して

コードをよく見ると

    //データ選択後に呼び出されるメソッド
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
    {

NSIndexPathになちゃってるんで

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {

に変更して実行

実行できたね

ここまでのコード

class TableDelegateViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
    
    @IBOutlet weak var delegateLabel: UILabel!
    
    //データ
    var dataList = ["青山","阿部","加藤","神田","佐藤","坂田"]
    
    //データの個数を返すメソッド
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }
    
    //データの個数を返すメソッド
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //セルを取得する。
        let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for:indexPath) as UITableViewCell
        
        cell.textLabel?.text = dataList[indexPath.row]
        return cell
    }
    
    //データ選択後に呼び出されるメソッド
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        let selectData = tableView.cellForRow(at: indexPath as IndexPath)!.textLabel!.text
        delegateLabel.text = "\(selectData!)さんが呼び出されました。"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

後半をやる〜〜〜

追加するコードは

  //ハイライト前に呼び出されるメソッド
    func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool
    {
        print("1 ハイライト前")
        return true
    }
    
    //ハイライト後に呼び出されるメソッド
    func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath)
    {
        print("2 ハイライト後")
    }
    
    //ハイライト解除後に呼び出されるメソッド
    func tableView(tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: NSIndexPath)
    {
        print("3 ハイライト解除後")
    }
    
    //データ選択前に呼び出されるメソッド
    func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath?
    {
        print("4 データ選択前")
        return indexPath
    }
 
    //データ選択後に呼び出されるメソッド
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
    {
        print("5 データ選択後")
    }
    
    //データ選択解除前に呼び出されるメソッド
    func tableView(tableView: UITableView, willDeselectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath?
    {
        print("6 データ選択解除前")
        return indexPath
    }
    
    //データ選択解除後に呼び出されるメソッド
    func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath)
    {
        print("7 データ選択解除後")
    }
 

なだけなんで、重複してる
didSelectRowAtIndexPath
だけ、
print("5 データ選択後")をメソッドの中に追加して、
他の部分の
NSIndexPathを
IndexPathにしてFIXしていくと、、、

黄色エラー警告も消えたので、

シミュレータ再実行

てな感じで実行できた🕺

11 ブラッシュアップ

今回もAutoLayoutだけ〜〜〜

制約を追加
追加後、シミュレータで実行
問題なし🕺

今回のコード(まとめ)

class TableDelegateViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
    @IBOutlet weak var delegateLabel: UILabel!
    //データ
    var dataList = ["青山","阿部","加藤","神田","佐藤","坂田"]
    //データの個数を返すメソッド
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }
    //データの個数を返すメソッド
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //セルを取得する。
        let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for:indexPath) as UITableViewCell
        
        cell.textLabel?.text = dataList[indexPath.row]
        return cell
    }
    //ハイライト前に呼び出されるメソッド
    func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool
    {
        print("1 ハイライト前")
        return true
    }
    //ハイライト後に呼び出されるメソッド
    func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath)
    {
        print("2 ハイライト後")
    }
    //ハイライト解除後に呼び出されるメソッド
    func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath)
    {
        print("3 ハイライト解除後")
    }
    //データ選択前に呼び出されるメソッド
    func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath?
    {
        print("4 データ選択前")
        return indexPath
    }
    //データ選択後に呼び出されるメソッド
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        let selectData = tableView.cellForRow(at: indexPath as IndexPath)!.textLabel!.text
        delegateLabel.text = "\(selectData!)さんが呼び出されました。"
        print("5 データ選択後")
    }
    //データ選択解除前に呼び出されるメソッド
    func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath?
    {
        print("6 データ選択解除前")
        return indexPath
    }
    //データ選択解除後に呼び出されるメソッド
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)
    {
        print("7 データ選択解除後")
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

ポイント

前回のさて次回は、

で、佳境だね〜〜て書いたんだけど

今回のDelegateプロトコルは、

後半のコードで、

ビューのライフサイクルイベント
(ビューが表示された、セルが選択されたなど)
を管理する際に重要てことがイメージできたかなと思う。

ボタンの時には、アクション接続方法の設定

で色々あったのを思い出してもらえればなあって感じ。

さらに、

今回は、NSIndexPathがIndexPathに変更した点も重要👀
NSが付くものは、

大体Objective-C時代の名残な
他のメソッドをNSなしに変更
👉組み込むコードもNSなしに変更しないと正常に機能しない

ってあくまでも一例。

もちろん、全てを勉強して熟知してることに越したことはないし、コードを全て自分で起こせる人には関係ない話。

あくまでも、細かいコードを忘れて、数年ぶりに思い出しながらやっていても、

やり慣れていると、おかしいところに気づいて直せるし、
こういった勘も働くよ

って話

まとめ

UIKitも動かし慣れていると奥が深いし、面白い

Apple公式

その他の参考リンク(古い)

さて、次回は

をやる〜〜〜〜!

追記

そうそう、こういうスタイルで記事を書いてると、

「この人は、コードを起こせない人」

と勘違いする人もいるかもしれないけど、

コードの解説まで書くと長くなるし、他の記事

なんかでまとめてるし、コード解説をきちんとするなら

から入ることになるけど、細かいコードの解説に深く入りして、

木を見て森を見ず

になるよりも、

作り方、動かし方、考え方

を重視して、大枠で

綺麗なサンプルとイメージ=全体像

を作っておけば、あとはゆっくりやりたい人はコードを吟味できるかなと。

ひとつのプロジェクトファイルにサンプルを集めておけば、

今後コードの書き方が変わっても、
赤や黄色の警告で表示してくれる

しね!ここまでの記事を見ただけでも、すでに大分書き方が変わった箇所もあるから、コード自体を丸暗記や理解してもあんまり意味なく、それよりは

  • パーツの配置

  • アシスタントやビュー自体への接続

  • 必要なプロトコルの追加

  • プロトコルに応じたメソッド(stubs)

  • AutoLayoutの制約付け

といったところを理解や暗記より、素直に身につければ充分かなと。

忘れてもすぐ思い出せるように
覚えるべきところ=要点
を押さえる

でやっておけば何年か経っても思い出すしね!

やり方はひとそれぞれー!

この記事が気に入ったらサポートをしてみませんか?