I've encountered something that I've never seen. The scenario is as follows. My initial VC has an enroll button that when hit bring up a couple of separate view controllers deal with the enrollment procedure (I ask for user credentials, and if required by the server, I ask for a verification code). When this process is complete, I save a custom object to NSUserDefaults that is needed later. Then the initial VC is shown again.
Basically, I'm making sure I'm in the main thread and that the custom object is saved. If those conditions are met, I then push the main part of the app.
Here is the relevant code, all my logs are displayed in the output window, but the segue does not happen.
- (void)viewDidLoad {
[super viewDidLoad];
self.complete = false;
if([NSThread isMainThread]){
NSLog(#"Main Thread-Yes");
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"CUSTOMOBJECT"]) {
NSLog(#"Will begin pushToAuthenticate");
[self pushAuthenticateController];
}
} else {
NSLog(#"Main Thread-No");
}
}
- (void)pushAuthenticateController{
[self performSegueWithIdentifier:#"pushMainVC" sender:self];
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"pushMainVC"]) {
NSLog(#"PrepareForSegue-pushMainVC");
[segue.destinationViewController.navigationItem setHidesBackButton:YES];
[segue.destinationViewController.navigationItem setTitle:#"MainVC"];
}
}
In storyboard I have a segue that connects the initial VC with the MainVC, the identifier for that segue matches "pushMainVC", and if I close and reopen the app (after enrolling) the segues does occur. The console output for both instances (reloading the initial VC after enrolling and closing and reopening the app) are as follow:
APPNAME[4106:1628238] Main Thread-Yes
APPNAME[4106:1628238] Will begin pushToAuthenticate
APPNAME[4106:1628238] PrepareForSegue-pushMainVC
Any idea why this could happen, any small thing I'm missing, or any help will be greatly appreciated. Thank you so much.
You are attempting to perform a push segue from viewDidLoad. But you can't do that because it's too soon; viewDidLoad means that the view exists but is not yet in the interface. You need to wait until there is an interface so you have something to push from.
You cant try to load it from the storayboard itself.
like this:
pushMainVC* pushMainV = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([pushMainVC class])];
[self.navigationController pushViewController:pushMainV animated:YES];
NOTICE:
Dont forget to give your viewcontroller Storyboard ID.
Related
I have a button in a cell which calls a protocol that has data that needs to be passed to the view controller by the segue. The segue is happening through storyboard. My current code uses the shouldperformsegue to return no when the button is pressed as the first segue that happens does not have the data.
Im guessing the segue and the protocol are being handled asynchronously.
But before I return NO I tell it to perform the segue at a delay. This delayed segue does have the data and works fine.
My question is there a way to wait for the protocol to finish and then perform the segue? Maybe with an execution block?
The other responders have hinted about this, but haven't stated it explicitly, so here goes.
Do not tie a segue directly to the button. Instead, control-drag from the source view controller SCENE to the destination scene to create a segue at the view controller level. Give the segue a unique identifier.
Then, in your button IBAction code, do the async network download. You may want to display a "loading, please wait" message or something while the download is taking place. Most async network calls take a completion block. In that completion block, wrap a call to performSegueWithIdentifier in a call to dispatch_async(dispatch_get_main_queue() so the segue gets invoked on the main thread. (#SantaClaus's answer shows the syntax for that.)
So your button IBAction code might look like this:
- (IBAction) buttonAction: (UIButton *) sender;
{
//Display a "please wait"message to the user if desired
doAsyncCallTakingBlock( completion: ^(NSData *data)
{
//parse the data, (or whatever)
dispatch_async(dispatch_get_main_queue(), ^
{
//This call uses the button as the sender. That might be appropriate,
//or not.
[self performSegueWithIdentifier:#"jumpToOtherViewController"
sender: sender];
});
}
}
With this approach the segue doesn't get called until the async method (Which I called doAsyncCallTakingBlock in my example code) has finished it's work. You might call an Alamofire request method, use an NSURLSession, or any other async method that takes a completion block.
Yes, I would use blocks. For example:
[Api getDataWithCompletion:^(BOOL success, NSString *data) {
if (success) {
self.data = data;
[self performSegueWithIdentifier:#"MySegue" sender:self];
} else {
NSLog(#"Failed to get data");
}
}];
Then, to pass it to the next view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"MySegue"]) {
TargetViewController *targetVC = (TargetViewController*)segue.destinationViewController;
targetVC.data = self.data;
}
}
Check out performSegueWithIdentifier on UIViewController. If you set up a segue between the view controllers in your storyboard (not from the button) and give it an identifier, you can perform the segue as soon as the data is ready.
Since you mentioned that your data is being fetch asynchronously, you might need to dispatch the performSegueWithIdentifier call to the main thread like so:
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"jumpToOtherViewController" sender:self];
});
To actually pass the data on to the next view controller, you can use prepareForSegue as described here.
I am writing an ios app, that has multiple UIViewcontrollers. They all have UITableViews that are filled with data, that is acquired from different API's. But the problem that I am facing is that, when I tap on a cell, the the app won't navigate to the next page, until the data for that page is acquired. This make the app look, mighty slow. I need some way to navigate to next page, where I can put some spinner animation to let the user know that it is acquiring data(atleast). I don't want the user to think that the app has crashed, or something(it stays in the same page for solid 7-10 seconds)
Thanks in advance
you should call all the api in the background so it wont effect the main thread, use the queue for call api in background queue like below.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
//call apis here
});
My solution to this problem is,You can call the API using dispatch queue,So that it will not affect other functionalities.
Please follow these simple steps
ViewController1 = VC1
ViewController1 = VC2
in VC1
[self.navigationController pushViewController:VC2 animated:YES];
in VC2
#implementation {
BOOL hasData;
}
-(void)viewDidLoad {
hasData = NO;
[self getAndShowData];
}
-(void)getAndShowData {
// Start Showing Spinner
// load your data from server and
// on success
1.) hasData = YES;
2.) Call reload tableview
// Remove spinner
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section {
if(hasData == NO) return 0;
return actual number of rows.
}
}
i am integrating twitter app in my iOS app.the problem is on login when it return to my iOS app the segue which i want to perform is not performing visually, means the transition from one view controller to another is not visible but prepareForSegue method is being called and i can see that in my output console.
my twitter login method is as follow:
- (IBAction)tweetbutton:(id)sender {
[SCTwitter initWithConsumerKey:#"your_consumer_key" consumerSecret:#"your_consumer_secret"];
[SCTwitter loginViewControler:self callback:^(BOOL success){
NSLog(#"Login is Success - %i", success);
if (success) {
[self performSegueWithIdentifier:#"authentication" sender:self];
}
}];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSLog(#"prepareForSegue: %#", segue.identifier);
}
the output in console is:
2014-12-10 23:54:16.923 gems[1300:60b] Login is Success - 1
2014-12-10 23:54:16.946 gems[1300:60b] prepareForSegue: authentication
i don't understand where i am wrong and why segue is not performing visually.please suggest a suitable solution and also for your knowledge i am using plain view controller and not navigation controller.
thanks in advance.Any help would be appreciated.
Make sure you have a segue called "authentication" wired up correctly. You can check that by setting a breakpoint in prepareForSegue:sender: method and examine the segue.destinationViewController object.
the app are not work i am trying to enter a first name but it isnt appears in the main view
#pragma IBAaction
-(IBAction)DoneButton:(id)sender;
{
Customer *newCustomer=[[Customer alloc]init];
newCustomer.firstName=self.firstNameTextField.text;
newCustomer.LastName=self.LastNameTextField.text;
[self.delegate addCustomerViewControllerDidSave:self newCustomer:newCustomer];
[self.tableView reloadData];
}
-(IBAction)CancelButton:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
I'm just taking a stab in the dark here given the little information given. But either your Done button isn't wired to the DoneButton: method in interface builder, or self.delegate isn't set like you think it if.
Either way, the debugger or a couple of NSLog statements should help shed some light on the issue.
I'm developing an app that needs to pick up a JPEG from a web site on start-up.
The splash screen displays, then the app attempts to get a web address from a file. If the file is missing I open a modal view (as UIModalPresentationFormSheet) that has a text view for the user to type in an address - the address is then saved to a file.
The user taps the OK button, and an attempt is made to get the JPEG. If the address was wrong, or the JPEG is not on the web server, the modal dialog must re-open so the user can change the web address to the correct one.
The splash screen view controller contains these methods:
- (void)openAddressDialog
{
serverView *viewController = [[serverView alloc]init];
[viewController setServerAddress:[businessLogic serverAddress]];
[viewController setDelegate:self];
[viewController setModalPresentationStyle:UIModalPresentationFormSheet];
[self presentModalViewController:viewController animated:YES];
}
Interestingly, when I called the openAddressDialog method from the viewDidLoad method the modal view did not appear. I had to move it to the viewDidAppear method. So presumably the view has to be in a particular state before it will entertain modal views.
- (void)closeDialog:(UIViewController *)dialogController:(Boolean)actionRequired
{
// If action required, get the server address from the dialog
if (actionRequired)
{
serverView *viewController = (serverView *)dialogController;
NSString *address = [[viewController serverAddress]copy];
[businessLogic setServerAddress:address];
[self dismissModalViewControllerAnimated:YES];
if (![logoImage image])
{
[logoImage setImage:[businessLogic eventLogo]];
if (![logoImage image])
{
[self openAddressDialog];
}
}
}
else
{
exit(0);
}
}
This is the delegate method called back from the modal view when the user has touched OK or Cancel. The actionRequired param indicates that OK was tapped. And if so, the new server address is picked up from the modal view, and the modal view is dismissed. An attempt is made to get the JPEG from the new address (in a business rules class), and if still no file can be found, the first method shown above (openAddressDialog) is called again so the user can correct the address again.
The modal view appears fine the first time, but will not reappear if the user entered the wrong address. Does this have something to do with me attempting to represent the modal view so quickly after dismissing it?
I'm quite new to iPad development, so would appreciate any advice.
One other thing, which demonstrates my inexperience of C++ perhaps, is ... if I declare a private method in the m file, let's call it
- (void) methodB
and that method calls another private method, let's call it
- (void) methodA
methodA must be defined earlier in the m file than methodB. If I also want methodA to call methodB I reach an impasse. The only way around that I am aware of is to declare methodB in the h file - which makes it public. How do I code this scenario so the outside world can see neither of the methods?
if use to create nib to serverView then do like this
serverView *viewController = [[serverView alloc]initWithNibName:#"serverView" bundle:nil];