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.
Related
I'm using MZFormSheetController to present modals in my app. There is a situation where I want to present a second sheet controller right after I dismiss the first one. In order to do that, there is a completion block, but I can't figure out how to actually use it.
The code looks like this:
[self mz_presentFormSheetController:formSheet
animated:YES
completionHandler:^(MZFormSheetController *formSheetController) {
formSheetController.didDismissCompletionHandler;
}];
in that completion handler, what am I supposed to do to get notified of the sheet dismissal so I can then call the second sheet?
This is actually pretty simple, but not totally intuitive if you haven't spent some time in this type of environment.
[self mz_presentFormSheetController:formSheet
animated:YES
completionHandler:^(MZFormSheetController *formSheetController) {
formSheetController.didDismissCompletionHandler = ^(UIViewController *presentedViewController){
[self presentOtherController];
};
}];
I am running an animation on the iPhone with the below recursive function. When I animate, user interaction is blocked (and when the animation is done, user interaction works). I have been trying to enable user interaction, and have tried
passing the flag UIViewAnimationOptionAllowUserInteraction to animateWithDuration.
defining a function called touchesBegan as of this website. This function is never called (and called in other views when I tap the screen).
running the animation on a different thread with dispatch_async and dispatch_sync as this SO answer specifies. I have tried several methods but am not even sure if it'll work.
putting a UIButton in to detect taps. The function it's linked to isn't called for ~1-2 seconds.
To me, that all sounds like user interaction isn't enabled. How can it be responsive while this animation is running?
This animation is rather long and complex -- it's the whole reason this app exists. Each longAndComplicatedCalculation takes about 1s and this function is called ~30 times.
- (void)startAnimation:(dispatch_block_t)block withUIBlock:(dispatch_block_t)uiBlock iteration:(int)N{
[UIView animateWithDuration:1.0
delay:0.0
options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionAllowAnimatedContent)
animations:^(void) {
[block invoke];
[uiBlock invoke];
}
completion:^(BOOL finished) {
if(FINISHED_IF && N<N_MAX) {
__weak id weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf startAnimation:block withUIBlock:uiBlock iteration:N+1];
}];
}
}
];
}
This function is called with
[self startAnimation:^{
imageChange = [self longAndComplicatedCalculation];
} withUIBlock:^{
self.imageView.image = imageChange;
}
iteration:1];
You are calling both blocks (the block and the uiBlock) from the main thread. If your longAndComplicatedCalculation is blocking the thread the behaviour is normal. You should call your calculation in a separate thread and from there after finishing call the UIThread to initiate the animation.
To make it more clear, this is the way I would do it (without having your exact implementation and testing):
considering your image is declared as property:
#property (nonatomic, retain) UIImage *changedImage;
when you call the animation you can do it like
[NSThread detachNewThreadSelector:#selector(updateImage) toTarget:nil withObject:nil];
and in the function you do the calculation and afterwards call the animation on the UIThread:
- (void)updateImage {
self.changedImage = [self longAndComplicatedCalculation];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self doAnimationAndChangeImage];
}];
}
I hope this helps.
I'm having some really weird behavior in my UIViewController. I'm connecting two devices via the Multipeer Framework and once a iPhone starts sending data I want the other iPhone to display a UIViewController which handles the received data. So once iPhone A starts sending, iPhone B posts a NSNotification. The notification is arriving pretty fast and the code below is being executed:
- (void)presentPeerView:(NSNotification *)notif
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ClientViewController *clientView = (ClientViewController *)[storyboard instantiateViewControllerWithIdentifier:#"peerView"];
[clientView setImage:_image];
clientView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self.window.rootViewController presentViewController:clientView animated:YES completion:nil];
}
Now here comes the weird part. First of all, the ClientViewController gets displayed. But there is no animation, it just appears out of nowhere. My viewDidLoad gets called and runs following code:
- (void)viewDidLoad
{
[self.view setBackgroundColor:[UIColor colorWithPatternImage:[self blurImage:_image];
[self.imageView setImage:_image];
}
Now, I see the blurred image, which is fine. But the (non blurred) image dosen't displays in the UIImageView. I mean the image get's passed properly, otherwise I couldn't see the blurred Background.
I also have a few Interface elements from the Storyboard:
So the UIViewController loads with the blurred background Image:
As you can see, none of the Storyboard UI Elements are loaded, except for the UISwitch (which looks quite strange, too).
After a couple of seconds (it depends, sometimes up to 30, sometimes only 3 seconds) the whole UI loads up:
So, maybe someone of you had the same issue before, I really don't understand why the UI does take so much time to "load". The CPU percentage is somewhere around 3%.
I also added a NSLog() to the this completion block:
[self.window.rootViewController presentViewController:clientView animated:YES completion:^{
NSLog(#"done.");
}];
which is called instantly. The viewDidLoad seems to work fine, there are no lines it might get stuck, if I do a NSLog at the end of viewDidLoad it gets called instantly as well, but the view (as you can see in the images below) isn't loaded (completely).
From the MCSession class reference:
Important: Delegate calls occur on a private operation queue. If your app needs to perform an action on a particular run loop or operation queue, its delegate method should explicitly dispatch or schedule that work.
You're seeing this delay because you're doing UIView work on the wrong thread.
There's a number of ways to fix this, for example:
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
dispatch_async(dispatch_get_main_queue(), ^{
// post your notification or display your view
});
}
I have a piece of code that firstly calls a method that does quite a bit of thumbnail generation so it slows the device down for about a second. I was hoping to run a method that generates a "loading message" before the first method is called and then remove it when the first method is finished.
[picker dismissViewControllerAnimated:YES completion:^{
NSLog(#"Loading");
[self generatingThumbnailMessageShow];
[self loadAllEffects];
}];
The problem seems to be that although the "generatingThumbnailMessageShow" method is before the "loadAllEffects" method it still seems to get called after the "loadAllEffects" message is finished. What is the best method to call the "loadAllEffects" method only when the first method is finished?
The problem is that [self loadAllEffects] runs on the main thread and blocks the UI. Changes to the UI become only visible after program control has returns to the main runloop.
You have to move the execution of [self generatingThumbnailMessageShow]; to a background thread, something like
[picker dismissViewControllerAnimated:YES completion:^{
NSLog(#"Loading");
// Show "loading" message (must be done on main thread)
[self generatingThumbnailMessageShow];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// perform long running task on background thread
[self loadAllEffects];
dispatch_sync(dispatch_get_main_queue(), ^{
// Hide "loading" message (must be done on main thread again).
[self generatingThumbnailMessageHide];
})
})
}];
I have a modal view controller that appears, checks a service on the Internet and then dismisses itself when done. The nib contains an activity indicator and a label to inform the user what is going on.
When the update is complete, the label changes to "Update Complete" and then dismisses the view controller. However, I want it to delay the dismiss for a couple of seconds to give the user a chance to see the text before it disappears. So I've done this:
#pragma mark - AssetLoaderServiceDelegate
- (void)assetLoaderServiceDidFinishLoading:(AssetLoaderService *)service
{
[self.spinner stopAnimating];
self.infoLabel.text = #"Update complete";
[self performSelector:#selector(dismissUpdater) withObject:nil afterDelay:2.0];
}
- (void)dismissUpdater
{
[self dismissModalViewControllerAnimated:YES];
}
But for some reason, the selector is never being called. I've tried running it in mode NSRunLoopCommonModes too, but that doesn't work either.
I must be doing something wrong, but I can't see what...
EDIT: The delegate callback is actually happening within an NSOperationQueue, which might mean it's not on the same thread when it sends the message back to the view controller? So I tried
[self performSelector:#selector(downloadQueueComplete) withObject:nil afterDelay:0.0 inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
followed by
- (void)downloadQueueComplete
{
[delegate assetLoaderServiceDidFinishLoading:self];
}
But the performSelector doesn't seem to be working here either.
Following up your suggestion about the thread issue, would you try with:
[self performSelectorOnMainThread:#selector(downloadQueueComplete) withObject:nil waitUntilDone:YES]];
Sorted! In the AssetLoaderService, I had to perform selector on main thread:
[self performSelectorOnMainThread:#selector(downloadQueueComplete) withObject:nil waitUntilDone:YES];
After that, all the following calls were on the correct thread. :)