I made an app for iPhone. Now, I'm recreating it for iPad.
When the user selects the action button in the toolbar, a popover should show with a UIActivityViewController, but for some reason, it's taking about 10 seconds for it to show the first time. On iPhone, it takes about a second. It's the same code except for the popover.
I tried disabling the popover, but it still takes around 10 seconds to show.
Here is the code:
-(IBAction)Actions:(UIBarButtonItem*)sender
{
if ([activityPopover isPopoverVisible] == YES)
{
[activityPopover dismissPopoverAnimated:YES];
return;
}
UIWebView *currentWebView = ((TabView *)self.tabs[self.currentTabIndex]).webViewObject;
NSString *currentURL = (NSString*)[currentWebView request].mainDocumentURL;
if (currentURL == NULL) return;
BookmarkActivity *bookmarkActivity = [[BookmarkActivity alloc] init];
UIActivityViewController *sharing = [[UIActivityViewController alloc] initWithActivityItems:[NSArray arrayWithObject:currentURL] applicationActivities:#[bookmarkActivity]];
activityPopover = [[UIPopoverController alloc] initWithContentViewController:sharing];
[activityPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
I have tested on my iPad 3 and my iPad mini, both take awhile to present this.
How can I solve the problem?
Good question, I just had the same problem. It is not really solvable. However, you may improve the user experience by creating an activity indicator and then sending the initialization of the UIActivityViewController to the background:
-(void)openIn:(id)sender
{
// start activity indicator
[self.activityIndicator startAnimating];
// create new dispatch queue in background
dispatch_queue_t queue = dispatch_queue_create("openActivityIndicatorQueue", NULL);
// send initialization of UIActivityViewController in background
dispatch_async(queue, ^{
NSArray *dataToShare = #[#"MyData"];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:dataToShare applicationActivities:nil];
// when UIActivityViewController is finally initialized,
// hide indicator and present it on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
[self presentViewController:activityViewController animated:YES completion:nil];
});
});
}
It works like a charm. When the user touches the button, the activity indicator starts animating, thus indicating that the process will take a while.
I was having the same issue on iOS 7. When I removed UIActivityTypeAirDrop from the allowed activity types, however, the controller appears almost instantly.
Although these calls are already from the main thread, since iOS 7, wrapping some of those presentation calls in a dispatch block seems to greatly reduce the delay
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:activityViewController animated:YES completion:nil];
});
Had this issue myself recently. Would sometimes take nearly 4 or 5 seconds to pop up, which is a lifetime! Only the first time though. Subsequent calls were quick.
Also had a similar issue a couple of years back with the keyboard appearing slowly and someone produced a few lines of code added to the appdelegate that preloads the keyboard to get around that.
I used a similar approach here to preload the UIActivityViewController by placing this in the AppDelegate on startup. It's absolutely a hack which shouldn't be necessary but I couldn't find any other options.
let lagfreeAVC:UIActivityViewController = UIActivityViewController(activityItems: ["start"], applicationActivities: nil)
lagfreeAVC.becomeFirstResponder()
lagfreeAVC.resignFirstResponder()
Related
I'm examining some code written by someone else, as part of a new job I've been hired for. One of the bugs confronting me is that, on certain pages, when the MBProgressHUD is displayed for "Logging In" or "Connecting," it gets a chunk taken out of the middle. For example:
Obviously what I'm looking for is something a little more like this:
It only seems to happen when the app returns from the background (i.e. not when the app is first booting up, when we also use MBProgressHUD but it works perfectly), and only on certain pages. The box loads correctly, and then about half a turn of the Activity Indicator later, that hole appears. It then stays like that until the box disappears.
I'd add some code to look at, but to be perfectly honest, I don't know where to start. I can't think of anything that could take a chunk out of the middle like that, and as you can see from the transparency of the second picture, there doesn't seem to be a box of that shape/size behind the Activity Indicator that could be accidentally turning green.
I've never used MBProgressHUD myself before, and I've never encountered a graphical bug of this nature. Does anyone know what is going on, or failing that, can anyone give me some leads to investigate regarding what could be causing this behavior?
EDIT:
Below is the code used to add the Activity Indicator to the HUD (from within the MBProgressHUD object):
// Update to indeterminate indicator
[self.indicator removeFromSuperview];
self.indicator = nil;
if (IOSVersion >= 8.0 && (DeviceScreenSize().height >= 1136.0 || DeviceScreenSize().width >= 1136.0)) {
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
} else {
self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
}
[(UIActivityIndicatorView *)indicator startAnimating];
[self addSubview:indicator];
This is part of a larger updateIndicators method, but the salient points are there: the indicator is removed (in case there was another there before), then it is re-added and animated. Comment out either of the startAnimating or addSubview lines, and the HUD appears without the Activity Indicator, but the problem never occurs.
That sounds to me like the animation of the Activity Indicator is somehow causing the missing piece of the underlying view. But why would that be? Has anyone heard of that kind of thing before?
EDIT 2:
As far as I can tell, the problem only happens on one ViewController in the whole app, and yet that ViewController never references MBProgressHUD or Activity Indicators of any kind. And all of that functionality is in the AppDelegate method applicationDidBecomeActive:, as below:
MBProgressHUD* hud = [[MBProgressHUD alloc] initWithWindow:self.window];
[self.window addSubview:hud];
hud.labelText = LocalizedString(#"Logging in...");
[hud showAnimated:YES whileExecutingBlock:^{
User* U = self.SelectedUser;
if (!isEmpty(U)) {
if ([U networkLogin]) {
[self setSelectedUser:U];
if ([U Disabled] != disabledTypesNone) {
[Flurry logEvent:#"Login Failed" withParameters:#{#"Name": U.DisplayName,
#"DeviceID": [#([Device sharedDevice].DeviceID) stringValue],
#"Disabled": [#([U Disabled]) stringValue]}];
ret = NO;
} else {
[Flurry logEvent:#"Login" withParameters:#{#"Name": U.DisplayName,
#"DeviceID": [#([Device sharedDevice].DeviceID) stringValue]}];
[Flurry setUserID:[NSString stringWithFormat:#"%# - %#", U.EmpID, U.DisplayName]];
}
} else {
ret = NO;
}
}
} completionBlock:^{
[hud removeFromSuperview];
if (!ret) {
[[NSNotificationCenter defaultCenter] postNotificationName:kDisplayLogin object:nil];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:kLoggedIn object:nil];
}
}];
Since it's happening in AppDelegate, it ought to be the same throughout the app, correct? And yet, when that same code is called (from AppDelegate, as before) while the app is on a different ViewController, it works with no problems.
What might make the behavior of that method different between different ViewControllers?
I have a tab-based app with 2 tabs: the first one performs multiple operations in a background thread (downloading json) and updates the UI on the main thread when the fetching is over. The second tab presents a camera as soon as it appears. When I open the app, the fetching starts in background in tab #1. If I switch to tab #2 while in the background thread in tab 1, the camera loads. If I wait until the main thread updated the UI (still tab 1) before switching to tab 2, the camera takes 10 seconds to load, only showing a black screen. What's even more weird is that the NSLogs tell me the camera is supposed to be already loaded, but a black screen shows up. My question is, is there a way to "clear" the main thread when tab #2 appears, or even better, is there a way to show the camera as a high priority task in the main thread?
This is the code in ViewDidAppear (Tab 2):
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"4");
[self showImagePickerForSourceType:UIImagePickerControllerSourceTypeCamera];
});
Next:
- (void)showImagePickerForSourceType:(UIImagePickerControllerSourceType)sourceType
{
NSLog(#"5");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"6");
[[UIApplication sharedApplication] setStatusBarHidden:YES];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
NSLog(#"7");
imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
imagePickerController.sourceType = sourceType;
imagePickerController.delegate = self;
imagePickerController.showsCameraControls = NO;
// NSLog(#"HERE");
if (isiPhone5)
{
NSLog(#"8");
[[NSBundle mainBundle] loadNibNamed:#"OverlayView" owner:self options:nil];
}
else
{
NSLog(#"not 5");
[[NSBundle mainBundle] loadNibNamed:#"Over2" owner:self options:nil];
}
self.overlayView.frame = imagePickerController.cameraOverlayView.frame;
imagePickerController.cameraOverlayView = self.overlayView;
self.overlayView = nil;
self.imagePickerController = imagePickerController;
[self presentViewController:self.imagePickerController animated:NO completion:nil];
NSLog(#"9 DONE");
}
});
}
Seeing log output doesn't necessarily mean that your camera view is visible, hence why there is a completion block provided to you in the presentViewController:animated:completion: method. Have you tried putting some code there to see when that's run?
This is the kind of problem that the time profiler tool in instruments was designed for. If you haven't already, I highly suggest investigating with that tool, as it can basically pinpoint what's causing the lag in your app.
I posted an answer here (iOS 7 UIImagePicker preview black screen) that may be a temporary solution to your issue. Essentially the UIImagePickerController has a memory leak in iOS 7, meaning that it'll get slower and slower with each instantiation until you have 10+ seconds of black screen while you wait for the camera to load.
As mentioned in the link above, I recommend subclassing UIImagePickerController or just making it an instance variable in your ViewController so its ever only instantiated once.
I know you're looking for an answer more along the lines of "clearing the main queue", but hopefully this will help you out in the meantime.
In our iPgone & iPad app we use push segue transitions between different ui contollers, most of them extend UICollectionViewController. In each controller we load data from our internal API. Loading is done viewWillAppear or viewDidLoad.
Now, the thing is, that this API call sometime can take a second or two, or even three... well, lot's of stuff there, let's assume we can't change it. But, we can change the user experience and at least add the "loading" circle indicator. The thing is, what I can't understand by means of correct concept, while transition from A to B, the "load" is done at B, while page A still presented.
So, question is "how do I show indicator on page A, while loading controller for page B?"
Thanks all,
Uri.
Common approach in this case is to load data in destination view controller NOT in main thread. You can show indicator while loading data in background thread and then remove it.
Here is sample of code from my project solving the same problem:
- (void) viewDidLoad {
...
// add indicator
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.spinner.hidesWhenStopped = YES;
self.spinner.center = self.view.center;
[self.view addSubview:self.spinner];
...
// fetch news
[self.spinner startAnimating];
__weak typeof(self) weakSelf = self
[[BitrixApiClient sharedInstance] getLatestNewsWithCompletionBlock:^(NSArray *newsArray, NSUInteger maxPageCount, NSUInteger currentPageNumber, NSError *error) {
if (!error) {
weakSelf.newsArray = newsArray;
weakSelf.currentPageNumber = currentPageNumber;
[weakSelf.newsTableView reloadData];
}
// stop spinning
[weakSelf.spinner stopAnimating];
}];
}
I have a strange issue using presentViewController as part of a library.
The code that is using the library calls this method. It takes up to 12 seconds from calling presentViewController to running the completion block. But not all the time normally it's almost instantaneous.
However if I touch any where on the screen while it is "lagging" it will fire instantly.
-(void) advancedMenuWithPresentingViewController
:(UIViewController *)presentingViewController
animated:(BOOL)animated
onClose:(void (^)(void))onClose
onPrint:(void (^)(NSString *, NSString *))onPrint{
AdvancedMenuViewController *amc = [[AdvancedMenuViewController alloc] init];
AdvancedMenuViewController * __weak weakAmc = amc;
[amc setOnClose:^(void)
{
[weakAmc dismissViewControllerAnimated:animated completion:^{
onClose();
}];
}];
[amc setOnPrint:onPrint];
//Time from here
[presentingViewController presentViewController:amc
animated:animated
completion:^{
//To here
}];
}
viewDidLoad and viewWillAppear are called without any lag and then there is a long delay (unless you touch the screen) until viewDidAppear is called.
There is nothing inside any of these methods that would slow it down. As it normally works fine.
If any one could shed any light on this issue I would be most grateful, the most confusing part is that if you interact with the screen after firing advancedMenuWithPresentingViewController it will occur instantly.
Replacing
[presentingViewController presentViewController:amc
animated:animated
completion:^{
//To here
}];
with
dispatch_async(dispatch_get_main_queue(), ^{
[presentingViewController presentViewController:amc
animated:animated
completion:^{
//To here
}];
});
Resolved the issue.
Thanks to rmaddys suggestion.
Run the app in Instruments with the Time Profiler.
That should at least tell you if you have some slow drawing code or if something in UIKit is slowing it down.
Reasons for Rejection: The activity indicator spins indefinetely and the user can't access the content
The same situation,Second time be rejected because of used MBProgressHUD.
Who can tell me Uploaded to appstore app would be any different? I done various tests, such a problem did not appear in the local.
-----------------------------in my controller-----------------------------------
- (void)downloadList
{
if (isLoading) {
return;
}
isLoading = YES;
//do something......
//show the progressbar based on MBProgressHUD
[[MyDelegate getAppDelegate] showProgressBarForTarget:self whileExecuting:#selector(showProgressBarForLoading)];
}
}
- (void)showProgressBarForLoading
{
while (isLoading) {
//i++;
continue;
}
}
- (void)downloadListDidReceive:(XGooConnection*)sender obj:(NSObject*)obj
{
//do something......
isLoading = NO;
}
-----------------------------in my AppDelegate-------------------------------
- (void)showProgressBarForTarget:(id)target whileExecuting:(SEL)theSelector
{
UIViewController *controller = [splitViewController.viewControllers objectAtIndex:0];
HUD = [[MBProgressHUD alloc] initWithView:controller.view];
[controller.view addSubview:HUD];
HUD.delegate = self;
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:theSelector onTarget:target withObject:nil animated:YES];
}
-----------------------------Reasons for Rejection detail-------------------------------------
The most recent version of your app has been rejected........
Reasons for Rejection:
The steps to reproduce are:
Launch the app
Select the Menu button at the top left corner
Select a menu item
The activity indicator spins indefinetely and the user can't access the content
First off, the reason for this rejection is likely improper usage of MBProgressHUD, not MBprogressHUD itself.
If this only occurs during app store testing, try running the app in Release configuration. There also might be networking conditions there, that you haven't anticipated. Perhaps this only occurs when there is a network error (airplane mode?). Are you setting isLoading = NO when a network error occurs?
FWIW, there is a much better way to show / hide the HUD for asynchronous requests. Pooling a flag in a while loop like this is extremely inefficient. Look at the NSURLConnection example in the MBProgressHUD demo app.