Non-self delegate is not called - swift

Consider the following code. Everything is done by the book, except that the delegate is assigned an instance of some other class other than self. Yet this code fails - the delegate is never called. Why is that?
class My: UITextField {
...
init(...) {
delegate = MyDelegate()
}
}
public class MyDelegate: NSObject, UITextFieldDelegate {
public func textFieldDidBeginEditing(_ textField: UITextField) {
print("MyDelegate was called")
}
}

If your delegate property is defined using a weak var, the MyDelegate instance won't be retained. Without a strong reference it will shortly be deallocated and no longer exist to receive calls.
Your example code strays from the typical delegate pattern:
Delegation and the Cocoa Frameworks
The delegating object is typically a framework object, and the
delegate is typically a custom controller object.
The text field's view controller would commonly assign itself as the text field's delegate:
class MyViewController: UITextFieldDelegate {
...
func viewDidLoad() {
myTextField.delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
print("textFieldDidBeginEditing")
}
}

didLoad is not a function of UITextField so your delegate is not really set and that is why it is not called. If you are creating your My class programmatically, you can set the delegate after the instance is created.
let myTextField = My()
mytextField.delegate = MyDelegate()
If you want to set the delegate everytime you class is created, you need to override the init functions of UITextField and create the delegate inside your init functions.
class MyTF : UITextField
{
override init(frame: CGRect)
{
super.init(frame: frame)
delegate = MyDelegate()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
delegate = MyDelegate()
}
}

You can achieve this by creating a strong reference to your delegate class first with "let", and then setting the delegate. Otherwise, because the delegate reference is weak it will have a zero reference count and nil out, leaving you with no delegate.
let theDelegate = MyDelegate()
delegate = theDelegate

Related

UIView IBOutlet not changing when change called from a different class

I have an XIB with a UILabel to it. I have referenced the UILabel to a UIView class that I have created. I can change the label using label.text = "hi" when initializing the view. When I try and call a change from another class it doesn't change the UILabel on screen (but if I print label.text it shows as what I set it to). I cannot make the UILabel load the text when initializing as the text could be changed by the user at any time. (switchText() is called from a UITableCell)
2nd class
class Second {
func switchText() {
let first = First()
DispatchQueue.main.async {
first.label.text = "bye"
}
}
}
1st class
class First: UIView {
let kCONTENT_XIB_NAME = "First"
#IBOutlet var label: UILabel!
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed(kCONTENT_XIB_NAME, owner: self, options: nil)
contentView.fixInView(self)
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
Also, in my XIB I have my UIView hooked up to File's Owner and contentView inside my UIView class. My label outlet goes to file's owner and then to the UIView class where it is declared as label.
You are not really changing the text on your First() class. What your switchText() function does is create another reference of the class named First and then set the text of the label for that new reference.
let first = First()
DispatchQueue.main.async {
first.label.text = "bye"
}
What you can do is make your switchText() function conform to a protocol then call it on your First() class through a delegate.
protocol SecondClassDelegate {
func didSwitchText(editedText: String)
}
class Second {
var delegate: SecondClassDelegate!
func switchText() {
delegate.didSwitchText("bye")
}
}
Now you can add this to your First() class
class First: SecondClassDelegate {
func didSwitchText(editedText: String) {
label.text = editedText
}
}
Just don't forget to set the delegate wherever you're setting your Second() class
let second = Second()
second.delegate = self
I suggest reading about this for a better understanding of delegates. https://www.appcoda.com/swift-delegate/

How to make delegate between a cell with a UIView and a view controller?

I have a view controller which holds a tableview. Inside that tableview I have a dynamic cell. Inside of that cell I have a UIView. Inside the UIView I have a label with a tap recognizer which is supposed to perform a segue to a different view controller. I have tried making a delegate between the view controller by putting vc.delegate = self in the viewdidload, but it did not work. I have also tried putting the same line inside cellforrowat, but it still doesn't do anything. How can I make a delegate that communicates from my cell with UIView to this view controller? Also, the thing that will be communicating is my UIView class and not my cell class.
If I understand it correctly. Create a delegate protocol for your cell view, put delegate property to the cell and pass it via the tableView method tableView(_:cellForRowAt:) to the controller.
Protocol implementation:
protocol MyCellViewDelegate: class {
func viewDidTap(_ view: MyCellView)
}
Cell View Implementaiton:
class MyCellView: UIView {
private let tapGesture = UITapGestureRecognizer()
weak var delegate: MyCellViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
tapGesture.addTarget(self, action: #selector (viewDidTap))
}
#objc
func viewDidTap() {
delegate?.viewDidTap(self)
}
}
Cell Implementation:
Then, in your Cell implementation. Pass the delegate reference to the MyCellView, which will be handled in the Controller.
class MyCell: UITableViewCell {
private let myContentView = MyCellView()
weak var delegate: MyCellViewDelegate? {
didSet { myContentView.delegate = delegate }
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
}
Then you should be able to set delegate in the TableView DataSource delegate methods.
Controller Implementation:
extension MyController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue the cell...
cell.delegate = self
return cell
}
}
And it will force you to add delegates.
extension MyController: MyCellViewDelegate {
func viewDidTap(_ view: MyCellView) {
// Do some stuff
}
}
However, TableView could "steal" your Tap Gesture action. Maybe, if you set tableView.isUserInteractionEnabled = false, it could work.
The easy way is NotificationCenter. Just post the notification and receive at ViewController.
Though, you achieve this as well by using the delegate where you need to pass the information as below. This is very high level where I am assuming you have below view as a separate class.
UIView -> [Cell -> TableView] -> UIViewController
You can use the completion handler as well.

