継承済みの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ですが、入力時のカーソル長押し時のメニューを画面ごとにカスタマイズ可能になります。