36

I always try to present a popover from a cell inside a tableView this way:

[myPopover presentPopoverFromRect:cell.frame inView:self.tableView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

but I cannot use UIPopoverArrowDirectionRight or Left, because, depending on the position of the ipad (portrait or landscape), the popover appears someplace else.

Am I presenting the popover the right way?

PS: the table view is in the detailView of a splitView.

Shmidt
  • 16,436
  • 18
  • 88
  • 136
Omer
  • 5,470
  • 8
  • 39
  • 64

11 Answers11

50

You're getting the frame from the cell via the rectForRowAtIndexPath method. This is correct. However the tableview is most likely a subview of a larger iPad view so when the popover gets the coordinates it thinks they're in the larger view. This is why the popover appears in the wrong place.

Example, the CGRect for the row is (0,40,320,44). Instead of the popover targeting that frame on the tableview it instead targets that frame on your main view.

I solved this problem by converting the frame from the relative coordinates of the table to coordinates in my larger view.

code:

CGRect aFrame = [self.myDetailViewController.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:theRow inSection:1]];
[popoverController presentPopoverFromRect:[self.myDetailViewController.tableView convertRect:aFrame toView:self.view] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionRight animated:YES];

Hope that helps others searching for this issue.

John
  • 566
  • 4
  • 3
25

In Swift, between the above answers this works for me on an iPad in any orientation:

if let popOverPresentationController : UIPopoverPresentationController = myAlertController.popoverPresentationController {

    let cellRect = tableView.rectForRowAtIndexPath(indexPath)

    popOverPresentationController.sourceView                = tableView
    popOverPresentationController.sourceRect                = cellRect
    popOverPresentationController.permittedArrowDirections  = UIPopoverArrowDirection.Any

}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Fred Faust
  • 6,696
  • 4
  • 32
  • 55
  • This is nice if you want to setup the segue in a Storyboard. You can just set the sourceRect property from the prepareForSegue: method. – Joony Aug 10 '15 at 09:49
22

I came across this problem today, and I've found a simpler solution.
When instantiating the popover, you need specify the cell's content view:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIViewController *aViewController = [[UIViewController alloc] init];
    // initialize view here

    UIPopoverController *popoverController = [[UIPopoverController alloc] 
        initWithContentViewController:aViewController];
    popoverController.popoverContentSize = CGSizeMake(320, 416);
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    [popoverController presentPopoverFromRect:cell.bounds inView:cell.contentView 
        permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

    [aView release];
    // release popover in 'popoverControllerDidDismissPopover:' method
}
antalkerekes
  • 2,128
  • 1
  • 20
  • 36
  • 1
    Just a question, Can you instantiate an UIPopoverController using that method with only a UIVew? I'll; give it a try =D – Omer May 11 '11 at 13:21
  • @Omer Not sure what you mean by this. Can you clarify? – antalkerekes May 11 '11 at 13:45
  • This is the right answer. The documentation for presentPopoverFromRect says the rect is the rectangle in view at which to anchor the popover window and the view is the view containing the anchor rectangle for the popover, so it's actually simpler than it seems! – Clafou Jul 17 '12 at 23:15
  • 1
    Initiate a UIPopovercontroller with UIView will give you a warning and a NSInvalidArgumentException when you try to execute. You should put it in a UIViewController, and then initiate the popovercontroller. – lagos Sep 25 '12 at 10:14
4

To pop a popover next to the accesory u can use this code :)

I use this for more advanced use:

  1. finds custom accesoryView (cell.accesoryView)
  2. if empty, find generated accesoryView (UIButton) if cell has
  3. if the UIButton doesn't exists, find cell contet view (UITableViewCellContentView)
  4. if the cell contet view doesn't exists, use cell view

Can be use for UIActionSheet or UIPopoverController.

Here is my code:

UIView *accessoryView       = cell.accessoryView; // finds custom accesoryView (cell.accesoryView)
if (accessoryView == nil) {
    UIView *cellContentView = nil;

    for (UIView *accView in [cell subviews]) {
        if ([accView isKindOfClass:[UIButton class]]) {
            accessoryView   = accView; // find generated accesoryView (UIButton) 
            break;
        } else if ([accView isKindOfClass:NSClassFromString(@"UITableViewCellContentView")]) {
            // find generated UITableViewCellContentView                
            cellContentView = accView; 
        }
    }
    // if the UIButton doesn't exists, find cell contet view (UITableViewCellContentView)           
    if (accessoryView == nil) { 
        accessoryView   = cellContentView; 
    }
    // if the cell contet view doesn't exists, use cell view
    if (accessoryView == nil) {
        accessoryView   = cell; 
    }
}

[actionSheet showFromRect:accessoryView.bounds inView:accessoryView animated:YES];

Tested in iOS 4.3 to 5.1

Best to use as custom method:

-(UIView*)getViewForSheetAndPopUp:(UITableViewCell*)cell;

And method code:

-(UIView*)getViewForSheetAndPopUp:(UITableViewCell*)cell {
UIView *accessoryView = cell.accessoryView;

if (accessoryView == nil) {
    UIView *cellContentView = nil;

    for (UIView *accView in [cell subviews]) {
        if ([accView isKindOfClass:[UIButton class]]) {
            accessoryView = accView;
            break;
        } else if ([accView isKindOfClass:NSClassFromString(@"UITableViewCellContentView")]) {              
            cellContentView = accView;
        }
    }       

    if (accessoryView == nil) {
        accessoryView   = cellContentView;
    }
    if (accessoryView == nil) {
        accessoryView   = cell;
    }
}

return accessoryView;
}
Pion
  • 708
  • 9
  • 21
  • 1
    Tested working in iOS6SDK. I didn't try the custom method, just in the ViewController's-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath – James Perih Aug 06 '13 at 22:37
