2

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.

Sunil Sharma
  • 2,653
  • 1
  • 25
  • 36
  • You can make your custom text view delete, and set it from `UIViewController` and when your textview tells itself about `textViewDidChangeSelection` you would tell your `UIViewController` through you custom delegate. – Adil Soomro Mar 26 '16 at 21:05
  • There are lot other methods in textview delegate and I don't want to do this for all.Is there any way that I can make to listen both subclass and VC. – Sunil Sharma Mar 26 '16 at 21:10
  • You can set the delegate in custom text view and then in `ViewController` like this: `[texView addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];` More info [here](http://stackoverflow.com/a/7010676/593709) – Adil Soomro Mar 26 '16 at 21:19
  • addTarget method is not available for textview. – Sunil Sharma Mar 26 '16 at 21:25
  • My bad, I guess the only way is to implement custom delegate liket his user did. [UITextView delegate methods](http://stackoverflow.com/a/25318852/593709) – Adil Soomro Mar 26 '16 at 21:27
  • @Fattie The answer you posted is doing exactly what I suggested in my very first comment. But OP didn’t want to override everything in the delegate twice. That’s why I suggested to listen for editing changes for a textview. – Adil Soomro Apr 12 '23 at 16:55
  • hi @AdilSoomro ! You're right. the question and some of the comments are extremely confusing. The fact is, it's a completely everyday problem, and you just use an "outsideDelegate" (or some people write "consumerDelegate") in your text view subclass. The way you do that is in the answer I gave, note that odm's answer was on the right track but a bit muddled. That's why I put the everyday answer in. Cheers! – Fattie Apr 12 '23 at 17:29

4 Answers4

2

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.

nayooti
  • 395
  • 2
  • 15
1

@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
    }
    
}
odm
  • 888
  • 1
  • 14
  • 22
  • 1
    odm, it's true that it should be "super." where you mention. but the "extension" code is a bit confusing. I will put in an answer with the ordinary usual approach to this issue. – Fattie Apr 12 '23 at 15:59
0

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.

Shamsiddin Saidov
  • 2,281
  • 4
  • 23
  • 35
0

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.

Fattie
  • 27,874
  • 70
  • 431
  • 719