shouldPerformSegueWithIdentifier always executed first - objective-c

I have a function called "openButtonPressed" which gets executed when a button on the ui gets pressed. Now I would like to show a loading view at first and then execute the segue. For some reason, the segue always gets called first.
Does somebody have a clue?
Thank you!
- (void)openButtonPressed:(id)sender
{
NSDebug(#"Open Button");
[self showLoadingView];
static NSString* segueToContinue = kSegueToContinue;
if ([self shouldPerformSegueWithIdentifier:segueToContinue sender:self]) {
[self performSegueWithIdentifier:segueToContinue sender:self];
}
}
- (void)showLoadingView
{
if(!self.loadingView) {
self.loadingView = [[LoadingView alloc] init];
[self.view addSubview:self.loadingView];
}
[self.loadingView show];
}

It looks like LoadingView is being initialized without a size.
It also may be executing the segue very quickly and you don't have any time to see the loading view.
You can set a breakpoint at the performSegue call and use the view debugger in Xcode to confirm if the view was loaded and what size the view was initialized to.

Maybe the view is not in the right place or in the right size. But even if you fix that you need to add a delay so the user can see the loading... I replicated your scenario here and after giving the view a initWithFrame it appears, but cannot be seen because is too fast...
If you want to show the loading for a period of time before the performSegue, you could use the performSelector:withObject:afterDelay.
If you need to wait an action from the loadingView, create a completionHandler in the loadingView initializer...

The hints below were very helpful to me and totally a better solution but I ended up using [CATransaction flush]; to force the refresh.
- (void)openButtonPressed:(id)sender
{
NSDebug(#"Open Button");
[self showLoadingView];
[CATransaction flush];
static NSString* segueToContinue = kSegueToContinue;
if ([self shouldPerformSegueWithIdentifier:segueToContinue sender:self]) {
[self performSegueWithIdentifier:segueToContinue sender:self];
}
}

Related

Xcode how to switch between scenes

I'm new at Objective-C development and looking for a way to change from scene programmatically.
I've searched a lot but I keep getting errors.
Could anyone tell me what I'm doing wrong?
Any help would be very appreciated.
My code so far:
- (IBAction)retryOrNextLevel:(id)sender {
if (retryInteger == 1){
[self viewDidLoad];
}
if (retryInteger == 2){
Level2 *secondLevel = [[Level2 alloc] initWithNibName:#"Level2" bundle:nil];
[secondLevel setTitle:#"Level2"];
secondLevel.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:secondLevel animated:NO completion:Nil];
}
}
retryInteger is an integer that is set to 1 if you have failed the level and to 2 if you have completed the level of the game. I use the same button for two things: if you have failed the level you can replay the level and when you've completed the level you can go to the next level. Level2 is a UIViewController.
Thanks in advance!
Matis
First off, you should never call [self viewDidLoad]; this is called automatically when would you guess your viewDidLoad.
Second what is retryInteger? I am going to assume for this answer it is some sort of value you have set if the user has already hit this button, or done something.
So lets see what we can do with your code :
- (IBAction)retryOrNextLevel:(id)sender {
if (retryInteger == 1){
[self callSomeMethodThatIsntVIEWDIDLOAD];
}
if (retryInteger == 2){
Level2 *secondLevel = [[Level2 alloc] initWithNibName:#"Level2" bundle:nil];
[secondLevel setTitle:#"Level2"];
// Pushing onto a navigationController would seem more logical (Would recommend)
// [self.navigationController pushViewController:secondLevel animated:YES];
// Otherwise you want this
[self presentViewController:secondLevel animated:YES completion:nil];
}
}
There are several things assumed in this answer and until you update your question this answer is based on assumptions. First of what is Level2 is it a UIViewController? What error you are getting? As your original code should present the view controller so I am going to assume that when it tries to present it you are doing something in Level2 controller that is breaking it, cause if something goes wrong in Level2 controller then it will show on this line [self presentViewController:secondLevel animated:NO completion:Nil]; I would also make it so it is animated as if you say NO it makes this modalTransitionStyle pointless.
If you update your question I will try to improve this answer but until you do there isn't much else I can do. If your app quits on crash then use an Exception Breakpoint that is stops on when an exception is thrown.
EDIT
You are not calling [self retryOrNextLevel] you want to be calling [self retryOrNextLevel:myButton] your not calling the correct method. Where ever you are calling it you are missing the parameters so you are missing :myButton.
Your method is
- (IBAction)retryOrNextLevel:(id)sender;
and not
- (IBAction)retryOrNextLevel;
note the sender parameter.
To make sure that the class responds to retryOrNextLevel you can do
if([self respondsToSelector:#selector(retryOrNextLevel:)] {
// Code for calling the method.
}
I have found the answer! The error was because I forgot to declare something and the reason nothing happened when I touched the button was because I didn't drag a segue between the view controllers. This video answered my question: http://www.youtube.com/watch?v=XApYtAlRDVE.

Can't get UIImagePickerController to work?

-(void) openPhotoLib:(id)sender {
[self dismissModalViewControllerAnimated:YES];
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
[imagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
[imagePicker setDelegate:self];
[self presentModalViewController:imagePicker animated:YES];
NSLog(#"openPhotoLib called");
}
Nothing happens except for the NSLog, even though my view controller is a subclass of UINavigationController and UIImagePickerDelegate. Does anyone have any insight or experience with UIImagePickerController?
I should note that I'm primarily using an iPhone for testing.
SOLUTION: Make a new class that subclasses ONLY UINavigationBarDelegate and UIImagePickerDelegate. In that classes's viewDidAppear, put the code to modally present the imagePicker. Create an instance of this class inside the method (inside another class, import the .h file and all) and the modally present that class.
^ I take it back. The modal animations was the real problem. Trying to use another class instance for this messes up the method implementations of UIImagePicker.
The problem is due to dismissModalViewControllerAnimated and presentModalViewController being called one after the other.
The dismiss action takes some time as it has to animate the view being dismissed. During the animation, it is still the top Modal View. So, you cant present another model view during that time. If you try then the call fails and does nothing.
To fix, use [self dismissModalViewControllerAnimated:NO]; i.e no animation.
If you still want animation then follow one of these solutions:
Problem opening new ViewController after UIImagePickerController
Correct way of showing consecutive modalViews
EDIT:
If using storyboards, you should define prepareFroSegue in your delegate:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddPlayer"])
{
PlayerDetailsViewController
*playerDetailsViewController =
(PlayerDetailsViewController *) segue.destinationViewController;
playerDetailsViewController.delegate = self;
}
}
(source)
Am I not sure, but I think that the call:
[self dismissModalViewControllerAnimated:YES];
might interfere with the following call
[self presentModalViewController:imagePicker animated:YES];
since both are done within the same run loop.
I would suggest doing like this:
define a method to encapsulate the call to presentModal...
- (void)presentPicker:(...)picker {
[self presentModalViewController:imagePicker animated:YES];
}
replace the original call to presentModal... with:
[self performSelector:#selector(presentPicker:) withObject:picker afterDelay:0.0];
Explanation: by using performSelector the way I suggest, we are simply enqueuing the call to presentPicker in the run loop (without any delay actually, since we specify 0.0 as a delay value). In this way, we give UIKit a chance to dismiss the modal view and do all necessary clean up before we try and present the next modal view.
The reason to define presentPicker as a method is that performSelector only allows to specify one single argument (instead of the two that presentModal... requires).
Hope this helps.

UIButton still calls IBAction after it's disabled

I'm having a problem with the enabling/disabling of a UIButton, disabling the button works fine if I don't enable it again later in my code. When I do enable it later on it shows up as though it's disabled (the opacity changes) yet when I press it the attached IBAction function is still called.
The code:
- (void)loadDataFromURL:(NSURL *)URL withLoadIndicator:(UIActivityIndicatorView *)loadIndicator errorName:(NSString *)name sender:(id)sender andCallback:(SEL)selector{
// Start loading indicator, block button so we will have only one call at a time
[loadIndicator startAnimating];
if ([sender isKindOfClass:[UIButton class]]) {
UIButton *button = sender;
[button setEnabled:NO];
}
// Run the data load sequence
self.dispatchQueue = dispatch_queue_create("com.companyname.settingsqueue", 0);
dispatch_async(self.dispatchQueue, ^{
// Downloading JSON and using CoreData to put it into the sqlite database here
});
// After loading is complete stop animating and re-enable the button
dispatch_async(dispatchQueue, ^{
[loadIndicator stopAnimating];
if ([sender isKindOfClass:[UIButton class]]) {
UIButton *button = sender;
button.enabled = YES;
}
});
}
The strange thing is it works perfectly for the loadIndicator. The button I get from the sender parameter in the function does exist (it's not null). When I remove button.enabled = YES; it stays disabled as it should. Is there a way to enable it again after my async code has executed without the button still being enabled during the async execution?
Thanks in advance for helping me.
I found the answer to my problem after extensive searching. Because I wasn't running all my UIView functions on the main thread the UI didn't update even though my function finished. Therefor the button disabled for a really short time and then enabled again, even though my overlaying UIView and UIActivityIndicator were not yet removed from the screen.
I hope this helps someone else with similar issues.

Grand Central Dispatch. When using GCD [spinner startAnimating] similar to [myView setNeedsDisplay]?

In Grand Central Dispatch I want to start a spinner - UIActivityIndicatorView - spinning prior to beginning long running task:
dispatch_async(cloudQueue, ^{
dispatch_async(dispatch_get_main_queue(),
^{
[self spinnerSpin:YES];
});
[self performLongRunningTask];
dispatch_async(dispatch_get_main_queue(),
^{
[self spinnerSpin:NO];
});
});
Here is the spinnerSpin method:
- (void)spinnerSpin:(BOOL)spin {
ALog(#"spinner %#", (YES == spin) ? #"spin" : #"stop");
if (spin == [self.spinner isAnimating]) return;
if (YES == spin) {
self.hidden = NO;
[self.spinner startAnimating];
} else {
[self.spinner stopAnimating];
self.hidden = YES;
}
}
One thing I have never seen discussed is the difference - if any - between [myView setNeedsDisplay] and [myActivityIndicatorView startAnimating]. Do they behave the same?
Thanks,
Doug
The [UIView setNeedsDisplay] method has nothing to do with a UIActivityIndicatorView's animation state.
setNeedsDisplay simply informs the system that this view's state has changed in a way that invalidates its currently drawn representation. In other words, it asks the system to invoke that view's drawRect method on the next drawing cycle.
You very rarely need to invoke setNeedsDisplay from outside of a view, from code that is consuming the view. This method is meant to be invoked by the view's internal logic code, whenever something changes in its internal state that requires a redraw of the view.
The [UIActivityIndicatorView startAnimating] method is specific to the UIActivityIndicatorView class and simply asks the indicator to start animating (e.g. spinning). This method is instant, without requiring you to call any other method.
On a side note, you could simplify your code by simply calling startAnimating or stopAnimating without manually showing/hiding it. The UIActivityIndicatorView class has a hidesWhenStopped boolean property that defaults to YES, which means that the spinner will show itself as soon as it starts animating, and hide itself when it stops animating.
So your spinnerSpin: method could be refactored like this (as long as you haven't set the hidesWhenStopped property to NO):
- (void)spinnerSpin:(BOOL)spin {
if (YES == spin) {
[self.spinner startAnimating];
} else {
[self.spinner stopAnimating];
}
}

objective-c modalViewController too quick

I am having an issue dismissing a modal view controller on a certain edge case. I display the modal view when I am retrieving a PDF to display in a UIWebView. When the file I am retrieving is very small the modal view will try to dismiss too soon. I present the modal view in the view controller that contains the UIWebView. I dismiss it in the UIWebView's didFinishLoad delegate method.
I am fine with not animating the initial presentation of the modal view... but is that any more safe than what I was doing? does this still have potential to fail, and if so how would you change it? I have been looking through the docs and nothing I have read so far adresses this situation.
//
// This will download the file if not # specific path, otherwise use local file.
// _myFileManager is a helper class and _myFileRecord is the backing data model
//
-(id)initWithNib... fileRecord:(MYFileRecord *)_myFileRecord
{
[_myFileManager cacheFileAsync:_myFileRecord delegate:self];
}
- (void)viewDidLoad
{
// doesn't seem to work, NO for animated does seem to work
[self.navigationController presentModalViewController:_splashController
animated:YES];
_splashController.messageLabel.text = #"Retrieving File...";
}
- (void)recordSaved:(MyFileRecord *)myFileRecord fileName:(NSString *)fileName
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:fileName]];
[_webView loadRequest:request];
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
_splashController.messageLabel.text = #"Opening File...";
}
//
// This fails when a small file is already cached to disk and the time
// for the webView to finishLoad is faster than the splashView can present itself
//
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self.navigationController dismissModalViewControllerAnimated:YES];
}
Try implementing the viewDidAppear in your SplashController, to catch when the view has finished animating, and set a flag. Then you can control if the SplashController's view has finished loading using this flag, and wait for it if it is not finished yet?
E.g.
-(void)viewDidAppear {
if (shouldDismiss) {
[self dismissViewControllerAnimated:YES];
}
readyToDismiss = YES;
}
And in your main VC:
-(void)webViewDidFinishLoading:(UIWebView*)webViewv
{
if (_splashController.readyToDismiss) {
[_splashController dismissViewControllerAnimated:YES];
} else {
_splashController.shouldDismiss = YES; // will dismiss in viewDidAppear
}
}
You can try testing to see if the splashView has finished and use performSelector:afterDelay: to check back later.
My idea is to create a method like this
-(void)dismissWhenReady {
if ( splashView is finished) {
[self.navigationController dismissModalViewControllerAnimated:YES];
} else
[self performSelector:#selector(dismissWhenReady) afterDelay:1.0];
}
}
viewDidLoad fires too early (before it is displayed), you will want to use -(void)viewDidAppear:(BOOL)animated to present your modal view instead along with a flag to know if it is the first load. If it still does not display long enough add a delay for the desired amount of time.