Sunil Sharma
Sunil Sharma

Reputation: 2689

Using UITextViewDelegate in subclass and in UIViewController

I am subclassing UITextView and implementing some delegate methods in subclass like textViewDidChangeSelection but I also need to get notify in View Controller for UITextView delegates. So if I create object of subclass and set textview delegate in view controller then delegates method is notified only in view controller not inside subclass. I need to notify both class. And language I am using is swift 2

I tried to inherit UITextViewDelegate in subclass delegate:

@objc protocol CustomTextViewDelegate:UITextViewDelegate {

    func customTextViewDidChangeSize(chatTextView: CustomTextView)

}

and then in VC:

let customTV = CustomTextView()
customTV.customTextViewDelegate = self

but any textview delegate method is not getting called.

Upvotes: 2

Views: 1291

Answers (4)

Fattie
Fattie

Reputation: 12206

Very simple 2023 approach.

This is a common, everyday thing and the idiom is just:

(1) Yes, you simply "keep" the delegate set by consumers in a variable:

class YourTextView: UITextView, UITextViewDelegate {
    
    weak var outsideDelegate: UITextViewDelegate?
    
    override var delegate: UITextViewDelegate? {
        set { outsideDelegate = newValue }
        get { return outsideDelegate }
    }

it's that easy. And

(2) You simply set the delegate to "yourself"

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        common()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        common()
    }
    
    func common() {
        super.delegate = self
    }

(3) Simply go ahead and use any of "your" delegates as you wish. Note that of course, obviously, you must call on the "outside super" - assuming you want to.

(It's NOT possible to "automate" this in some way, and it would make no sense at all to do so.)

    func textViewDidBeginEditing(_ textView: UITextView) {
        .. some layout code, for example ..
        outsideDelegate?.textViewDidBeginEditing?(textView)
    }
    
    func textViewDidChange(_ textView: UITextView) {
        outsideDelegate?.textViewDidChange?(textView)
        .. some layout code, for example ..
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        .. some database code, for example ..
    }

As an important detail, note that you may need to call the "outside" delegate either before or after your own work. Note that, of course, this applies every single time you have ever written an iOS call (like "viewDidLoad" where you have to call super. In the three examples above the "outside" delegate is called after, before, and not at all.

Here's the whole thing to copy and paste.

class YourTextView: UITextView, UITextViewDelegate {
    
    weak var outsideDelegate: UITextViewDelegate?
    
    override var delegate: UITextViewDelegate? {
        set { outsideDelegate = newValue }
        get { return outsideDelegate }
    }

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        common()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        common()
    }
    
    func common() {
        super.delegate = self
    }
    
    .. your delegate calls go here ..
    .. you must (of course) call the outsideDelegate as you see fit ..
 }

Again - there is NO WAY to "automate" calling the consumer delegates. It would make no sense at all to try to do so. You just call them in "your" degelates, if relevant and as you see fit. Exactly as in the million times you have called "super. ..." in iOS code. It makes absolutely no sense to say "oh you may forget to do it". Any of the million times you have had to type "super. ..." in iOS, you may have forgotten to do it! There is nothing "automatic" about it, it's a decision based on the code being written where/how "super. ..." is called.

Upvotes: 0

odm
odm

Reputation: 906

@nayooti 's approach is excellent. However I have to do a few fixes in order to work properly. The main one being that some delegates methods won't get called (E.g. shouldChangeTextIn range and hence textViewDidChange as well).

I found out it can be fixed by returning super.delegate instead of superDelegate on the get accessor of the overridden delegate variable.

class CustomTextView: UITextView {
    
    override var delegate: UITextViewDelegate? {
        set {
            superDelegate = newValue
        } get {
            return super.delegate
        }
    }
    
    private weak var superDelegate: UITextViewDelegate?
    
    init() {
        super.init(frame: .zero, textContainer: nil)
        super.delegate = self
    }
    
    override public init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        super.delegate = self
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        super.delegate = self
    }
    
    func textDidChange(text: String?) {
        // Do something
    }
    
}

