Search code examples
iosswiftuicollectionviewuipopover

When presenting a popover view, how can I let the user select cell in parent collection view?


I have a collection view and when a cell is selected it presents a popover view showing more information about that cell.

I would like to allow the user to click another cell and then have the popover view change to showing that cell's information without having to close the popover. If the user were to click somewhere on the parent view that isn't a cell then the popover should close. But, I would like the user to still be able to scroll the collection view without closing the popover.

How can that be done?


Solution

  • According to Apple :

    When a popover is active, interactions with other views are normally disabled until the popover is dismissed. Assigning an array of views to this property allows taps outside of the popover to be handled by the corresponding views.

    Then you can use the passthroughViews in the following way :

    CollectionViewController

    import UIKit
    
    let reuseIdentifier = "Cell"
    
    class CollectionViewController: UICollectionViewController {
    
       var popoverViewController : PopoverViewController?
    
       override func viewDidLoad() {
           super.viewDidLoad()         
    
       }
    
       override func didReceiveMemoryWarning() {
          super.didReceiveMemoryWarning()
          // Dispose of any resources that can be recreated.
       }   
    
       override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
          //#warning Incomplete method implementation -- Return the number of sections
          return 1
       }    
    
       override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
          //#warning Incomplete method implementation -- Return the number of items in the section
          return 15
       }
    
       override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    
          let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
          cell.labelInfo.text = "Cell \(indexPath.row)"        
          return cell
       }
    
       override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    
          println("tapped")
    
          if let popover = self.popoverViewController {
    
              var cell = self.collectionView!.cellForItemAtIndexPath(indexPath) as! CollectionViewCell
              popover.labelPop.text = cell.labelInfo.text
    
          }
          else {
    
              self.popoverViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PopoverViewController") as? PopoverViewController
    
              var cell = self.collectionView!.cellForItemAtIndexPath(indexPath) as! CollectionViewCell
    
              var t = self.popoverViewController!.view
              self.popoverViewController!.labelPop.text = cell.labelInfo.text
    
              self.popoverViewController!.modalPresentationStyle = .Popover
              var popover = self.popoverViewController!.popoverPresentationController
    
    
              popover?.passthroughViews = [self.view]
              popover?.sourceRect = CGRect(x: 250, y: 500, width: 0, height: 0)
              self.popoverViewController!.preferredContentSize = CGSizeMake(250, 419)
    
              popover!.sourceView = self.view
    
              self.presentViewController(self.popoverViewController!, animated: true, completion: nil)
          }
       }    
    }
    

    The above code is the CollectionViewController to handle the UICollectionViewController and all its delegates.

    CollectionViewCell

    class CollectionViewCell: UICollectionViewCell {    
        @IBOutlet weak var labelInfo: UILabel!        
    }
    

    The custom cell with just a UILabel inside.

    PopoverViewController

    class PopoverViewController: UIViewController {
    
       @IBOutlet var labelPop: UILabel!
    
       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.
       }   
    }
    

    And finally the PopoverViewController to show in form of .Popover.

    There are some observations in answer I would like to point out :

    • I set a reference to the class PopoverViewController to keep it through the life cycle and pass it data when it remains open yet.

    • The line var t = self.popoverViewController!.view it's necessary because if not the @IBOutlet inside the PopoverViewController was not init until it's presented, there could be other ways to do it.

    • I present the popover in the middle of the screen to handle the tap in several cell and test it the scroll too, you can display it in any position you want.

    • In the views to allow when the popover is opened , I set the self.view, but in this way you need to dismiss it for you own, because it never is dismissed when you make taps in the view, you can put any view you want instead.

    Any trouble you have with the solution I can share it the project on Github.

    I hope this help you