0

The following code should show two ways to pass information from an embedded controller (UICollectionView) back to a detailed view controller using either the Responder Chain OR delegate approach. Both approaches use the same protocol, and delegate method. The only difference is if I comment out the delegate?.method line in didSelectItemAtIndex path, the Responder Chain works. BUT, if I comment out the Responder Chain line in the didSelectItemAtIndex method, the uncommentented delegate? property doesn't call the method, and remains nil.

Protocol defined and included above DetailViewController. Needed for both approaches.

protocol FeatureImageController: class {
func featureImageSelected(indexPath: NSIndexPath)
}

Delegate property declared in the custom UICollectionViewController class, which is only needed for delegate approach.

class PhotoCollectionVC: UICollectionViewController
{
   weak var delegate: FeatureImageController?

In DetailViewController, an instance of PhotoCollectionVC() is created, and the delegate property set to self with the delegate protocol as type.

class DetailViewController: UIViewController, FeatureImageController 
{...
    override func viewDidLoad() {
    super.viewDidLoad()

    let photoCollectionVC = PhotoCollectionVC()
    photoCollectionVC.delegate = self as FeatureImageController

Within the collection view controller's didSelectItemAtIndexPath method, pass back the selected indexPath via either the Responder Chain (commented out) OR the delegate to the featureImageSelected method in the DetailVC.

override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
//    if let imageSelector =  targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
//       imageSelector.featureImageSelected(indexPath)
//      }
        self.delegate?.featureImageSelected(indexPath)
}

An instance of elegate method in DetailViewController. Needed for both.

func featureImageSelected(indexPath: NSIndexPath) {
    record?.featureImage = record?.images[indexPath.row]
    self.configureView()
}

Why would the Responder Chain approach work, but the delegate not?

There are no compiler or run time errors. Within the didSelectItemAtIndexPath method, the delegate always returns nil and nothing prints from the delegate method.

DrWhat
  • 2,360
  • 5
  • 19
  • 33

2 Answers2

0

Your responder code calls a featureImageSelected on self:

self.featureImageSelected(indexPath)

but the delegate code calls featureImageSelected on the delegate:

self.delegate.featureImageSelected(indexPath)

Which would be the DetailVC's delegate, not the collectionViews delegate. Im not really sure what your code is doing, but you probably want something like

collectionView.delegate?.featureImageSelected(IndexPath)

which looks like it would just end up being

self.featureImageSelected(indexPath)
ColdLogic
  • 7,206
  • 1
  • 28
  • 46
  • self.featureImageSelected(indexPath) gives a compiler error: Value of type 'PhotoCollectionVC' has no member featureImageSelected. I can't see the difference between when the Responder Chain assigns imageController as FeatureImageController, and when the delegate assigns delegate? as FeatureController. (except of course that the delegate method is not called, and the property remains nil) – DrWhat Dec 09 '15 at 08:35
  • Ah ok, if self is the PhotoCollectionVC, you do want `self.delegate.featureImageSelected`. This isn't apparent with the code you have posted. If your delegate is nil, that is the problem. `The targetForAction` will go up the responder chain until it finds something that does implement `featureImageSelected`, thats why it works. – ColdLogic Dec 09 '15 at 21:32
  • Any idea why the delegate approach is not working? or how to make it call the delegate method? – DrWhat Dec 14 '15 at 09:01
  • Debug your code there in the `didSelectitemAtIndexPath` method and determine if the delegate is nil. From there you need to make sure you are setting the delegate correctly. I can't see all of your code, but some thoughts would be that 1) you set the delegate on a different `PhotoCollectionVC`, 2) you don't conform to the protocol, so it doesn't set the delegate correctly. – ColdLogic Dec 15 '15 at 15:58
  • I updated the question with what I think is all the relevant code. It is not a complex project, with only one PhotoCollectionVC. The delegate is always nil, and the method never gets called. It conforms to the protocol for the Responder Chain approach. – DrWhat Dec 16 '15 at 19:38
0

The error in the question is where, in the conforming class, "an instance of PhotoCollectionVC() is created, and the delegate property set to self". In viewDidLoad, that just creates another instance with an irrelevant delegate property that will never be called. The delegate property of the actual embedded PhotoCollectionVC needs to be assigned to self - in order for the two VCs to communicate. This is done from within the prepareForSegue method:

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
    ...
        let controller =  (segue.destinationViewController as! PhotoCollectionVC)
        ...
        controller.delegate = self
        }
    }
}

The rest of the example code is fine.

Here is a super simple example of delegation from an embedded container to its delegate VC. The embedded container simply tells the VC that a button has been pressed. The story board is just a VC with a container in it and a text outlet. In the container VC, there is just a button. And the segue has an identifier.

The code in the delegate ViewController is:

protocol ChangeLabelText: class
{
  func changeText()
}

class ViewController: UIViewController, ChangeLabelText
{
    @IBOutlet weak var myLabel: UILabel!

    override func viewDidLoad()
    {
        super.viewDidLoad()
        myLabel?.text = "Start"
    }

    func changeText()
    {
        myLabel?.text = "End"
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "feelTheBern"
        {
            let secondVC: myViewController = segue.destinationViewController as! myViewController
            secondVC.delegate = self
        }}
}

The code in the delegating View Controller, myViewController, is:

class myViewController: UIViewController
{
    weak var delegate: ChangeLabelText?

    @IBAction func myButton(sender: AnyObject)
    {
        print("action")
        delegate?.changeText()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
DrWhat
  • 2,360
  • 5
  • 19
  • 33