I have a view controller,that is a UINavigationController, that at some point pushes (navigationcontroller pushViewController: animated:) a second view that later pushes a third view where I have a button that pops back to the root view (popToRootViewController: animated:). The problem is that after the view is poped back to the root one, the method viewWillApper of the root view is not being called. I've set some breakpoints to check it and it's just not passing through it. I have a method to reload some contents of my root view placed in the viewWillApper and its being completely passed by after the popToRootViewController: animated.
Any idea of what's going on?
Thanks
I used a delegate method to force the update of my view after popToRootViewController. My rootViewController called a network upload class and, on completion, I wanted to reset the form fields on the rootViewController.
In the network upload class, I created a delegate protocol:
#protocol MyNetworkDelegate <NSObject>
#required
- (void) uploadCompleted;
#end
#interface MyNetworkUploader : NSObject{
id <MyNetworkDelegate> _delegate;
}
#property (nonatomic,strong) id delegate;
//other properties here
+(id)sharedManager;
-(int)writeAssessments;
#end
In MyNetworkUploader.m:
-(int)writeAssessments{
//code here to do the actual upload
//.....
//this is a non-view class so I use a global navigation controller
//maybe not the best form but it works for me and I get the required
//behaviour
[globalNav popToRootViewControllerAnimated:NO];
[[globalNav.view viewWithTag:1] removeFromSuperview];
[_delegate uploadCompleted];
}
Then, in my rootViewController:
//my upload is done within a completion block so I know when
//it's finished
typedef void(^myCompletion)(BOOL);
-(void) uploadAssessment:(myCompletion) compblock{
//do the upload
sharedManager=[MyNetwork sharedManager]; //create my instance
sharedManager.delegate=self; //set my rootViewController as the network class delegate
int numWritten= [sharedManager writeAssessments];
compblock(YES);
}
#pragma mark - protocol delegate
-(void)uploadCompleted{
//this is a local method that clears the form
[self clearTapped:nil];
}
I'm NOT proposing that this is the best solution but it worked a treat for me!
When using a navController to push, assuming VC1 is pushing VC2 and you are using a custom presentation style to push your VC2, depending on which style you choose viewWillAppear of VC1 will not be called when VC2 is popped. Here is a list of when is called according to its presentation style.
UIModalPresentationStyle, iPhone, iPad
.fullScreen YES YES
.pageSheet YES NO
.formSheet YES NO
.currentContext YES YES
.custom NO NO
.overFullScreen NO NO
.overCurrentContext NO NO
.blurOverFullScreen only on tvOS - N/A, N/A
.popover YES NO
.none CRASH CRASH
reference found here
Related
I have a main view controller that is able to process gestures in an iPad app.
I launch a second view controller via:
wVC = [self.storyboard instantiateViewControllerWithIdentifier:#"vc_webView"];
[self presentViewController:wVC animated:YES completion:nil];
If I now gesture while that VC is showing, the gestures are not processed. How can I "pass" the gestures to the first storyboard for subsequent processing so that I don't need to rewrite the whole gesture functionality in the new VC?
OK so the answer is to use delegates.
For completion's sake, here is the way to do it:
Set up the first VC to be a delegate of the second, then in the second VC call the delegate function.
In original controller right before presenting the wVC:
wVC.delegate = self;
In the wVC.h file:
#protocol senddataProtocol <NSObject>
-(void)ProcessPasswordGesture:(NSInteger)iGest;
#end
#property(nonatomic,assign)id delegate;
In the wVC.m file:
#synthesize delegate;
[delegate ProcessPasswordGesture:<data>];
Hope this helps someone else!!
On iPad simulator, I have a ViewController A that presents an UIPopoverController whose contentViewController is ViewController B, inside which I have a button to dismiss the UIPopoverController.
When it is dismissed, I need to update the view of ViewController A based on some field in ViewController B.
In order to do this, I am declaring ViewController A as a property (weakref) of ViewController B so that within ViewController B where it dismisses the popover, I can say:
[self.viewControllerA.popover dismissPopoverAnimated:YES];
self.viewControllerA.popover = nil;
self.viewControllerA.textLabel.text = self.someField
Is this the correct way of doing it? Since there is no callback when we dismiss the popover pragmatically, I can't think of any better solution.
Anybody has a better idea? Passing view controllers around just seems awkward to me.
The best way is use of Delegation, just declare the delegate in your controller B like
#protocol ControllerSDelegate <NSObject>
-(void) hidePopoverDelegateMethod;
#end
and call this on action for passing the data and dismiss of controller like
if (_delegate != nil) {
[_delegate hidePopoverDelegateMethod];
}
and
in your controller A you can handle this delegate call
-(void) hidePopoverDelegateMethod {
[self.paymentPopover dismissPopoverAnimated:YES];
if (self.paymentPopover) {
self.paymentPopover = nil;
}
[self initializeData];
}
I think, delegates or sending NSNotification will make better.
Note:
A change of the execution sequence will do more perfection to your current code.
self.viewControllerA.textLabel.text = self.someField
[self.viewControllerA.popover dismissPopoverAnimated:YES];
self.viewControllerA.popover = nil;
I have the problem that many already have reported, didSelectViewController doesn't get called, but in my case it sometimes gets called. I have three tabs and three view controllers. Every time user presses second or third tab I need to execute some code. In my SecondViewController and ThirdViewController I have:
UITabBarController *tabBarController = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
[tabBarController setDelegate:self];
Now everything works fine with the SecondViewController, the didSelectViewController gets called every time the second tab is pressed. Also in ThirdViewController didSelectViewControllergets called every time the third tab is pressed but only when second bar is meanwhile not pressed. So when I switch back and forth between FirstViewController and ThirdViewController everything is OK. But when I go in a pattern like first->second->third, then didSelectViewController doesn't get called in ThirdViewController. Also when I go like first->third->second->third didSelectViewController gets called in ThirdViewController the first time but not the second time. Any ideas?
It's hard to follow what exactly you are doing, but from what I understand you are responding to tab switches by changing the UITabBarController's delegate back and forth between SecondViewController and ThirdViewController.
If that is true, I would advise against doing this. Instead I would suggest you try the following:
Assign a delegate that never changes. For a start you could use your app delegate, but it would probably be better if you had a dedicated small class for this. I am sure that now you have a non-changing delegate, it will get 100% of all the calls to tabBarController: didSelectViewController:.
The object that is the delegate must have a reference to both the SecondViewController and ThirdViewController instances. If you are designing your UI with Interface Builder, you might do this by adding two IBOutlets to the delegate class and connecting the appropriate instances to the outlets.
Now when the delegate receives tabBarController: didSelectViewController: it can simply forward the notification to either SecondViewController or ThirdViewController, depending on which of the tabs was selected.
A basic code example:
// TabBarControllerDelegate.h file
#interface TabBarControllerDelegate : NSObject <UITabBarControllerDelegate>
{
}
#property(nonatomic, retain) IBOutlet SecondViewController* secondViewController;
#property(nonatomic, retain) IBOutlet ThirdViewController* thirdViewController;
// TabBarControllerDelegate.m file
- (void) tabBarController:(UITabBarController*)tabBarController didSelectViewController:(UIViewController*)viewController
{
if (viewController == self.secondViewController)
[self.secondViewController doSomething];
else if (viewController == self.thirdViewController)
[self.thirdViewController doSomethingElse];
}
EDIT
Some hints on how to integrate the example code from above into your project:
Add an instance of TabBarControllerDelegate to the .xib file that also contains the TabBarController
Connect the delegate outlet of TabBarController' to the TabBarControllerDelegate instance
Connect the secondViewController outlet of TabBarControllerDelegate to the SecondViewController instance
Connect the thirdViewController outlet of TabBarControllerDelegate to the ThirdViewController instance
Add a method - (void) doSomething to SecondViewController
Add a method - (void) doSomethingElse to ThirdViewController
Make sure that you don't have any code left in SecondViewController and ThirdViewController changes the TabBarController delegate!
Once you are all set and everything is working fine, you will probably want to cleanup a bit:
Change the names of the notification methods doSomething and doSomethingElse to something more sensible
If you followed the discussion in the comments, maybe you also want to get rid of the secondViewController and thirdViewController outlets
I too had this problem and got fed up with it. I decided to subclass UITabBarController and override the following methods. The reason I did both was for some reason on application launch setSelectedViewController: wasn't being called.
- (void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
// my code
}
- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
// my code
}
I just dug through this tutorial on storyboards, and I thought of an alternative to using UITabBarControllerDelegate. If you want to stick to UITabBarControllerDelegate then feel free to ignore this answer.
First, create a subclass of UITabBarController, let's call it MyTabBarController. In the storyboard editor you need to change the "Class" property of the tab bar controller so that the storyboard picks up your new class.
Add this code to MyTabBarController.m
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SecondVC"])
{
SecondViewController* secondViewController = (SecondViewController*)segue.destinationViewController;
[secondViewController doSomething];
}
else if ([segue.identifier isEqualToString:#"ThirdVC"])
{
ThirdViewController* thirdViewController = (ThirdViewController*)segue.destinationViewController;
[thirdViewController doSomethingElse];
}
}
In the storyboard editor, you can now select the two segues that connect to SecondViewController and ThirdViewController and change the segue identifier to "SecondVC" and "ThirdVC", respectively.
If I am not mistaken, that's all you need to do.
ViewController
UIWebView: webView - a simply UIWebView
UIButton: aboutButton - takes you to the AboutViewController
AboutViewController
UIButton: websiteButton - connected to clickWebsiteButton
IBAction: clickWebsiteButton - dismiss AboutViewController, load http://websiteURL.com/ in webView (which is within the ViewController)
AboutViewController Code
// AboutViewController.h
#import "ViewController.h"
#class ViewController;
#interface AboutViewController : UITableViewController <UIWebViewDelegate> {
ViewController *viewController;
}
// AboutViewController.m
-(IBAction)clickWebsiteButton:(id)sender {
[viewController.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://websiteURL.com/"]]];
[self dismissModalViewControllerAnimated:YES];
}
Question
I want to be able to load http://websiteURL.com/ within the UIWebView upon dismissing the view via the IBAction. As of now, all it does is dismiss the view but not load the URL within the WebView. The WebView is working and loading URL's properly, I'm just having troubles loading this URL from a different view. Any ideas?
Thanks
I answered your other question regarding persistent data storage. That is a different way of getting your viewControllers to share data so you may no longer need this, but just in case...
The issue is that you are calling a method on your presenting viewController before you have dismissed the presented viewController (aboutViewController). It needs to be invoked after the dismiss process is complete.
This method:
dismissModalViewControllerAnimated:
Is deprecated in iOS6, and since iOS5 you are encouraged to use this instead
dismissViewControllerAnimated:completion:
where completion takes a block argument. Code you place in the completion block will execute after the dismissing is complete. You can send a message to the presenting viewController here.
self.presentingViewController is a reference to the viewController which presented aboutViewController - it is provided by iOS as part of the presenting process. But you can't use it in the completion block as it gets nulled during the dismiss process, so you need to copy it to a local variable first.
In aboutViewController...
-(IBAction)clickWebsiteButton:(id)sender
{
//to use self.presentingViewController in the completion block
//you must first copy it to a local variable
//as it is cleared by the dismissing process
UIViewController* presentingVC = self.presentingViewController;
[self.presentingViewController dismissViewControllerAnimated:YES
completion:
^{
if ([presentingVC respondsToSelector:#selector(loadRequestWithString:)]) {
[presentingVC performSelector:#selector(loadRequestWithString:)
withObject:#"http://websiteURL.com/"];
}
}];
}
In your presenting viewController, make a method to accept the string argument:
- (void) loadRequestWithString:(NSString*)webString
{
NSURL* requestURL = [NSURL URLWithString:webString];
[self.webView loadRequest:[NSURLRequest requestWithURL:requestURL]];
}
One option is to use a delegate callback. With your current code, the viewController instant is nil. I have an example how to implement a delegate pattern here.
Remember that if you are using UINavigationController you'll have to do
UINavigationController *viewConNav = (UINavigationController *)self.presentingViewController;
YourVC *viewCon = (YourVC *)viewConNav.topViewController;
I'm trying to implement an iBooks-like flip transition as a storyboard. The segue should push resp. pop the destinationViewController onto/from the UINavigationControllers stack.
I can push viewcontrollers in my segues perform method but I am not able to pop. When I pop the controller right after creating my flip animation the animation does not run and its callback - that should perform [[UIApplication sharedApplication] endIgnoringInteractionEvents] gets never called and my App results dead.
So I tried to push/pop in the animationDidStop:anim:flag delegate method but it never gets called with the flag set to true.
I assume that the segue is deallocated before the delegate method gets called. What else could I do?
Forgive me if I am completely misunderstanding this question, but it seems like you just want to do a basic horizontal flip back and forth between two view controllers. And even if you've already figured this out, maybe it will help anyone else who has the same question.
(1) In your storyboard (that has ViewController A & B) create a Modal Segue from A to B. Give it an identifier (showViewControllerB) and choose Transition:Flip Horizontal.
We set up the protocol and delegates:
(2a) In ViewControllerB.h add above #interface:
#class ViewControllerB;
#protocol ViewControllerBDelegate
- (void)viewControllerBDidFinish:(ViewControllerB *)controller;
#end
(2b) Add the delegate as a property:
#property (weak, nonatomic) id <ViewControllerBDelegate> delegate;
(3a) In ViewControllerB.m synthesize:
#synthesize delegate;
(3b) And delegate in the method to flip back:
- (IBAction)flipBack:(id)sender
{
[self.delegate viewControllerBDidFinish:self];
}
(4) In ViewControllerA.h add at the very top #import "ViewControllerB.h" and on the end of #interface <ViewControllerBDelegate>
(5a) In ViewControllerA.m add the method to conform to the protocol:
- (void)viewControllerBDidFinish:(ViewControllerB *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
(5b) Then set it as the delegate in prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showViewControllerB"]) {
[[segue destinationViewController] setDelegate:self];
}
}
I hope this answers your question. If I misunderstood, just let me know.
Your question is a bit confusing as so mingle pop, push, flip and backflip. I´m not sure if ai can answer your question, but i can tell what i did.
If i push a viewController into the navigation controller stack and set the Storyboard Segue Style to Push, it will be pushed into the view from right to left. A back button appears and shows the title of the presentingViewController in it.
If i set the Storyboard Segue Style to Modal i can set the Transition to Flip Horizontal (what seems to be what you want). But then no Back Button will appear. In the presentedViewController i dismiss the view with:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
It will flip the second view back with a right flip.
But this is the dirty solution and it is not recommended by apple.
http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ManagingDataFlowBetweenViewControllers/ManagingDataFlowBetweenViewControllers.html#//apple_ref/doc/uid/TP40007457-CH8-SW9
Luke Dubert gave you an example how to implement the delegate.