How to set delegate in a protocol extension for a UIkit class

I'm trying to have all my TextView instances use this same delegate function from UITextPasteDelegate without copying the delegate function into each VC.
Xcode 10.3 compiler doesn't throw an error when I do this:
extension UITextView : UITextPasteDelegate {
private func textPasteConfigurationSupporting(_ textPasteConfigurationSupporting: UITextPasteConfigurationSupporting, shouldAnimatePasteOf attributedString: NSAttributedString, to textRange: UITextRange) -> Bool {
return false
}
}
However the problem I run into is when I try to set this delegate in my VC. The code looks something like this:
class viewController : UIViewController{
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.pasteDelegate = self
}
}
It breaks on the line textView.pasteDelegate = self and I understand that it breaks here because the viewController class is not implementing UITextPasteDelegate. But my question is whether it is possible to assign textView.pasteDelegate = ? to something. I've tried textView.pasteDelegate = UITextView.Type but that also fails.
Is there a way to achieve this?
Extensions cannot override methods. This is not supported. Sometimes it may happen to work, but it is undefined behavior.
From Extensions in The Swift Programming Language:
Extensions can add new functionality to a type, but they cannot override existing functionality.
(This is all true, but doesn't actually apply to this question; see more below.)
This is exactly what subclasses are for. In Interface Builder, select your subclass and it'll still wire up as a UITextView. It'll just be configured the way you want. For example:
class NonAnimatingTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
self.pasteDelegate = self
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
extension NonAnimatingTextView: UITextPasteDelegate {
func textPasteConfigurationSupporting(
_ textPasteConfigurationSupporting: UITextPasteConfigurationSupporting,
shouldAnimatePasteOf attributedString: NSAttributedString,
to textRange: UITextRange) -> Bool { false }
}
For completeness, the exact thing you're trying to do is in fact possible; it's just not the right approach. (In this case it works out that it's not an override, so it's possible.)
First, you marked the method as private when it should be public and ideally #objc (#objc may not be required here, but I would add it to be very clear):
extension UITextView : UITextPasteDelegate {
#objc public func textPasteConfigurationSupporting(_ textPasteConfigurationSupporting: UITextPasteConfigurationSupporting, shouldAnimatePasteOf attributedString: NSAttributedString, to textRange: UITextRange) -> Bool {
return false
}
}
And then the UITextView is its own pasteDelegate:
class ViewController : UIViewController{
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.pasteDelegate = textView
}
}
But I still recommend the subclass. It's much clearer what you're doing.

delegate is always nil

