Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unpredictable delay before UIPopoverController appears under iOS 8.1

This problem happens with SDK 8.1, when running under iOS 8.1, but not when running under iOS 7. It is for iPad only. The problem appears both with simulator and on a hardware device.

The code below demonstrates a view controller that contains a UITableView with 1 row, and below that, a UIButton. Tapping on the button or on the row will cause a popover to appear. This works just fine when tapping the button, but when tapping the row of the tableview, the popover appears with some delay. In my testing, first time I tap the row the popover usually appears with little or no delay, but second time I tap the row, it can take many seconds before the popover appears, and often it does not appear until I tap somewhere else on the view. However, the delay can happen even at the first tap on the row (especially when tested on hardware).

The code I show has been boiled down as much as possible, while retaining the problem. It seems to me, that the UITableView is somehow critical to cause this delay to happen.

I do know that some of the UIPopoverController code is deprecated under iOS 8. I have tried to use UIPopoverPresentationController for iOS 8, and the result is the same: A sometimes very long delay before the popover appears. I am not yet very familiar with this new approach, so I may well be making mistakes, but in any case the iOS 8 code can be tested by setting the USE_TRADITIONAL_METHOD macro to 0 instead of 1.

Any suggestions on how to fix or bypass this (while still using a tableview) will be greatly appreciated.

#import "MyViewController.h"

@interface MyViewController ()

@property (nonatomic) UIPopoverController* myPopoverController;

@end

@implementation MyViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // setup table view
     UITableView* tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 500, 500) style:UITableViewStyleGrouped];
    [self.view addSubview:tableView];
    tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    tableView.dataSource = self;
    tableView.delegate = self;
    
    // setup button
    UIButton* button = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.view addSubview:button];
    button.frame = CGRectMake(20, 550, 100, 20);
    [button setTitle:@"Show popover" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(showPopover:) forControlEvents:UIControlEventTouchUpInside];
}


- (IBAction)showPopover:(id)sender
{
    UIView* senderView = (UIView*)sender;
    
    UIViewController* contentViewController = [[UIViewController alloc] init];
    contentViewController.view.backgroundColor = [UIColor purpleColor];
    
#define USE_TRADITIONAL_METHOD 1

#if USE_TRADITIONAL_METHOD
    self.myPopoverController = [[UIPopoverController alloc] initWithContentViewController:contentViewController];
    [self.myPopoverController presentPopoverFromRect:senderView.frame inView:senderView.superview permittedArrowDirections:UIPopoverArrowDirectionLeft animated:NO];
#else
    contentViewController.modalPresentationStyle = UIModalPresentationPopover;

    [self presentViewController:contentViewController animated:NO completion:^{
        NSLog(@"Present completion");       // As expected, this is executed once the popover is actually displayed (which can take a while)
    }];

    // do configuration *after* call to present, as explained in Apple documentation:
    UIPopoverPresentationController* popoverController = contentViewController.popoverPresentationController;
    popoverController.sourceRect = senderView.frame;
    popoverController.sourceView = senderView;
    popoverController.permittedArrowDirections = UIPopoverArrowDirectionLeft;
    
#endif
    NSLog(@"Popover is visible: %d", self.myPopoverController.isPopoverVisible);    // This shows "1" (visible) for USE_TRADITIONAL_METHOD, under both iOS 7 and iOS 8
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 1;
}

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.textLabel.text = @"Show popover";
    return cell;
}

#pragma mark - UITableViewDelegate

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self showPopover:[tableView cellForRowAtIndexPath:indexPath]];
}


@end
like image 478
rene Avatar asked Nov 29 '14 14:11

rene


2 Answers

I found that deselecting the row before attempting to show the popover seems to fix the problem. This may be a useful work-around, but I'm still looking for a better answer, since this may not be robust.

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:NO]; // adding this line appears to fix the problem
    [self showPopover:[tableView cellForRowAtIndexPath:indexPath]];
}

Note, as Yasir Ali commented, that for this workaround to work, deselect animation must be off. Almost 4 years after the original post, this behavior still affects iOS 12, and the bug report with Apple is still open - sigh.

like image 116
rene Avatar answered Sep 24 '22 20:09

rene


I had the same issue, and deselecting the cell prior to displaying the popover did nothing to resolve my issue. What did work for me was dispatching the popover code to the main queue with a minuscule delay. This produces consistent display of the popover AND allows me to keep the cell SELECTED, which is key to my UI:

Expected UI

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    switch actions[indexPath.row] {
    case .rename:
          .... 
    case .duplicate:

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.01, execute: {
            let copyController = UIStoryboard.init(name: "Actions", bundle: nil).instantiateViewController(withIdentifier: "copyToNavController")

            copyController.modalPresentationStyle = .popover

            let cell = tableView.cellForRow(at: indexPath)
            copyController.popoverPresentationController?.sourceView = cell
            if let frame = cell?.frame {
                copyController.popoverPresentationController?.sourceRect = frame
            }
            copyController.popoverPresentationController?.permittedArrowDirections = .left

            self.popoverPresenter = copyController.popoverPresentationController
            self.present(copyController, animated: true, completion: nil)
        })
    default:
        break
    }
}
like image 26
Ken Franklin Avatar answered Sep 22 '22 20:09

Ken Franklin