I am working on implementing Apple Pay for my Application. While I have everything mostly figured out, I am not able to dismiss the PKPaymentAuthorizationViewController that authenticates.
Here is my code..
-(void) viewDidAppear:(BOOL)animated {
// request is initialized here.
paymentPane = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:request];
//paymentPane is not nil.
paymentPane.delegate = self;
[self presentViewController:paymentPane animated:TRUE completion:nil];
}
The above function is called and successfully presents the Apple Pay View Controller to authenticate the payment. I have implemented the following methods from the delegate as follows:
-(void)paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didAuthorizePayment:(PKPayment *)payment completion:(void (^)(PKPaymentAuthorizationStatus))completion {
NSLog(#"%#", [payment.token description]);
completion(PKPaymentAuthorizationStatusSuccess);
}
Ideally it hits the didAuthorizePayment first, where we authenticate the payment, and pass success if its successful, then the paymentAuthorizationViewControllerDidFinish is called and that is used to complete transaction and hide the Apple View Controller. I put breakpoints in there and it hits both functions, but the ViewController doesn't go away. It doesn't go even if you tap the Cancel button inside Apple Pay View.
My class declaration is as follows and does implement the required delegates.
#interface ApplePayManager : UIViewController<PKPaymentAuthorizationViewControllerDelegate, UIAlertViewDelegate>
Any ideas on what I am doing wrong? Why is the view controller not dismissing?
Lastly, I am a bit new to iOS. So I'd appreciate guides and/or links to clarify my doubts!
Already looked at:
http://www.raywenderlich.com/87300/apple-pay-tutorial
https://dzone.com/articles/integrating-your-ios-app-apple
This worked. My teammate found out this solution. Turns out that the ViewController we presented the ApplePay View on wasn't the rootVC. So, switching it to rootVC worked.
UIViewController *rootViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
[rootViewController presentViewController:paymentPane animated:TRUE completion:nil];
Related
I have in my objective-c application a loginViewController from where I display the homeViewController after the user logged in successfully. This is how I display the home:
-(void) displayHome {
UIViewController * home = [self.mainStoryboard instantiateViewControllerWithIdentifier:#"homeViewController"];
_window.rootViewController = publicHome;
[_window makeKeyAndVisible];
}
The displayHome function is in the AppDelegate. After that, in the homeViewController i present some view controllers using
[self presentViewController:controller animated:false completion:nil];
Sometimes I have to redirect the user to the loginViewController so I call this VC the same as I do for the home (by setting the rootViewController) in order to remove all the other views. But when I use the 3d debugger I see that the presented views are there.
Why when I set the rootViewController with the loginViewController, the presented views are not removed even if there are attached to the homeViewController that is as the rootViewController?
Edit
An example:
Suppose that from the homeViewController(that is the rootViewController) I go to the viewController1 (viewController1 is presented). Now when I press a button in the viewController1 I have to return to the loginViewController. In the button action, this is what I do:
I call
[self dismissViewControllerAnimated:false completion:nil];
and [appDelegate displayLogin];
//AppDelegate
-(void) displayLogin {
UIViewController * login = [self.mainStoryboard instantiateViewControllerWithIdentifier:#"homeViewController"];
_window.rootViewController = publicHome;
[_window makeKeyAndVisible];
}
When I debug, I find that the viewController1 is still here. It under the login. Why is the viewController1 here even if I set the rootViewController?
Maybe you should first check whether the method -dealloc has been executed in you UIViewControllers. Try add this code in viewController1:
- (void)dealloc {
NSLog(#"ViewController %# dealloc", NSStringFromClass([self class]));
}
If this method hasn't been invoked, then check whether there is still any strong reference pointing to viewController1 after you called [self dismissViewControllerAnimated:false completion:nil];
This usually happens due to the retaincount, check your implementation and make sure there is no completion block or something which is not letting dealloc get called. Once your dealloc gets called all views will get removed
Since testing my app on iOS 8, I find a work around view controllers initialization and presentation really baaadly slow.
I used to work with a code similar to this on iOS 6 & 7:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
....
[self.window setRootViewController:_rootController];
[self.window makeKeyAndVisible];
// Conditions
if (#first launch condition#) {
// quite small controller containing Welcome showcase
WelcomeViewController *w = ....
[_rootViewController presentViewController:w animated:NO];
}
else if (#last opened item condition#) {
// pretty big container, root view controller contains
// a grid view which opens Item detail container the same way
ItemDetailController *item = ....
[_rootViewController presentViewController:item animated:NO];
}
}
This became a really sluggish hell with iOS 8. Root view controller now appears visible for 0.5-1 second and then instantly redraws the screen with presented one. Moreover, the slowness of the presentation began to cause an Unbalanced calls to begin/end appearance transitions _rootViewController warning.
Initial quick hint was to move both conditions with calls to another function and call it with a zero-delay so it's processed in next main run loop:
[self performSelector:#selector(postAppFinishedPresentation) withObject:nil afterDelay:0];
or something like that. This fixes the unballanced calls issue, but the visual gap (rootviewcontroller, gap, presented one) becomes (obviously) even bigger.
The slowness of the presentation is also obvious when you call something usual as:
// Example: Delegate caught finished Sign In dialog,
// dismiss it and instantly switch to Profile controller
-(void)signInViewControllerDidFinishedSuccessfully
{
[self dismissViewControllerAnimated:NO completion:^{
UserProfileViewController *userProfile = ...
[self presentViewController:userProfile animated:NO];
}];
}
which should be completely fair piece of code, which used to perform direct transition without a visible flick of parent view controller on iOS 7. Now, same thing – parent flicks during the transition, even it's both handled without animation.
Does anybody face this as an issue? Any solutions? I'd love to solve this without a need to do some hilarious magic with UIWindows for each thing I need to transit flawlessly.
I am not sure the requirement is restricted to have root view controller and present anything over there.
But according to your code it's have welcome view controller thing and I think in this case, this logic is more useful.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Conditions
if (#first launch condition#) {
// quite small controller containing Welcome showcase
WelcomeViewController *w = ....
//It can be navigation or tab bar controller which have "w" as rootviewcontroller
[self.window setRootViewController:w];
}
else if (#last opened item condition#) {
// pretty big container, root view controller contains
// a grid view which opens Item detail container the same way
ItemDetailController *item = ....
//It can be navigation or tab bar controller which have "item" as rootviewcontroller
[self.window setRootViewController:item];
}
[self.window makeKeyAndVisible];
}
If you use Storyboard, why not try:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:[[NSBundle mainBundle].infoDictionary objectForKey:#"UIMainStoryboardFile"] bundle:[NSBundle mainBundle]];
ViewController *_rootController = [storyboard instantiateViewControllerWithIdentifier:#"root"];
[self.window setRootViewController:_rootController];
[self.window makeKeyAndVisible];
if (vcToShow == 1) {
ViewController2 *w = [storyboard instantiateViewControllerWithIdentifier:#"vc2"];
[_rootController presentViewController:w animated:NO completion:nil];
}
else if (vcToShow == 2) {
ViewController2 *w = [storyboard instantiateViewControllerWithIdentifier:#"vc3"];
[_rootController presentViewController:w animated:NO completion:nil];
}
It looks like there is no delay here.
The delay I had from a dismiss/present pair was fixed by this. It may or may not help your instance. I had to completely change my strategy of displaying/dismissing modal view controllers under iOS 8:
[_rootViewController presentViewController:vc1 animated:NO completion:nil];
if(iNeedToDisplayVC2) {
[vc1 presentViewController:vc2 animated:NO completion:nil];
}
So once I am done with vc2 later on, I dismiss both vc1 and vc2 in the same call. This strategy works under iOS 7 as well. I assume earlier versions too but I did not test them.
With iOS8, I've found the old presentation is not quite completed yet in the completion block, and calling dismiss or present right away can lead to console messages and sometimes the present/dismiss does not even occur. I have had some success by adding a further delayed perform to the second presentation:
[self dismissViewControllerAnimated:NO completion:^{
UserProfileViewController *userProfile = ...
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self presentViewController:userProfile animated:NO];
}];
}];
EDIT
After some more digging around it has become apparent that if i try to call a segue from the view controller class after a fast application switch to/from Safari i get an exception stating that the segue does not exist. It definitely does, in the VC that is loaded on screen, and i have checked the spelling and it's 100% correct.
What makes it even weirder is that if i call that exact same method from an IBAction it works fine.
Here's the code i am using:
-(void)goToPhotoPage{
[self performSegueWithIdentifier:#"twitterAuthComplete" sender:self];
}
If i call this when the app resumes i get an exception, however if i call it from this IBAction, attached to a UIButton, with doing nothing different the segue works as expected and no exception.
- (IBAction)twitterToPhoto:(id)sender
{
[self goToPhotoPage];
}
Arrrrgrghrhhgrgrg!
ORIGINAL QUESTION
I am working on an iPad application that uploads user photos to Facebook/Twitter.
As part of the process i have to jump to Safari to do OAuth for twitter, the app then jumps back via a specific URL and get's the tokens e.t.c
However for some reason when the application re-awakes i cannot trigger any segue's or manually load any VC's.
I have a success block in my AppDelegae which get's called when the upload is complete which calls a method in the VC to close a spinner and segue to the success view.
The spinner stops as expected, but no matter what i try i cannot get the segue to work.
Everything is hooked up, the segue has an ID and i get no error or exceptions in the console, just nothing happens. The only way i can get code triggered segue to work after this point is to use a user trigger one connect to a UIButton, after that one complete they start to work again.
Here is the code with callback in my AppDelegate:
- (void)postToTwitter:(NSString*)postText image:(UIImage*)image
{
NSData *data = UIImageJPEGRepresentation(image, 0.5);
[_twitter postStatusUpdate:postText
mediaDataArray:#[data]
possiblySensitive:nil
inReplyToStatusID:nil
latitude:nil
longitude:nil
placeID:nil
displayCoordinates:nil
uploadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
} successBlock:^(NSDictionary *status) {
ViewController * vc = [[ViewController alloc]init];
[vc triggerSuccess];
} errorBlock:^(NSError *error) {
ViewController * vc = [[ViewController alloc]init];
[vc uploadFail:error];
}];
}
in the VC i have tried the following:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
}
As well as:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
In a moment of pure desperation i even tried:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
//[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
[self performSelector:#selector(segueToSuccessPart2) withObject:nil afterDelay:0.25];
}
- (void)segueToSuccessPart2
{
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
I've obviously missed something, and i'm guessing it's to do with the application going into the background and the VC being "unloaded" from memory, and needing re-instatiating, but i'm not sure where to begin with that.
Any help would be greatly appreciated as i'm about to throw the computer out of the window . .
Thanks
Gareth
Edit 1
After doing some more troubleshooting it appears that during this call in the VC:
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
self.navigationController is nil . . .
I'm guessing that is likely the cause of the issues i'm seeing?
Edit 2
So as suggested by Kelin, i am trying to make sure i retain the Navigation Controller.
I have done the following in AppDelegte:
#property (nonatomic, strong) UINavigationController *navController;
and
#synthesize navController;
But when i try to access this from the VC using the below code, it's still nil . .
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
UINavigationController *navController = appDelegate.navController;
[self dismissViewControllerAnimated:NO completion:nil];
[navController popToRootViewControllerAnimated:NO];
Edit 3
I have also added this to the AppDelegate, which means the Navigation Controller is no longer nil. But still nothing happens . . .
if (!self.navController)
{
self.navController = [[UINavigationController alloc]initWithNibName:#"Main" bundle:nil];
}
The problem is you trying to perform segue from storyboard using programmatically created View Controller.
You use -performSegueWithIdentifier: sender: completely wrong. In this method "sender" is a button, or any other control which action initialises segue, but not the view controller to be pushed.
Usually segue is created with storyboard. But if you really need to create it manually, call:
-[UIStoryboardSegue initWithIdentifier:source:destination:]
where source and destination are view controllers.
Read this: https://developer.apple.com/library/ios/recipes/xcode_help-interface_builder/articles-storyboard/StoryboardSegue.html
or this (about custom segues):
You can not do both dismiss and pop together either you have to pop or dismiss the view controller and
Every ViewController have it's own NavigationController which is default point out to navigation controller you added to AppDelegate.
So, You also can access it by self.navigationController (Edit 3)
Also check whether you initialised navigation controller in
AppDelegate
self.navController = [[UINavigationController alloc] initWithRootViewController:firstVC];
This one is tricky. I have a subclass of UINavigationController that overrides pop/push and present/dismiss methods. Here I customise the behaviour to set the correct size if the UINavigationController subclass is contained in a popover. Nothing too fancy, but I do it this way to don't write subclasses of all my ViewControllers and use Autolayout.
However, the completion blocks of the presentViewController:animated:completion: and dismissViewControllerAnimated:completion: are not being executed. And this is the weird part: the exact same code on iPhone works correctly, but on iPad is not executing the blocks. Here is a code sample.
#interface SBNavigationController : UINavigationController
#end
#implementation SBNavigationController
- (void) presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
if ([viewControllerToPresent isKindOfClass:[UINavigationController class]])
{
UINavigationController *nav = (UINavigationController *) viewControllerToPresent;
[nav.topViewController setContentSizeForViewInPopover:kFullSizePopover];
} else
{
[viewControllerToPresent setContentSizeForViewInPopover:kFullSizePopover];
}
viewControllerToPresent.modalPresentationStyle = UIModalPresentationCurrentContext;
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion ;
{
[super dismissViewControllerAnimated:flag completion:completion];
}
#end
And the code using it is this:
#implementation SBInviteFBContactViewController
...
- (void) createInviteByMailViewController
{
SBInviteMailViewController *mailInvite = [[SBInviteMailViewController alloc] initWithDelegate:self userInfo:_userInfo];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:mailInvite];
[self.navigationController presentViewController:navController
animated:YES
completion:^{
NSLog(#"presentViewController:");
}];
}
#pragma mark SBInviteMailProtocol
- (void) invitedMailContacts:(NSArray *)contacts;
{
[self.navigationController dismissViewControllerAnimated:YES
completion:^{
NSLog(#"animation Ended");
if (contacts) {
[self.delegate invitedMailContact:contacts];
[self popViewControllerAnimated:YES];
}
}];
}
...
#end
Any ideas?
This seems to be a huge bug. Please report it to Apple (and I am about to do the same). I found my way here because I just discovered the same bug myself, and did a google search to see if anyone else was talking about it.
I've created a very small demonstration project, whose architecture is like this:
ViewController - the main view controller
Its view contains a button Tap Me.
PopoverViewController - presented in popover
When you tap Tap Me in the main ViewController, it creates a UIPopoverController with this vc, PopoverViewController, as its content view controller; its view, too, contains a button Tap Me.
PopoverViewController2 - presented "modally" in same popover
PopoverViewController2 has its modalPresentationStyle set to UIModalPresentationCurrentContext so it can appear inside the popover. When you tap Tap Me in the popover, PopoverViewController calls presentViewController:....
Here's the code:
- (IBAction)doTapMe:(id)sender {
NSLog(#"about to present view controller");
[self presentViewController:[PopoverViewController2 new] animated:YES completion:^{
NSLog(#"in completion handler"); // never called!
}];
NSLog(#"did present view controller");
}
The log reads "about to present view controller" and "did present view controller", but "in completion handler" never appears, even though the "modal" view controller's view appears in the popover just fine.
(Moreover, changing to animated:NO not only doesn't fix it, it causes a visual glitch.)
The UIModalPrsentationCurrentContext style is only available if you are compiling against iOS 3.2 or greater. Can't imagine that is the issue though.
The docs for UIModalPrsentationCurrentContext also say:
When presenting a view controller in a popover, this presentation style is supported only if the transition style is UIModalTransitionStyleCoverVertical. Attempting to use a different transition style triggers an exception. However, you may use other transition styles (except the partial curl transition) if the parent view controller is not in a popover.
This is a strange one.
Any chance you're running a different version of iOS on the iPhone and the iPad?
Noobie iOS developer with an adopted iOS app here.
I have a settings piece of an iOS app and when the user clicks done, I want the modal view controller (which it currently does) and I want to call a function called updateMainUI in the presentingViewController.
Here's the method I am calling:
- (void)done:(id)sender
{
[[self presentingViewController] dismissViewControllerAnimated:YES completion:nil]; // works
[[self presentingViewController updateMainUI]; //doesn't work
But I get the following error:
No visible #interface for 'UIViewController' declares the selector 'updateMainUI'
But I have declared this in the presenting controller
#interface LocationsViewController : UITableViewController <CLLocationManagerDelegate, UISearchBarDelegate, UISearchDisplayDelegate>
-(void)updateMainUI;
But it's odd that the error should be about UIViewController when the presenting controller is a UITableViewController.
Any ideas on how to get the updateMainUI working? And what I'm doing wrong?
thx
edit #1
This is how I'm creating this - any idea how to get a refernce to the *nearbyViewController?
UIViewController *nearbyViewController = [[LocationsViewController alloc] initWithNearbyLocations];
UINavigationController *nearbyNavigationController = [[UINavigationController alloc] initWithRootViewController:nearbyViewController];
...
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:nearbyNavigationController, browseNavigationController, eventsNavigationController, nil];
After dismissing ones self, you should expect self.presentingViewController to become nil. Try calling updateMainUI just before dismissing.
Crud, I see the real answer now. It's a casting problem. Try this:
[[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];
[(LocationsViewController *)[self presentingViewController] updateMainUI];
You need to be very certain that -presentingViewController returns an object of type LocationsViewController or you will have bigger problems on your hands.
[[self presentingViewController updateMainUI] is broken and won't compile. Assuming you meant [[self presentingViewController] updateMainUI], then there are other ways of doing it.
LocationsViewController *presenting = (LocationsViewController *)[self presentingViewController];
[presenting dismissViewControllerAnimated:YES completion:^{
[presenting updateMainUI];
}];
Using the variable presenting is an important step.