30

I need to perform a Popover segue when user touches a cell in a dynamic TableView. But when I try to do this with this code:

- (void)tableView:(UITableView *)tableview didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
        [self performSegueWithIdentifier:@"toThePopover" sender:[tableView cellForRowAtIndexPath]];
    //...
} 

than I get an error:

Illegal Configuration

Popover segue with no anchor

Is there any way to do this (to perform a popover segue from dynamic TableView manually)?

Hlib Dmytriiev
  • 303
  • 1
  • 3
  • 5

4 Answers4

61

I was faced with this same issue tonight, there a couple workarounds (including presenting the popover the old fashioned way).

For this example, I have an object that is stored in my custom cell class. When the cell is selected I call a function like this to open details in a popOverViewController about the object, and point (anchor) to it's corresponding cell in the table.

    - (void)openCustomPopOverForIndexPath:(NSIndexPath *)indexPath{
        CustomViewController* customView = [[self storyboard] instantiateViewControllerWithIdentifier:@"CustomViewController"];

        self.myPopOver = [[UIPopoverController alloc]
                                   initWithContentViewController:customView];
        self.myPopOver.delegate = self;
        //Get the cell from your table that presents the popover
        MyCell *myCell = (MyCell*)[self.tableView cellForRowAtIndexPath:indexPath];
        CGRect displayFrom = CGRectMake(myCell.frame.origin.x + myCell.frame.size.width, myCell.center.y + self.tableView.frame.origin.y - self.tableView.contentOffset.y, 1, 1);
        [self.myPopOver presentPopoverFromRect:displayFrom
                                             inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
    }

The problem with this method is that we often need the popover view to have a custom initializer. This is problematic if you want your view to be designed in storyboard instead of a xib and have a custom init method that takes your cells associated object as a parameter to use for it's display. You also can't just use a popover segue (at first glance) because you need a dynamic anchor point (and you can't anchor to a cell prototype). So here is what I did:

  1. First, create a hidden 1px X 1px UIButton in your view controllers view. (important to give the button constraints that will allow it to be moved anywhere in the view)
  2. Then make an outlet for the button (I called mine popOverAnchorButton) in your view controller and control drag a segue from the hidden button to the view controller you wish to segue to. Make it a popOver segue.

Now you have a popover segue with a 'legal' anchor. The button is hidden, so no one can touch it accidentally. You are only using this for an anchor point.

Now just call your segue manually in your function like this.

    - (void)openCustomPopOverForIndexPath:(NSIndexPath *)indexPath{
        //Get the cell from your table that presents the popover
        MyCell *myCell = (MyCell*)[self.tableView cellForRowAtIndexPath:indexPath];

        //Make the rect you want the popover to point at.
        CGRect displayFrom = CGRectMake(myCell.frame.origin.x + myCell.frame.size.width, myCell.center.y + self.tableView.frame.origin.y - self.tableView.contentOffset.y, 1, 1);

        //Now move your anchor button to this location (again, make sure you made your constraints allow this)
        self.popOverAnchorButton.frame = displayFrom;
        [self performSegueWithIdentifier:@"CustomPopoverSegue" sender:myCell];
    }

And...... Voila. Now you are using the magic of segues with all their greatness and you have a dynamic anchor point that appears to point to your cell. now in -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender you can simply cast the sender to your cell's class (given that you do the proper checks on sender type and which segue is being called) and give the segue's destinationViewController the cell's object.

Let me know if this helps, or anyone has any feedback or improvements.

johnrechd
  • 1,801
  • 19
  • 25
  • 7
    Also, if this helps you. If you could up vote it. I am a new user and need to earn nerd credit to do anything. – johnrechd Jan 25 '13 at 03:34
  • Do you know if Xcode 5/iOS 7 improved anything in this area? Anyway great workaround. – neural5torm Sep 16 '13 at 16:33
  • I'm not sure yet, though I don't think so. I am updating an app that uses this next week for iOS7 and will update if necessary. – johnrechd Sep 22 '13 at 01:20
  • I had the same problem in Xcode 5/iOS 7... It's a shame this is actually necessary, but this helped me out. Thanks. – Jasper Kuperus Dec 14 '13 at 19:39
  • I agree that it's a shame, thanks for the update on Xcode 5/iOS 7 – johnrechd Dec 16 '13 at 23:30
  • 1
    Using hidden button is great idea. I'm using it to show popover pointing to certain `div` inside `webView`. Instead of using `frames`, I go with constraints and auto-layout. It even easier to implement. – Mike Keskinov Apr 30 '14 at 21:36
  • What is the code we need to write in -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender? Not clear from the above explanation.. plz help.. my popover is just pointing far from mentioned frame.. what may be the issue!! – Vjlakshmi Jun 11 '14 at 15:39
  • @Vjlakshmi, here is a stack overflow post that might give you a good explanation on passing data with segues. http://stackoverflow.com/questions/20017026/passing-data-between-view-controllers-using-segue – johnrechd Jun 13 '14 at 18:10
  • This didn't work for me and my issue was that changing the frame wasn't moving the button. I changed the code to change the center of the button and that worked. `CGPoint tapPoint = CGPointMake(cell.frame.origin.x + cell.frame.size.width, cell.center.y + self.tableView.frame.origin.y - self.tableView.contentOffset.y); self.hiddenAnchorButton.center = tapPoint; ` – Kurt Anderson Mar 23 '15 at 17:31
  • @KurtAnderson, sorry that moving the frame didn't work for you. I'd guess that possibly you were using fixed constraints and possibly you couldn't break them. I haven't tried that to see if it would be an issue, but I imagine it is. I only have > < constraints on my hidden button. The proper way to do this would probably be to get an outlet on the constraints and adjust those in code instead of the frame anyway. If I get a chance, I'll test and update. Thanks. – johnrechd Mar 23 '15 at 18:32
  • @johnrechd, nope no constraints on the button. Adjusting the center did work and I'm getting exactly what I need. This is for an iOS 8 only app. – Kurt Anderson Mar 24 '15 at 00:56
  • @KurtAnderson, interesting. Although in autolayout, by not putting constraints on the button, you are letting the system put constraints on at run-time, which is still probably the limiting factor. I'll test that scenario. – johnrechd Mar 24 '15 at 06:14
  • 1
    Using this in a split view and found:`CGPoint popoverCentre = CGPointMake(cell.center.x, cell.center.y + self.tableView.contentOffset.y); [self.popoverAnchorButton setCenter:popoverCentre];` to work nicely. – matt_s Oct 17 '15 at 17:29
3

Just adding this answer as an alternative way to present a popover from a touched cell, though it uses code rather than a segue. It's pretty simple though and has worked for me from iOS 4 through iOS 7:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:NO];

    //get the data of the row they clicked from the array
    Url* clickedRec = [self.resultsArray objectAtIndex:indexPath.row];

    //hide the popover in case it was already opened from a previous touch.    
    if (self.addNewPopover.popoverVisible) {
            [self.addNewPopover dismissPopoverAnimated:YES];
            return;
        }

    //instantiate a view controller from the storyboard
    AddUrlViewController *viewControllerForPopover =
    [self.storyboard instantiateViewControllerWithIdentifier:@"addUrlPopup"];

    //set myself as the delegate so I can respond to the cancel and save touches.
    viewControllerForPopover.delegate=self;
    //Tell the view controller that this is a record edit, not an add        
    viewControllerForPopover.addOrEdit = @"Edit";
    //Pass the record data to the view controller so it can fill in the controls            
    viewControllerForPopover.existingUrlRecord = clickedRec;

    UIPopoverController *popController = [[UIPopoverController alloc]
                                          initWithContentViewController:viewControllerForPopover];

    //keep a reference to the popover since I'm its delegate        
    self.addNewPopover = popController;

    //Get the cell that was clicked in the table. The popover's arrow will point to this cell since it was the one that was touched.
    UITableViewCell *clickedCell = [self.tableView cellForRowAtIndexPath:indexPath];

    //present the popover from this cell's frame.
    [self.addNewPopover presentPopoverFromRect:clickedCell.frame inView:self.myTableView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
James Toomey
  • 5,635
  • 3
  • 37
  • 41
2

Swift answer using popoverPresentationController: Using storyboard, set up the new view controller with a Storyboard ID of popoverEdit.

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    let fromRect:CGRect = self.tableView.rectForRowAtIndexPath(indexPath)

    let popoverVC = storyboard?.instantiateViewControllerWithIdentifier("popoverEdit") as! UIViewController
    popoverVC.modalPresentationStyle = .Popover
    presentViewController(popoverVC, animated: true, completion: nil)
    let popoverController = popoverVC.popoverPresentationController
    popoverController!.sourceView = self.view
    popoverController!.sourceRect = fromRect
    popoverController!.permittedArrowDirections = .Any

}
Drewster
  • 499
  • 5
  • 13
  • This works for me inside a prepareForSegue for a popover segue too. I don't have to convert the rect or do any other old workarounds with a 1x1 pt view. I also set the sourceView in the storyboard to the tableView. This helps with adaptation on the split view as my popover was sliding mostly off the screen when the primary view was hidden in multitasking adjustments. – James Oct 22 '15 at 21:52
0

I have made this in the simplest way:

  1. Make this Popover Presentation Segue in Storyboard as usually but drag from ViewController (not button)
  2. select anchor view as table view
  3. then in table view cell's button touch make:

     private func presentCleaningDateDatePicker(from button: UIButton) {
        performSegue(withIdentifier: "Date Picker Popover Segue", sender: button)
    }
    
  4. and implement prepare(for segue) method

      override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
        if let identifier = segue.identifier {
            switch identifier {
            case "Date Picker Popover Segue":
    
                if let vc = segue.destination as? DatePickerViewController {
                    if let ppc = vc.popoverPresentationController {
                        ppc.sourceView = sender as! UIButton
                        ppc.sourceRect = (sender as! UIButton).frame
                        ppc.delegate = vc
    
                        vc.minimumDate = Date()
                        vc.maximumDate = Date().addMonth(n: 3)
                        vc.delegate = self
                    }
                }
            default:
                break
            }
        }
    }
    
Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143