0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

定義済みのdelegateメソッドのうち一部を別クラスに委譲する

Posted at

継承済みのdelegateメソッドのうち一つだけを別クラスに委譲する

UITextViewのカスタムクラスを作成し、そのクラス内でUITextViewDelegateを継承し、いくつかのdelegateメソッドを実装していました。
ですが、delegateメソッドのうち一つだけをViewControllerで実装したいという場面がありました。
その対処方法についての記事です。

つまり、UITextView を継承したカスタムクラスを使う際、
「基本的な編集イベントは内部で処理したいけど、一部のdelegateメソッドだけViewController側で扱いたい」
という場合です。

例えば:

  • 入力開始や終了などの処理はカスタムビュー内で完結させたい
  • でも editMenuForTextIn のような UIカスタマイズだけは外から制御したい

結論としては、「delegate の一部だけを外部にフォワーディング」しました。


背景:delegate の二重登録はできない

カスタムクラス内部で self.delegate = self としてdelegateメソッドを実装して、その後呼び出し元(ViewController)で delegate をセットするとカスタムクラス側のdelegateメソッドは呼び出されません。
すべてViewController側の処理で上書きされます。


特定の delegate メソッドだけを外部にフォワーディング

① カスタムクラス内で delegate を self に設定
② 外部用の delegate プロパティを用意
③ 特定の delegate メソッドだけ外部に転送

CustomTextView クラス


import UIKit

class CustomTextView: UITextView, UITextViewDelegate {

    // 外部から設定できる delegate(特定のメソッド用)
    weak var editMenuDelegate: UITextViewDelegate?

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        self.delegate = self
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class CustomTextView: UITextViewDelegate {

    func textViewDidBeginEditing(_ textView: UITextView) {
        // 編集開始時の内部処理
        print("Begin editing")
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        // 編集終了時の内部処理
        print("End editing")
    }

    // MARK: - UIMenuの制御だけ更に委譲
    func textView(_ textView: UITextView,
                  editMenuForTextIn range: NSRange,
                  suggestedActions: [UIMenuElement]) -> UIMenu? {
        // 外部(ViewController)に転送
        if let editMenuDelegate = editMenuDelegate,
           let menu = editMenuDelegate.textView?(textView, editMenuForTextIn: range, suggestedActions: suggestedActions) {
            return menu
        }
        return UIMenu(children: suggestedActions)
    }
}

ViewController側の実装

class ViewController: UIViewController {

    private let textView = CustomTextView()

    override func viewDidLoad() {
        super.viewDidLoad()

        textView.frame = CGRect(x: 20, y: 100, width: 300, height: 150)
        textView.backgroundColor = .systemGray6
        view.addSubview(textView)

        textView.editMenuDelegate = self
    }
}

extension ViewController: UITextViewDelegate {
    // 外部 delegate として受け取るメソッド
    func textView(_ textView: UITextView,
                  editMenuForTextIn range: NSRange,
                  suggestedActions: [UIMenuElement]) -> UIMenu? {
        let customAction = UIAction(title: "Custom Action", handler: { [unowned self] _ in
            print("custom action")
        })
        let customMenu = UIMenu(title: "Custom Menu", children: suggestedActions)
        return customMenu
    }
}

こうすることで、内部で完結したロジックと、外部に任せたいカスタマイズを分離でき、
特定の delegate メソッドだけ ViewController に任せることができます。
上記の場合、汎用的なUITextViewですが、入力時のカーソル長押し時のメニューを画面ごとにカスタマイズ可能になります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?