How to correctly present a popover from a UITableViewCell with UIPopoverArrowDirectionRight or UIPopoverArrowDirectionLeft

54,981

Solution 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];

}

Solution 2

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.

Solution 3

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

}

Solution 4

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
}

Solution 5

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;
}
Share:
54,981
Omer
Author by

Omer

Ancient History: Graphic + 3D Designer and post-producer. Recent History: App Developer, mobile focused, UI/UX oriented. Present: Anti-Hero @OrangeLoops, watching Kezmo grow from the ground up. What's out there? Here: Kezmo CloseNet Some open source: OLSDynamicHeaderViewController

Updated on September 15, 2020

Comments

  • Omer
    Omer almost 4 years

    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.

  • Omer
    Omer about 14 years
    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
    Omer almost 14 years
    Hey.. thanks, I'll give it a try.. this seems a smart workaround jeje
  • Omer
    Omer about 13 years
    Just a question, Can you instantiate an UIPopoverController using that method with only a UIVew? I'll; give it a try =D
  • antalkerekes
    antalkerekes about 13 years
    @Omer Not sure what you mean by this. Can you clarify?
  • Peter Johnson
    Peter Johnson almost 13 years
    This fixed my problem of it working in portrait, but not in landscape. Thanks
  • casey
    casey over 12 years
    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;
  • Clafou
    Clafou almost 12 years
    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!
  • lagos
    lagos almost 12 years
    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.
  • jt_uk
    jt_uk about 11 years
    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.
  • James Perih
    James Perih almost 11 years
    Tested working in iOS6SDK. I didn't try the custom method, just in the ViewController's-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
  • Enrico Susatyo
    Enrico Susatyo almost 11 years
    Most people shouldn't do this. Never use hardcoded numbers like this, and this wouldn't work when the orientation changed.
  • user1244109
    user1244109 almost 9 years
    This is definitely helpful. I was getting the frame from the cell itself, [tableView cellForRowAtIndexPath:indexPath], which is a bad thing to do. Wasn't even aware of -rectForRowAtIndexPath:. Thanks!
  • Joony
    Joony almost 9 years
    This is nice if you want to setup the segue in a Storyboard. You can just set the sourceRect property from the prepareForSegue: method.
  • Saif
    Saif over 8 years
    We should not hard code the x value of rect, as we have devices with different resolutions
  • rahul
    rahul over 7 years
    Great answer :D
  • cvb
    cvb over 7 years
    swift 3: [self.tableView .rectForRow(at: indexPath)];