I have a custom UIView class called MyView and a View Controller.
When the user taps a button on the UIView, I want to call a function on the view controller. I'm trying to achieve this through delegation
custom UIClass
#objc protocol MyViewDelegate{
optional func expandCollapse()
}
class MyView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
weak var delegate:MyViewDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
loadNib()
}
}
override init(frame:CGRect){
super.init(frame: frame)
loadNib()
}
func loadNib(){
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! MyView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
#IBAction func expandit(sender: AnyObject) {
//this is where it fails. delegate is nil
delegate!.expandCollapse!()
}
}
My View Controller
class ViewController2: UIViewController, MyViewDelegate {
#IBOutlet weak var theview: UIView!
var myview : MyView?
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
myview = MyView(frame: CGRectMake(0,0,theview.frame.size.width,theview.frame.size.height))
self.theview.addSubview(myview!)
myview!.delegate = self
}
func expandCollapse() {
viewheight.constant = 172
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
In the UIView, the delegate is always nil. What am I missing?
Using delegation for this is simply unsuitable. You are fighting UIKit design patterns.
The whole situation is very simple.
You have your ViewController.
Then you have your totally independent custom view.
Essentially, you want somehow to route the TouchUpInside event from the button to get to viewController.
If your Custom view contains a button, then the accessibility level of this button is internal by default. Looking at the code, I assume you created the button in Interface builder. Make an outlet from the custom view class to the button, so that there is a programatically accessible reference to it.
Your view controller declares an instance of this custom view. Then, in viewDidLoad you have to use the target-action pattern.
self.customView.button.addTarget(target: self, action: "expandCollapse", forControlEvents: .TouchUpInside)
That's basically all there is to it.
I'm not entirely confident of my ARC understanding, but I believe the issue is that your delegate is a weak reference and there's nothing keeping a reference to the delegate after it's set, so it' deallocated.
Replace it with this and I believe it will work:
var delegate:MyViewDelegate?
Try assigning the delegate to "myview" before adding it to "theview"
The problem is in the loadNib() member function.
You're creating two instances of "MyView". The second instance being added as a subview.
You're setting the delegate in one instance and referring to a nil delegate in the other instance.
Try using a static class method like below to create one instance of "MyView"
class func loadFromNib() -> MyView? {
guard let myView = Bundle.main.loadNibNamed("MyView", owner: nil, options: nil)?.first as? MyView else {
assertionFailure("Failed to load nib for 'MyView'!")
return nil
}
return myView
}
Doing it this way, you won't need the custom init()s either.
Hope that helps!

Pass data by protocol while using Container to view another ViewController in Swift

I started working on this question app.
I began by tableView of the categories:
For data exchange, I decided to use a protocol:
protocol Category {
func data(object:AnyObject)
}
In the first ViewController has the following code:
class ViewController: UIViewController {
var items:[String] = ["Desktop","Tablet","Phone"]
let CategoriesData:Category? = nil
override func viewDidLoad() {
super.viewDidLoad()
CategoriesData?.data(items)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In the second ViewController (tableView in Container) have the following code:
class CategoriesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, Category {
#IBOutlet var table: UITableView!
var items:[String] = []
func data(object: AnyObject) {
self.items = (object as? [String])!
print(object)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.table.dequeueReusableCellWithIdentifier("SegueStage") as! TableViewCell
cell.nameLabel.text = items[indexPath.row]
return cell
}
}
For me, apparently it's all right. But nothing appeared on the simulator.
My question is: If the Container use to present another viewController as passing data by protocols should be done?
EDITED
I answered why the TO:s solution didn't work as intended, but I just realised that I haven't given a viable answer to how to use protocols as delegates for the ViewController -> ViewController communication. I'll leave the half-answer below until someone can possibly answer the full question better.
In the way protocol is used in your code, you define your protocol Category to be a delegate for instances of the type ViewController. When an instance of type ViewController is initialised in---and hence owned locally in the scope of---some other class, the instance can delegate callbacks to the owning class.
The problem is that your CategoriesViewController does not contain any instances of type ViewController. We note that both these classes are, in themselves, subclasses of UIViewController, but none of them contain instances of one another. Hence, your CategoriesViewController does indeed conform to protocol Category, by implemented the protocol method data(...), but there's no ViewController instance in CategoriesViewController that can do callbacks to this function. Hence, your code compile file, but as it is, method data(...) in CategoriesViewController will never be called.
I might be mistaken, but as far as I know, protocol delegates are used to do callbacks between models (for model in MVC design) and controllers (see example below), whereas in your case, you want a delegate directly between two controllers.
As an example of model-delegate-controller design, consider some custom user control, with some key property value (e.g. position in rating control), implemented as a subclass of UIView:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
}
}
var delegate: CustomUserControlDelegate?
// ... some methods/actions associated with your user control.
}
Now lets assume an instance of your CustomUserControl is used in a a view controller, say ViewController. Your delegate functions for the custom control can be used in the view controller to observe key changes in the model for CustomUserControl, much like you'd use the inherent delegate functions of the UITextFieldDelegate for UITextField instances (e.g. textFieldDidEndEditing(...)).
For this simple example, use a delegate callback from the didSet of the class property value to tell a view controller that one of it's outlets have had associated model update:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
#IBOutlet weak var customUserControl: CustomUserControl!
// Instance of CustomUserControl in this UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl.delegate = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
}