3

I did come across this problem as well. A solution for me was to simply change the width of the rect returned by CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath :

CGRect rect = [aTableView rectForRowAtIndexPath:indexPath];

//create a 10 pixel width rect at the center of the cell

rect.origin.x = (rect.size.width - 10.0) / 2.0; 
rect.size.width = 10.0;  

[self.addExpensePopoverController presentPopoverFromRect:rect inView:aTableView permittedArrowDirections:UIPopoverArrowDirectionAny  animated:YES]; 

This create a rect centred inside the cell. This way, the popover has more chances of findind a good spot to position itself.

Clive
  • 36,918
  • 8
  • 87
  • 113
okcompute
  • 31
  • 4
  • I used this method to attach an ActionSheet to the detailText of a tableviewcell on an iPad. `CGRect rectToUse = targetCell.bounds; rectToUse.origin.x = rectToUse.size.width - 200; rectToUse.size.width -= rectToUse.origin.x;` – casey Oct 02 '11 at 15:43
2

I had the same kind of problem, here's the workaround i used :

  • in my UITableViewCell, i added Actions (IBActions as i generate my cells from a NIB) for cell's specific buttons.
  • i then defined a CellActionDelegate protocol that mimics my actions selectors, to which i had my button (sender) and my cell (self)
  • then the detailViewController of my splitViewController implements this protocol, converting from the cell's to its coordinates...

here a example of code

In MyCustomTableViewCell.m :

   -(IBAction)displaySomeCellRelativePopover:(id)sender{
        //passes the actions to its delegate
        UIButton *button = (UIButton *)sender;
        [cellActionDelegate displaySomeCellRelativePopoverWithInformation:self.info
                                                               fromButton:button 
                                                                 fromCell:self];   
   }

and the, in MyDetailViewController.m :

-(void)displaySomeCellRelativePopoverWithInformation:(MyCellInformationClass *)info
                                          fromButton:(UIButton *)button 
                                            fromCell:(UIView *)cell{

UIPopoverController * popoverController = nil;

//create your own UIPopoverController the way you want

//Convert your button/view frame

CGRect buttonFrameInDetailView = [self.view convertRect:button.frame fromView:cell];

//present the popoverController
[popoverController presentPopoverFromRect:buttonFrameInDetailView
                               inView:self.view permittedArrowDirections:UIPopoverArrowDirectionRight animated:YES];]


//release objects created...
}

PS : Of course, the 'action' doesn't have to be a IBAction, and the frame from where the popover originates doesn't have to be a UIButton - a single UIView would be good :)

Vinzzz
  • 11,746
  • 5
  • 36
  • 42
1

The cell frame is going to be something like 0,0,width,size, i dont believe it will have its X and Y relative to the tableView...you want to use - (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath for this, this should return you the correct frame for the cell relative to the tableView...here is a link UITAbleView ref

Daniel
  • 22,363
  • 9
  • 64
  • 71
  • Hey! thanks for the answer.. but with the method you recommend I get the same CGrect, and I still have the problem with the popover's position. When the ipad is in portrait mode, the popover appears in the top left corner of the screen when I specify those arrow directions... it freaks me out!!! .. by the other hand,is it correct to present it from self.TableView? Thank you anyway – Omer Jun 11 '10 at 13:45
1

The accepted answer cannot compile now.

CGRect rect =[tableView rectForRowAtIndexPath:indexPath]; //This is how to get the correct position on the tableView    
UIPopoverPresentationController *popController = [controller popoverPresentationController];
popController.sourceRect = rect;
popController.permittedArrowDirections = 0;  //Here there is no arrow. It is my app's need
popController.delegate = self;
popController.sourceView = self.tableView;
popController.sourceView.backgroundColor = [UIColor yellowColor];
flame3
  • 2,812
  • 1
  • 24
  • 32
1

Here is the simple solution which works fine for me

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    CGRect rect=CGRectMake(cell.bounds.origin.x+600, cell.bounds.origin.y+10, 50, 30);
    [popOverController presentPopoverFromRect:rect inView:cell permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

}
HpTerm
  • 8,151
  • 12
  • 51
  • 67
vrk
  • 125
  • 1
  • 9
  • This would indicate that the origin of the presentation rect is 600 to the right of the cell's bounds origin (and 10 down from the top of the cell's origin). You could adjust these to place the presentation from anywhere within your cell. – jt_uk Jun 11 '13 at 19:27
  • 43
    Most people shouldn't do this. Never use hardcoded numbers like this, and this wouldn't work when the orientation changed. – Enrico Susatyo Sep 14 '13 at 07:11
  • We should not hard code the x value of rect, as we have devices with different resolutions – Saif Nov 18 '15 at 06:45
0

This is how i did and works perfectly fine.

RidersVC *vc = [RidersVC ridersVC];
vc.modalPresentationStyle = UIModalPresentationPopover;
vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
UIPopoverPresentationController *popPresenter = [vc popoverPresentationController];
popPresenter.sourceView = vc.view;
popPresenter.barButtonItem= [[UIBarButtonItem alloc] initWithCustomView:button];
popPresenter.backgroundColor = [UIColor colorWithRed:220.0f/255.0f green:227.0f/255.0f blue:237.0f/255.0f alpha:1.0];
[self.parentVC presentViewController:vc animated:YES completion:NULL];
i.jameelkhan
  • 1,215
  • 1
  • 9
  • 10
0

This also worked for me:


        //Which are the ABSOLUTE coordinates in from the current selected cell
        CGRect frame =[self.view convertRect:[tbvEventsMatch rectForRowAtIndexPath:indexPath] fromView:tbvEventsMatch.viewForBaselineLayout];