PresentViewController takes a lot of time - ios

I have a really strange situation: a view controller, called by presentViewController takes a lot of time to load. I mean that preparation stage takes a lot of time, though whole animation performed quickly. Going back from the presented controller to the previous one is fine.
What clues to I have?
First af all, I prepared my next view controller in first viewWillAppear:
METAddEventViewController *controller = [self.storyboard
instantiateViewControllerWithIdentifier:#"addEventView"];
self.nextController =
[[UINavigationController alloc] initWithRootViewController:controller];
Where self.nextController is nonatomic and strong.
So, my method that calls the next controller is pretty simple:
- (IBAction)addMeetingButtonPressed:(id)sender {
[self.addMeetingButton setImage:[UIImage imageNamed:#"addMeeting-pressed#2x.png"] forState:UIControlStateNormal];
[self presentViewController:self.nextController animated:YES completion:^{
NSLog(#"Completed");
}];
}
METAddEventViewController is just a UITableViewController with static cells of 4: 2 Basic cells, 1 cell with UIDatePicker and 1 with UITextView.
So, my viewDidLoad and viewWillAppear:
- (void)viewDidLoad
{
self.appointMeetingButton.enabled = NO;
self.title = #"Create Meeting";
self.commentaryView.delegate = self;
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[self navigationController] setNavigationBarHidden:NO animated:NO];
self.commentaryView.text = #"Enter comment for the meeting if needed";
self.commentaryViewFilled = NO;
self.commentaryView.textColor = [UIColor lightGrayColor];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateSelectedPersonCalled:)
name:#"updateSelectedPersonLabel"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateSelectedPersonCalled:)
name:#"updateSelectedLocationLabel"
object:nil];
[self.datePicker addTarget:self action:#selector(meetingAppointable) forControlEvents:UIControlEventValueChanged];
}
I ran Profiling and noticed that the most time consuming operations are allocation ones. Though, both viewDidLoad and viewWillAppear of METAddEventController are fine - about 10 ms. Most of the time in them takes configuring self.commentaryView
What can I do? Which additional information can I present to find the solution?

Related

UINavigationController pushes several of the same viewController

I have a UIButton in my app that when pressed it shows the next view controller. Sometimes the UI locks up and the app freezes for a moment due to background processes. When this happens the user might tap the button multiple times because nothing happened immediately on the first tap, and when this occurs the UINavigationController pushes the ViewController again a bunch of times on top of itself, so that you have to go back several times to get back to home. Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
self.pushVCButton.multipleTouchEnabled = NO;
}
- (IBAction)pushVCButtonPressed:(id)sender {
self.pushVCButton.enabled = NO;
ViewController *viewController = [[ViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
self.pushVCButton.enabled = YES;
}
How do I get this to never push multiple instances of viewController?
You should really try to make the background process run not on UI thread, but if you can not try setting the button enabled to yes only when view did disappear or listen for completion of push animation:
- (IBAction)pushVCButtonPressed:(id)sender {
self.pushVCButton.enabled = NO;
ViewController *viewController = [[ViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
// Hack: wait for this view to disappear to enable the button
//self.pushVCButton.enabled = YES;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated]
self.pushVCButton.enabled = YES;
}
Also make sure that the action is not called twice.

Game fps shutting down

I created View Controller for Game Over Scene. fps is shutting down when I restart game from Game Over ViewController. First time my fps is 30+, when I restart game it shutting down to 20, and I restart again it shutting down to 10...
and sometimes i see this error : Unbalanced calls to begin/end appearance transitions for GameOverViewController: 0x9bb8490>.
ViewController.m
- (void)awakeFromNib {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(goToGameOverViewController:)
name:#"GoToGameOverViewController"
object:nil];
}
-(void)goToGameOverViewController:(NSNotification *) notification {
GameOverViewController *gameOverController = [self.storyboard instantiateViewControllerWithIdentifier:#"GameOverViewController"];
[self.navigationController pushViewController:gameOverController animated:NO];
}
------------------------------------------------------------------------------
MyScene.m
- (void)gameOver {
[[NSNotificationCenter defaultCenter]
postNotificationName:#"GoToGameOverViewController" object:self];
}
GameOverView.m
-(void)playPressed
{
GameViewController *gameController = [self.storyboard instantiateViewControllerWithIdentifier:#"GameViewController"];
[self.navigationController pushViewController:gameController animated:YES];
}
-(void)createPlayButton
{
UIButton *playBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.view addSubview:playBtn];
playBtn.frame = CGRectMake(100, 100, 200, 44);
[playBtn setTitle:#"Play Again" forState:UIControlStateNormal];
[playBtn sizeToFit];
[playBtn addTarget:self action:#selector(playPressed) forControlEvents:UIControlEventTouchUpInside];
}
A couple core issues you have with your code and approach:
You continually push UIViewControllers onto your UINavigationController. Instead, maybe after a game finishes, you call popToRootViewControllerAnimated: or other. Every UIViewController you push on the stack stays around forever.
You never remove observers from NSNotificationCenter. That means each time you finish a level, each viewController is trying to push a GameOver viewController. That is what is causing the "unbalanced..." warnings. If you follow my advice from #1, you should only need to override dealloc and call the following code to fix that issue.
In playPressed use this code:
GameViewController *gameController = [self.storyboard instantiateViewControllerWithIdentifier:#"GameViewController"];
[self.navigationController setViewControllers:#[gameController] animated:YES];
And in later in GameViewController
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self]
}
This will make sure your ViewController, and therefore your scene, get deallocated and you don't have multiple ViewController trying to push games at the same time.
Make sure you free your SKScene when leaving from ViewController:
-(void)goToGameOverViewController:(NSNotification *) notification {
[((SKView *)self.view) presentScene:nil];
GameOverViewController *gameOverController = [self.storyboard instantiateViewControllerWithIdentifier:#"GameOverViewController"];
[self.navigationController pushViewController:gameOverController animated:NO];
}

UIApplicationDidBecomeActiveNotification and setRightBarButtonItem:animated:

TL;DR Update: Basically what I need is to delay my code until iOS finishes its "app startup" animation.
I would like to animate content of a navigation bar when my app becomes active. In my controller, I'm subscribed to UIApplicationDidBecomeActiveNotification and use setRightBarButtonItem:animated: to perform the change.
The problem is that the change is not animated.
I did some experimentation and discovered that if I wait a little ([NSThread sleepForTimeInterval:.3]), it's animating without any issues.
Here is a simple view controller demonstrating the problem:
#interface TESTViewController ()
#property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;
#end
#implementation TESTViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UINavigationItem *item = [UINavigationItem new];
UIBarButtonItem *oldItem = [[UIBarButtonItem alloc] initWithTitle:#"Old" style:UIBarButtonItemStyleBordered target:nil action:NULL];
[item setRightBarButtonItem:oldItem];
[[self navigationBar] setItems:#[item] animated:NO];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidBecomeActiveNotificationAction:) name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)applicationDidBecomeActiveNotificationAction:(NSNotification *)notification
{
// [NSThread sleepForTimeInterval:.3];
UIBarButtonItem *newItem = [[UIBarButtonItem alloc] initWithTitle:#"New" style:UIBarButtonItemStyleBordered target:nil action:NULL];
[[[self navigationBar] items][0] setRightBarButtonItem:newItem animated:YES];
}
#end
I'd like find a better solution than blocking the thread or performing the change after a fixed delay.
Simply use dispatch_after on the main queue instead of calling
+[NSThread sleepForTimeInterval:] class method.
Pass your animation as a block and it should work perfectly.
When posting notifications, I don't use your methods.
I use:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(changeButton) name:#"changeButton" object:nil];
on the receiving end.
And on the posting end, I call
[[NSNotificationCenter defaultCenter] postNotificationName:#"changeButton" object:nil];
Try moving your code to the method:
-(void)changeButton{
UIBarButtonItem *newItem = [[UIBarButtonItem alloc] initWithTitle:#"New" style:UIBarButtonItemStyleBordered target:nil action:NULL];
[[[self navigationBar] items][0] setRightBarButtonItem:newItem animated:YES];
}
Hope this helps!
Have you tried to performSelectorAfterDelay?
Try something like this:
- (void)applicationDidBecomeActiveNotificationAction:(NSNotification *)notification{
[self performSelector:#selector(yourAnimationAction) withObject:nil afterDelay:1];
}
- (void)yourAnimationAction{
//Set your NavAnimation here
[self.navigationItem setRightBarButtonItem:theItem animated:TRUE];
}
Just change the Delay to fit your apps wakeup delay... maybe this will take the effect?
Try by moving
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidBecomeActiveNotificationAction:) name:UIApplicationDidBecomeActiveNotification object:nil]; to viewDidLoad: method. I think object is not registered for "UIApplicationDidBecomeActiveNotification" while it is fired.
Just call viewDidAppear: manually from within your applicationDidBecomeActiveNotificationAction: method. Place your animation code in the viewDidAppear: method. Hope this helps.

UIViewController removeFromSuperview error

I have a UIViewController then when I longpress to self.view it will push a popup (MenuViewController). But when I try to remove popup by removeFromSuperview it still appears
You can see more detail of my problem with this http://www.youtube.com/watch?v=nVVgmeJEnnY
ViewController.m
#import "MenuViewController.h"
#interface ViewController () {
MenuViewController *menu;
}
....
- (void)viewDidLoad
{
....
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(albumButtonPressed : ) name:#"albumButtonPressed" object:nil];
....
}
....
-(void)albumButtonPressed : (NSNotification*) notification {
UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];
photoPicker.delegate = self;
photoPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:photoPicker animated:YES];
}
...
-(void)handleLongPress:(UILongPressGestureRecognizer*)recognizer {
menu = [[MenuViewController alloc] initWithNibName:#"MenuViewController" bundle:nil];
if (self.imageView.image != nil) {
menu.imageAdded = YES;
}
[self.view addSubview:menu.view];
}
MenuViewController.m
-(IBAction)albumButtonPressed:(id)sender {
[self.view removeFromSuperview];
[[NSNotificationCenter defaultCenter] postNotificationName:#"albumButtonPressed" object:nil];
}
Setting aside my reservations about not applying proper view controller containment, the problem is that your handleLongPress will be called multiple times with different recognizer.state values, once as UIGestureRecognizerStateBegan and again as UIGestureRecognizerStateEnded. You should be checking the state of the gesture, e.g.:
-(void)handleLongPress:(UILongPressGestureRecognizer*)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
menu = [[MenuViewController alloc] initWithNibName:#"MenuViewController" bundle:nil];
if (self.imageView.image != nil) {
menu.imageAdded = YES;
}
[self.view addSubview:menu.view];
}
}
Original Answer:
I'd suggest putting a NSLog or breakpoint at your code with the removeFromSuperview and see if you're even getting to that piece of code.
There are some clear problems here. Specifically, you're not adding added the view associated MenuViewController in handleLongPress properly. If you want a subview with it's own controller, you have to use containment (and that only works with iOS 5 and later). And in containment, you have critical methods like addChildViewController, etc. See Creating Custom Container View Controllers in the View Controller Programming Guide or see WWDC 2011 - Implementing UIViewController Containment. And, as an aside, you're also maintaining a strong reference to MenuViewController, so even if you succeeded in removing it's view, you'd leak the controller.
Spend a little time going through the containment documentation/video, and I think you'll want to revisit how you're presenting your menu. This is dense reading, but worth really understanding. Containment is powerful, but has to be done right.
[self.view removeFromSuperview];
what do you mean by this?????? removing the main view!!!!
Instead of directly using
[self.view removeFromSuperview];
use
[[self.view.superview subviews] makeObjectsPerformSelector:#selector(removeFromSuperview) withObject:self.view];

Show tableView popover as soon as data is loaded

i want to show a my Popover with a TableView just from the beginning, when the app loads. Problem is that the tableView doesn't have any content then. I am using that method:
UIBarButtonItem *barItem = [self.toolbar.items objectAtIndex:0];
[self.popoverController presentPopoverFromBarButtonItem:barItem permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
Now, my idea was to have a delegate method which is fired when the tableView finished fetching the data, but unfortunately nothing happens:
RootViewController.h:
#protocol RootDelegate <NSObject>
#optional
-(void)didFinishLoading;
#end
RootViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView initWithFrame:self.tableView.frame style:UITableViewStyleGrouped];
self.clearsSelectionOnViewWillAppear = NO;
[self setTitle:#"Zielscheiben"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataSaved:) name:#"DataSaved" object:nil];
[[self delegate] didFinishLoading];
}
DetailViewController.m (conforms to the protocol)
- (void)didFinishLoading
{
UIBarButtonItem *barItem = [self.toolbar.items objectAtIndex:0];
[self.popoverController presentPopoverFromBarButtonItem:barItem permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
The table view methods are threaded so your call at the end of viewDidLoad will happen at an unpredictable time, apparently here before the table is filled. I can't see any delegate method refering to the event "table is filled", so my best guess would be to keep your code and delay its execution :
[[self delegate] performSelector:#selector(didFinishLoading) withObject:nil afterDelay:1]
assuming 1 second will be enough for the table to be filled.
Another option you could explore is to fire it sometime in cellForRowAtIndexPath when you reckon the table has got enough data, but this is somehow more complex.
You need to place your popover in viewDidAppear:
- (void)viewDidAppear {
UIBarButtonItem *barItem = [self.toolbar.items objectAtIndex:0];
[self.popoverController presentPopoverFromBarButtonItem:barItem permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
viewDidAppear gets called after your UITableView has finished calling its datasource and tableview delegate methods.

Resources