extension CustomTextView: UITextViewDelegate {
    
    public func textViewDidChange(_ textView: UITextView) {
        // Catch text-change events here
        textDidChange(text: textView.text)
        superDelegate?.textViewDidChange?(textView)
    }
    
    public func textViewDidEndEditing(_ textView: UITextView) {
        superDelegate?.textViewDidEndEditing?(textView)
    }
    
    public func textViewDidChangeSelection(_ textView: UITextView) {
        superDelegate?.textViewDidChange?(textView)
    }
    
    public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
        return superDelegate?.textViewShouldBeginEditing?(textView) ?? true
    }
    
    public func textViewDidBeginEditing(_ textView: UITextView) {
        superDelegate?.textViewDidBeginEditing?(textView)
    }
    
    public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
        return superDelegate?.textViewShouldEndEditing?(textView) ?? true
    }
    
    public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        return superDelegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) ?? true
    }
    
    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        return superDelegate?.textView?(textView, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? true
    }
    
    public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        return superDelegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) ?? true
    }
    
}

Upvotes: 0

nayooti
nayooti

Reputation: 443

Good question. Here is a not so good answer, since it requires you to rewrite all the delegate-methods and is therefore not stable across iOS-versions in case the delegate methods change over time.

In this approach the ViewController and the CustomTextField both have access to delegate-events.

class CustomTextView: UITextView {
   override var delegate: UITextViewDelegate? {
      set {
         superDelegate = newValue
      } get {
         return superDelegate
      }
    }

    private weak var superDelegate: UITextViewDelegate?

    init() {
       super.init(frame: .zero, textContainer: nil)
       super.delegate = self
    }

    func textDidChange(text: String?) {
        // do something
    }

}

extension BoundTextView: UITextViewDelegate {
    public func textViewDidChange(_ textView: UITextView) {
        // catch text-change events here
        textDidChange(text: String?) 
        superDelegate?.textViewDidChange?(textView)
    }

    public func textViewDidEndEditing(_ textView: UITextView) {
        superDelegate?.textViewDidEndEditing?(textView)
    }

    public func textViewDidChangeSelection(_ textView: UITextView) {
        superDelegate?.textViewDidChange?(textView)
    }

    public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
       return superDelegate?.textViewShouldBeginEditing?(textView) ?? false
    }

    public func textViewDidBeginEditing(_ textView: UITextView) {
        superDelegate?.textViewDidBeginEditing?(textView)
    }

    public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
       return superDelegate?.textViewShouldEndEditing?(textView) ?? false
    }

    public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        return superDelegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) ?? false
    }

    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        return superDelegate?.textView?(textView, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? false
    }

    public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        return superDelegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) ?? false
    }
}

We override the delegate and store a reference to it in a separate variable (called superDelegate). The CustomTextField assigns itself to super.delegate and implements the UITextView-delegate. We have to make sure that every delegate-event triggers the corresponding superDelegate's event.

Our 'ViewController' can now assign itself as the CustomTextView's delegate:

class ViewController: UIViewController {

   ...
   lazy var textField: CustomTextView {
      let textView = CustomTextField()
      textView.delegate = self 
      return textField
   }()
   ...


}

extension ViewController: UITextViewDelegate {

   // implement only the delegate-methods you need

}

Now both, the ViewController and the CustomTextField, have both access to the UITextFieldDelegate.

Upvotes: 1

Shamsiddin Saidov
Shamsiddin Saidov

Reputation: 2281

Two objects can't be delegate to the UITextView object at the same time. For this reason you should create new protocol (CustomTextViewDelegate) for your CustomTextView and create delegate property in it. Make your ViewController confirm this CustomTextViewDelegate and implement it's methods. Inside your CustomTextView's implementation of UITextViewDelegate methods you can call appropriate CustomTextViewDelegate methods.

Upvotes: 0

Related Questions