Reputation: 2689
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
Reputation: 12206
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
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
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
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