On a button click, I am calling a web service and after that I am pushing a UIViewController. My UIViewController should load with the data obtained from the web service. But currently, before my web service is called, the UIViewController is being pushed. What can I do to make sure that my UIViewController is not loaded before all the web service calls are made and data retrieved.
Here is the code I am using.
MyWebService *webservice = [MyWebService myWebService];
webservice.delegate = self;
[webservice getMyDataWithMyNumber:mySharedNumber myOldNumber:temp];
[webservice getvDetailsWithmyData:myData myNumber:myNumber];
MyViewController *myViewController = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController: myViewController animated:YES];
[myViewController release];
Edit: The UIViewController should be pushed only after both web services are called.
I am passing an array to the new UIViewController. The array objects are added during the web service call. I cant figure out a way to do this. Need help. Thanks.
Your web service needs to call back to the view controller when it has completed.
This is usually done using a Delegate pattern, but there are other techniques you could use.
Your first view controller would pass itself as a delegate to MyWebService. MyWebService does what it needs to do, and when it is done it calls a method on its delegate, the view controller.
In this callback method, you could then push the next view controller.
You should also consider the user experience with this. A user want's a responsive device, or at least some indication something is happening. So when calling the web service, show a loading indicator. Alternatively, push the next view controller immediately, and then call the web service from the next view controllers viewWillAppear method (again show some sort of loading feedback).
.. I just re-read and noticed there is more to it. You have multiple separate web service calls. Are those 2 always called together? You could use a bool flag on return of each one, and only push if both have returned. I'd rather push the new view controller straight away, load them both and let them return independently to the new view controller.
UPDATED WITH EXAMPLE
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]
MyWebService *webservice = [MyWebService myWebService];
webservice.delegate = self;
[webservice getMyDataWithMyNumber:mySharedNumber myOldNumber:temp];
[webservice getvDetailsWithmyData:myData SmartJoinderNumber:myNumber];
//assume internally these web service calls aggregate into one response
}
- (void) myWebService:(MyWebService *)webService didRespondWith:(NSData *)data {
MyViewController *myViewController = [[MyViewController alloc]initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController: myViewController animated:YES];
[myViewController release];
}
In your case, Apple has recommended to use delegate pattern.
Here's an answer about delegate, that might help you to understand delegate concepts:
http://stackoverflow.com/questions/1089737/parsing-xml-in-cocoa/1090170#1090170
Related
So, I understand the basic example of delegates. What I have is this:
WebService (class to handle grabbing web data)
HomeViewController (home screen)
ProgressViewController (shows the progress of a long download modally for long downloads)
OtherViewController (another view controller that might make a quick network request)
Scenario 1: So from the home screen, they can make a download where we would then ask the web service to get the data, and show the progress if it's a long download.
Scenario 2: OtherViewController might need some simple information from the internet. It asks the web service for that data, and updates that view.
Currently, everything is handled with NSNotifications.
Scenario 1 with NSNotification: home screen presents modal view controller, adds the ProgressViewController as a listener to the webservice, ProgressViewController updates its screen when needed.
Scenario 2 with NSNotification: other view controller gets registered as an observer of the web service in viewDidLoad, gets the callbacks when needed from the web service.
I was wondering if and how I could set this up through delegation. I thought it might be better to have a WebServiceDelegate that could implement methods like:
- (void)webService:(WebService *)webService didUpdateProgress:(double)progress;
The problem I see with this is, if my web service starts a request to download some large amount of data, currently, the home screen view controller will do:
ProgressViewController *pvc = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
to present the view controller, and then it listens for the progress updates.
I don't see how I would do it through delegation since I don't know where I would set the delegate property. In the WebService, I need to do something like:
self.WebServiceDelegate = progressViewController;
However, the progressViewController doesn't get created in the web service. It gets created on the homeViewController. The only thing I have come up with so far is do something like:
ProgressViewController *pvc = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
pvc.progressViewControllerDelegate = [WebServiceManager sharedInstance];
self.webServiceManagerProgressDelegate = pvc;
NSDictionary *progressViewDict = #{ #"ProgressViewController" : pvc };
[[NSNotificationCenter defaultCenter] postNotificationName:WebServiceShowProgressViewNotification object:self userInfo:progressViewDict];
Where the web service knows that it's supposed to show this view controller, posts the notification for that, and then whoever (in my case the home view) was listening, can show the progressViewController, and then the progressViewController can show the progress and respond to web service delegate methods. It seems kind of roundabout and I didn't know if there was a better way to do this, or just stick with notifications. Thanks!
If I understand your situation correctly, homeViewController has a reference to the web service, and then homeViewController creates an instance of ProgressViewController. Then ProgressViewController needs to get updates from that web service using delegation. You could try something like this as part of HomeViewController:
ProgressViewController *pvc = ProgressViewController *pvc = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
self.webService.delegate = pvc;
Of course, this requires homeViewController to have a reference to WebService (I called this webService).
Where the web service knows that it's supposed to show this view controller
It really shouldn't know about any of that. It just needs to do its thing (download, upload, whatever), and if there's something to report, it does so by sending its delegate a message. That delegate (whoever that may be), will then update views accordingly.
EDIT: I just realized that WebService is a singleton. I believe the preferred approach for singletons is actually using notifications. Since all view controllers can access a singleton, delegation isn't always an option as all these view controllers may need an update on what's going on, while the object can only have one delegate. If only one or two view controllers use the WebService singleton, you should probably not make it a singleton.
I have an iPhone application that uses a UITabBarController for its primary interface. The application also makes heavy use of modal UINavigationControllers presented from the various tabs.
There is a ViewController that I need to present modally which can be triggered from a wide variety of locations within the application. It seems like a terrible idea to duplicate the code which creates and presents it between all the viewControllers that trigger it. I would like to have this code in a single place and trigger it from whichever viewController wants to present it.
Where should this centralised location be? My root ViewController is a UITabBarController so no good there, and I hate lumping view functionality into the AppDelegate.
I would create a new class file that has a function to present the view you want. That way you only need to write the code once to present the modal view and each view that needs to use it can call the function on the helper class with one line of code.
#interface ApplicationHelper : NSObject {
}
+(void)showMyModalView:(UIViewController *)parentViewController;
#end
Implementation:
#import "ApplicationHelper.h"
#import "ViewController.h"
#implementation ApplicationHelper
+(void)showMyModalView:(UIViewController *)parentViewController
{
ViewController *vc = [[ViewController alloc] init];
[parentViewController presentModalViewController:vc animated:YES];
}
#end
Then in each view controller import the ApplicationHelper and call the showMyModalView method
[ApplicationHelper showMyModalView:me];
This allows you to keep all the view handling code in a separate file to your application delegate.
Don't try and pass the reference around.
Just alloc] init]; a fresh instance where-ever you need to use it.
For example.
MyModalViewController *controller = [[MyModalViewController alloc] init];
[self presentModalViewController:controller animated:YES];
You can create one UIViewController and implement some delegate method for it. Then you can present the view controller like this:
[currentViewController presentViewController:yourViewController animated:YES];
And when an event is fired in your view controller, it will delegate to the caller.
In this case, you write the code only one time for "YourViewController", then reuse it where ever you want. You can also use pushviewcontroller also:
[self.navigationController pushviewcontroller:yourViewController animated:YES];
I think this is a pretty common usecase as I have seen it in several apps. But after spending couple of days, I am still struggling with this. I have a structure like the following:
UITabBarController
-- UINavigationController1
---- UITableViewController1
-- UINavigationController2
---- UITableViewController2
Now I have a logout button on UITableViewController2. When I click on that logout button I want all and any viewcontroller to be deallocated, all view unloaded. Basically start fresh like launching the app. I basically want the viewDidLoad on each of those UITableViewController called again.
I tried the following method to be called in my appdelegate when the logout action on UITableViewController2 is taken.
-(void) logout {
for (UINavigationController* ctrl in self.tabBarController.viewControllers) {
[ctrl popToRootViewControllerAnimated:NO];
ctrl.visibleViewController.view = nil;
}
[self.tabBarController.view removeFromSuperview];
[self.window addSubview:self.tabBarController.view];
}
But alas, it does not seem to work?
Any ideas how such a thing is accomplished? Also I see different behaviors in iOS4 vs iOS5 with the visibleViewController. I am not using any modal viewcontroller here. Any gotchas?
Update: I am not using ARC
thanks
mbh
Your for-loop will release and thus dealloc any view controllers that you have pushed onto the respective UINavigationController roots (depending on how many tabs you have), i.e. as these will not have a superview when you pop back to the root of each navigation controller, these are dealloc-ed automatically. These are your UITableViewControllers taken care of.
As for the respective UINavigationControllers, you would need your tabbar-controller to release the old instance. IMHO, this should be done for you when you release the UITabBarController.
This then leaves the UITabBarController itself. I don't think it can be done tbh. Your code will only remove the view, but not dealloc the tabbar controller itself. And as Krishna K points out, you need at least one view controller to reload all others.
Putting the code into the appdelegate makes sense, but you need to ensure that your logout() will not cause a retain on the UITableViewController2 as well as the UITabbarController as it's called from UITableViewController2 somewhere.
One idea to explore, does your AppDelegate hold an instance to the TabBar-Controller which you could release and create a new instance after removing the view from self.window?
// manually create UITabBarController - AppDelegate holds instance
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
}
- (void) logout {
[self.tabBarController.view removeFromSuperview];
[mytabcontroller release];
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
[self.window addSubview:self.tabBarController.view];
}
But as I said, there might be caveats with memory management at this point.
You need to release your view controllers. When their release method is called, that method should include statements to release all of its object's resources (and also dealloc its superclass).
Your rootViewController for both Navigation controllers are their respective TableView controllers. So I don't think popToRootViewController would do anything.
You probably need to reset the data and refresh the views instead of deallocating the views.
I am able to pass a variable forward from view controller to view controller by pushing its view controller onto the navigation stack. An example of how I do it would be this:
MyViewController *controller = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
controller.myString = stringToPass;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
However, what do I do if I want to pass a variable BACK UP the navigation stack? Using popViewControllerAnimated rather than pushViewController does not pass the variable up like I thought it would.
I need to be able to access the variable several pops up from the view controller it is defined in.
Any help will be greatly appreciated :)
You're passing values, not variables.
A view controller should not be responsible for popping itself. With Apple's view controllers (e.g. UIImagePicker), it is the parent view controller's responsibility to do the popping; the parent VC can also obtain the current value. (Not entirely correct; it might access the value before a keyboard autocompletion is applied)
Alternatively, if it's a value that can be shared globally, you can store it in your application delegate.
You can get a hold of the navigation controller in the VC stack using self.navigationController. You can just call some method like
[self.navigationController setMyString:stringToPassUp];
There are several more ways, e.g. self.tabBarController for the tabbarcontroller up in the stack, or most simply
[self.parentViewController setMyString:stringToPassUp];
edit given the downvotes on the examples above, and nobody giving a better explanation, let's discuss the proper way to do this.
If you have some object (like your MyViewController *controller) and that object has something to tell you, the usual approach is this:
MyViewController gets a delegate property, of type (id)
the view controller instantiating the MyViewController, sets this delegate property, like so:
controller.delegate = self;
MyViewController will, when it has something to say, do something like:
[self.delegate delegateMessage:arg1]; to "pass the message up" as you put it.
To do it perfectly, you may want to create your own #protocol MyViewControllerDelegate, and declare the class which would be setting controller.delegate = self; to adopt this protocol, by putting <MyViewControllerDelegate> on the #interface line. The delegate property of MyViewController should then be declared id<MyViewControllerDelegate> delegate;, so that the [self.delegate ...] messages can be matched to the protocol specification.
Basically the whole Cocoa Touch API works like this. Just have a look around for ideas how to implement your interaction.
I'm trying to change view upon receive a push notification while the app is still running. I tried using this in the AppDelegate.m
-(void)application:(UIApplication *)application didRecieveNotification:(NSDictionary *)userInfo
{
TestClass *aTestClassViewController = [[TestClass alloc]initWithNibName:#"TestClass" bundle:nil];
[self presentModalViewController:aTestClassViewController animated:YES];
[aTestClassViewController release];
}
But it didn't work. I can't even start up the app again. so I'm guessing this is the wrong way to do it.
Any idea guys? I would appreciate it.
Solved***
I did it this way ->
I showed an alert view first (Which i needed anyways)
then used the method of
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
TestClass *aSelectionScreenViewController = [[TestClass alloc] initWithNibName:#"TestClass" bundle:nil];
[viewController presentModalViewController:aSelectionScreenViewController animated: YES];
[aSelectionScreenViewController release]; }
We're missing some context about your application, but your basic problem is that it's the application delegate object which is receiving the notification, not a view controller. That's why you can't just do [self presentModalViewController:someViewController];
I think it's the snippet from your own answer that gives what you need: your app delegate (presumably) has a 'viewController' member, which is the root view controller for the application. It is that viewController object you need to prod into doing whatever it is you need. In the app I'm looking at right now, I have a tabBarController member in the app delegate, and I show an alert view and/or change the selected tab index when a notification comes in.
I would have your app delegate call a function on your main view controller when a message comes in, and have that function show the alert view, then do whatever state changes you need to do to make the main view controller reflect the received notification.