0

In a detail view controller, I've a 'featureImage' in the top left, and a thin horizontal strip of images below this. The strip of images is an embedded container view managed by a custom CollectionViewController, which shows an array of images. The initial featureImage is the first image in an array of images[0], and the same array is passed to the collection view.

I'd like the featureImage to update to the same image if a cell in the container view is selected / tapped.

I guess I need to call the delegate method didSelectItemAtIndexPath, which will give me the indexPath. Right? But then how do I pass the indexPath, which is already from a delegate, back to the detail view controller.

EDITED - The code shows code overlap and differences between Responder Chain AND delegate approaches. Uncommented in the didSelectItemAtIndex path, the Responder Chain approach works, while the delegate approach does not.

Protocol defined and included at top of DetailViewController (I doesn't seem to matter which file the protocol is in, and is only typed to class to allow the delegate property to be 'weak'). Needed for both approaches.

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

Delegate property declared in the custom UICollectionViewController class. Only needed for delegate approach.

 weak var delegate: FeatureImageController?

Delegate property initiated in the DetailViewController. Only needed for delegate approach.

    override func viewDidLoad() {
    super.viewDidLoad()

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

The Responder Chain (active) OR the delegate approach (commented out) within the collection view controllers didSelectItemAtIndexPath method.

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

Delegate method in DetailViewController. Needed for both.

func featureImageSelected(indexPath: NSIndexPath) {
    record?.featureImage = record?.images[indexPath.row]
    self.configureView()
}
DrWhat
  • 2,360
  • 5
  • 19
  • 33
  • With the commenting reversed, in the collectionView, both delegate?.featureImageSelected(indexPath) and delegate both print nil; and the delegate method in DetailViewController is never called. – DrWhat Dec 09 '15 at 08:46

1 Answers1

2

The communication of data selection between View Controllers in my experience can best be achieved in two ways- the delegation or responder chain route. Either way the first step would be creating a protocol that your DetailViewController will adhere to. Something like:

protocol FeatureImageController: class {
    func featureImageSelected(image: UIImage)
}

Your DetailViewController would then implement this function and use it to change the 'feature image'. How this is communicated then depends on whether you use delegation or the responder chain.

Delegation If you prefer to use delegation then declare a delegate property on your CollectionViewController like so:

weak var delegate: FeatureImageController?

then in didSelectItemAtIndexPath you would determine the selected image using the provided indexPath and pass it to your delegate:

delegate?.featureImageSelected(selectedImage)

where selectedImage is the image selected from the collection view.

Responder Chain If you decide to use the responder chain then you need not declare a delegate property. Instead you would ask for the first target that responds to your protocol method. So inside didSelectItemAtIndexPath you would say:

if let imageController = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
   imageController.featureImageSelected(selectedImage)
}

Both methods (delegation or responder chain) allow the collection view controller to pass its selection to the detail controller. The delegation route is more common in the Framework but I find as we use containers within containers more often it becomes pretty nasty to properly manage the chain of delegates without an amount of 'coupling' I'm not comfortable with. The responder chain, on the other hand, is already provided by the framework to 'dig' into the hierarchy of controllers to find one willing to handle your action.

Sean G
  • 479
  • 4
  • 9
  • Thanks! The Responder Chain approach works. That's the main thing I guess, but I still can't get the delegate approach to work. If I understood, the only difference really is to have either delegate?.feature.ImageSelected(indexPath) or the Responder Chain code (passing indexPath) in the didSelectItemAtIndexPath. If I comment out one or the other, only the Responder Chain code works. (also note, I got an error not to use weak for weak var delegate: FeatureImageController?) – DrWhat Dec 02 '15 at 09:28
  • Did you assign the detail view controller as the delegate on the collection view controller? I didn't mention that explicitly. That's the only issue I can imagine with your delegation scenario. – Sean G Dec 02 '15 at 15:09
  • I don't think so. I've the "var delegate: FeatureImageController" in the collection view controller, and the delegate method in the detailed view controller. I tried this in the view did load of the detailed view controller: let photoCollectionVC = PhotoCollectionVC(); photoCollectionVC.delegate = self as? FeatureImageController. But that didn't work. I put a print("bla") into the delegate method, but this only prints using the Responder Chain, never with the delegate. – DrWhat Dec 02 '15 at 17:14
  • Did you declare that detailed view controller adheres to the FeatureImageController protocol? I imagine you must have since the responder chain version works. When the collection view controller attempts to call the delegate method is the delegate property nil or set to your detailed view controller's instance? – Sean G Dec 02 '15 at 18:57
  • In the collection view didSelectItemAtIndexPath, print(delegate?.featureImageController featureImageSelected(indexPath)) returns nil. Also, the delegate method in the detail view controller never gets called. – DrWhat Dec 02 '15 at 21:29
  • In didSelectItemAtIndexPath what does "print(delegate)" display? – Sean G Dec 02 '15 at 23:57
  • nil - and here are 30 characters (minimum) – DrWhat Dec 03 '15 at 08:01
  • I've edited the original post with all the code I think required for both methods. The only difference between Responder Chain and delegate approaches is to switch the commenting in the didSelectItemAtIndexPath. – DrWhat Dec 03 '15 at 10:56
  • 1
    The delegate method definitely won't work as long as delegate is nil when you attempt to access it. At your line "photoCollectionVC.delegate = self as? FeatureImageController" do you get a warning that the cast will always succeed? If you've properly declared DetailViewController adheres to FeatureImageController the cast isn't needed and you should see a warning. If you don't there's something amiss and delegate is nil because DetailViewController does not adhere to FeatureImageController. – Sean G Dec 03 '15 at 19:31
  • Yes - sorry - the warning was there, I've removed the '?'. If I understand correctly, DetailViewController must adhere to FeatureImageController or the Responder Chain wouldn't work, oder? – DrWhat Dec 04 '15 at 08:13
  • You're correct the responder chain method would also fail if DetailViewController was not adhering to FeatureImageController. Your entire problem is that after assigning DetailViewController as the delegate it's being removed somehow. My only other guess would be it has something to do with the variable being 'weak'. But that would only matter if DetailViewController is being deallocated which would be difficult for me to imagine happening in your described scenario since it's being displayed. – Sean G Dec 04 '15 at 15:48
  • I don't seem to be able to make the variable strong (just by changing the word weak to strong). Thanks for answering the main part of this question. I'm going to set this up as a new question. – DrWhat Dec 07 '15 at 16:17
  • Strong is the default. So you would just remove the word weak. – Sean G Dec 07 '15 at 21:16
  • I can only remove the word weak by taking away the ': class' from the protocol declaration. When I do this, nothing changes in the outcome. Meanwhile, I've started a specific question on this issue of getting the delegate working (thanks for your help here): http://stackoverflow.com/questions/34138931/responder-chain-but-not-delegate-property-passes-value-back-to-view-controller-f/34142311#34142311 – DrWhat Dec 09 '15 at 08:40