Delegation, how to set up hierarchy with a non-viewController - iphone

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.

Related

Refreshing the content of TabView

Ok I am trying to refresh the tab content of each of my tabs after a web call has been made, and I have tried soo many different methods to do this that I have lost count. Could someone please tell me how this is possible?
The web call just calls JSON from a server and uses it to update the content of the tabs. For testing purposes I have a button set up inside my settings class. Settings class is a view within the home tab which has a button called refresh. When clicked this takes JSON stored on the device which is different to the one called from the web call on application start up. This saves me having to change the JSON on the server.
I will take you through some of the techniques I have tried and would be grateful if someone could tell me what I am doing wrong.
I tried making an instance of the class and calling the refresh method like this
DashboardVC *db = [[DashboardVC alloc] init];
[db refreshMe];
The refresh method in dashboard class is this
-(void) refreshMe
{
[self loadView];
[self viewDidLoad];
}
However no luck. This method will work if I call it inside the Dashboard class, but wont work if I call it from another class. I think it is become I am instantiating a new class and calling refresh on that. So I dropped that technique and moved onto the next method
This loops through all the tabBars and changes the tabTitles without any issues, so it I know it is definitely looping through the ViewControllers properly.
I also tried every varient of the view methods like ViewDidAppear, viewWillAppear etc, no luck.
I also tried accessing the refreshMe method I made in the dashBoard class through the tabController like this
[[[self.tabBarController viewControllers] objectAtIndex:0] refreshMe];
But again no luck, this just causes my application to crash.
I read through this guide
https://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewControllerPGforiOSLegacy/TabBarControllers/TabBarControllers.html
on the apple website but it doesn't seem to cover how to refresh individual tab content.
All I want is to have each individual tab refresh its content after the web call is made, and have spent ages trying to figure this out, but nothing is working.
So would be very grateful if someone could show me what I am doing wrong?
Thanx in advance....
EDIT:
Expand on what I have tried
After discussion with Michael I realised you should never call loadView as against Apple guidelines. So I removed any references to LoadView. I have now placed a method in all the main ViewControllers called RefreshMe which sets up the views, images texts etc in the class. And this method is placed inside the ViewDidLoad. Now I want to be able to call these methods after a web call has taken place, so effectively refreshing the application.
My viewDidLoad now looks like this in all my the main classes.
- (void) viewDidLoad {
[super viewDidLoad];
[self refreshMe];
}
And then the refreshMe method contains the code which sets up the screen.
The JSON data pulled from the web call will set up the content of each of the 5 tabs, so need them all to be refreshed after web call.
I tried looping through the viewControllers and calling viewDidLoad, which should in turn call the refreshMe method which sets up the class, however nothing happens. Code I used was this
NSArray * tabBarViewControllers = [self.tabBarController viewControllers];
for(UIViewController * viewController in tabBarViewControllers)
{
[viewController viewDidLoad];
}
For the time being I have also included
NSLog(#"Method called");
in the viewDidLoad of each class to test if it is being called. However the message is only being printed out when I first load the application or if I re-enter the application. This method should be called after I click the refresh button in the settings screen but it isn't and I have no idea why.
Anyone have any idea why this is not working?
From the question and your comments, it sounds like there are at least two problems:
You're having trouble accessing the view controllers managed by your app's tab bar controller.
You seem to be working against the normal operation of your view controllers.
The first part should be straightforward to sort out. If you have a pointer to an object, you can send messages to that object. If the corresponding method doesn't execute, then either the pointer doesn't point where you think it does or the object doesn't have the method that you think it does. Let's look at your code:
NSArray * tabBarViewControllers = [self.tabBarController viewControllers];
for(UIViewController * viewController in tabBarViewControllers)
{
[viewController viewDidLoad];
}
This code is supposed to call -viewDidLoad on each of the view controllers managed by some tab bar controller. Leaving aside the wisdom of doing that for a moment, we can say that this code should work as expected if self.tabBarController points to the object that you think it does. You don't say where this code exists in your app -- is it part of your app delegate, part of one of the view controllers managed by the tab bar controller in question, or somewhere else? Use the debugger to step through the code. After the first line, does tabBarViewControllers contain an array of view controllers? Is the number of view controllers correct, and are they of the expected types? If the -viewDidLoad methods for your view controllers aren't being called, it's a good bet that the answer is "no," so figure out why self.tabBarController isn't what you think.
Now, it's definitely worth pointing out (as Michael did) that you shouldn't be calling -viewDidLoad in the first place. The view controller will send that method to itself after it has created its view (either loaded it from a .xib/storyboard file or created it programmatically). If you call -viewDidLoad yourself, it'll either run before the view has been created or it'll run a second time, and neither of those is helpful.
Also, it doesn't make much sense to try to "refresh" each view controller's view preemptively. If your app is retrieving some data from a web service (or anywhere else), it should use the resulting data to update its model, i.e. the data objects that the app manages. When a view controller is selected, the tab bar controller will present its view and the view controller's -viewWillAppear method will be called just before the view is displayed. Use that method to grab the data you need from the model and update the view. Doing it this way, you know that:
the view controller's view will have already been created
the data displayed in the view will be up to date, even if one of the other view controllers modified the data
you'll never spend time updating views that the user may never look at
Similarly, if the user can make any changes to the displayed data, you should ensure that you update the model either when the changes are made or else in your view controller's -viewWillDisappear method so that the next view controller will have correct data to work with.
Instead of refreshing your view controllers when updating your tab bar ordering, why not simply refresh your views right before they will appear by implementing your subclassed UIViewController's viewWillAppear: method?
What this means is that each time your view is about to appear, you can update the view for new & updated content.

Execute a piece of code only after all web services are called

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

Calling a method in a View Controller via a TTURLAction?

I've created a Download Manager which is pretty much a TTTableViewController and it's accompanying DataModel is a pretty much an ASINetworkQueue.
So, when I've created a function in my view controller that in turns call my [dataModel addDownloadWithNameAndUrl: url:] and I get a new download added to the NetworkQueue, which starts, and is being displayed on the TTTableViewController and updates progress bars and the rest.
I've also mapped the DownloadManagerViewController class to a URL, and I can use the TTNavigator to open the download manager.
Now for my question. Is there a way I can call the url to execute the requested function without switching to the View Controller? How could I accomplish some
Have you tried looking in the samples/TTNavigatorDemo project? It has a lot of URL mapping examples.
Try using the [map from:<#(NSString *)#> toObject:<#(id)#> selector:<#(SEL)#>] function, such as:
[map from:#"tt://order/send" toObject:_controller selector:#selector(sendOrder)];
If I find myself needing the controller as an object, I initialize the object in the app delegate instead, such as:
_playerController = [[PlayerController alloc] init];
[map from:#"radio://player/(initWithStation:)" toViewController:_playerController transition:UIViewAnimationTransitionNone];
Well as a matter of fact it appears to be rather simple although it took me a while to figure it out.
Thee20 Navigator has method:
- (id) viewControllerForURL: (NSString *) url;
By using that, throughout the application you can get an instance of your view controller that you have assigned to the url. However the trick is that if you have set the url as a
[map #"url" toViewController: <class>];
each time you get a new instance of the View Controller.
In my case however, I wanted to use the already instantiated view controller so I need to setup the url as a sharedViewController.
So I've added a new method to my view controller which now I can call without switching the UI to it.

Reloading A View iPhone

So I have two views A and B. A is a profile view, B is a login view. A loads B in the ViewDidLoad Method using
LoginViewController *lvc = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:[NSBundle mainBundle]]; //make new instance of LoginViewController
[self presentModalViewController:lvc animated:NO]; //present the LoginViewController
[lvc release];
in the login View, if the login is successful, the view is removed
[self dismissModalViewControllerAnimated:YES];
On the login view, It downloads some data which I want to display on the profile view. How would I go about sending the data to the profile view and displaying it in the xib. I believe the profile view is already displayed but is just hidden.
This is a basic "communicate between two classes" question. There are many ways to do this, but here are three. I only wrote sample code for delegation (because I think that's probably the best choice in your situation), but let me know if you want examples of notifications or KVO.
Delegation Implement a delegation or callback method in Class A. Delegation is best for small class hierarchies. If Class A is the only class that will load B and A is the only class who cares what happens in B, then delegation is the easiest way to move data around. It's simple to implement, it's simple to understand and there's a clear relationship between the classes.
// Class A
- (void)displayLoginViewController {
LoginViewController *lvc = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:[NSBundle mainBundle]];
lvc.delegate = self;
[self presentModalViewController:lvc animated:NO]; //present the LoginViewController
[lvc release];
}
- (void)loginViewControllerWasSuccessfull:(LoginViewController *)loginViewController {
// Do whatever you need to do here
[self dismissModalViewControllerAnimated:YES];
}
In the login view controller do something like this in the header:
#property (assign) NSObject delegate; // declared assign so you don't have circular references
… and this in the implementation:
- (void)didLogin {
[self.delegate loginViewControllerWasSuccessfull:self];
}
Notification Class A will register to listen for login notifications. Class B will post login notifications. Notifications are best if the classes that care about login are distributed. i.e. there are many classes that care about a login event and they may not necessarily have a direct relationship with the class that is performing the login.
Key Value Observing KVO is best if you don't particularly care about the login event, you care about the changes to the data. You will have some class that manages your data, probably an NSManagedObject if you are using Core Data. Class A will observe changes to whatever property it's interested in. Your LoginViewController will update that data class when it is finished downloading data. Class A will be notified that the data has changed.
Whatever solution you decide to use, the choice ultimately comes down to asking, "What does Class A care about?". Does Class A need to know that Class B successfully logged in? Use delegation. Does Class A need to know that somewhere, some class logged in? Use notifications. Does Class A not care about logins, it only needs to know if data has changed? Use KVO.
You Load view A after downloading the data instead of ViewDidLoad.
when u click on the login button then download data and display it.
if your viewWillAppear is not calling then create nsnotification center object and post it when you want to call your view willAppear method.and then remove this notification.
you can store the downloaded data at delegate file in login view. And in viewWillAppear method of profile view use data from the delegate....
for that you have to create variable and set its property in .h and .m file .than you can set this variable value in login screen and it will synthesize to profile screen.
Another way
you have to create variable in appDalegate . appDalegate value set in login screen and use this value in profile screen
If I understand correctly, you are trying to do the equivalent of Android's Intents. Therefore I advise using iOS's NSNotificationCenter and send NSNotifications with associated data.

iPhone: Using a Singleton with Tabview Controller and Navigation Controller

I have developed a small iPhone application by using singleton that I use to navigate through the views. Here is a sample method from my singleton class.
+ (void) loadMenuController:(NSMutableArray *)menuItems{
MenuViewController *menuViewControler = [[MenuViewController alloc] initWithNibName:#"MenuViewController" bundle:nil];
[menuViewControler setMenuItems:menuItems];
RootViewController *root = (
P2MAppDelegate *appDelegate = (P2MAppDelegate*) [[UIApplication sharedApplication] delegate];
UINavigationController *navController = [appDelegate navigationController];
[navController pushViewController:menuViewControler animated:YES];
[menuViewControler release];
}
Now my requirement has changed to require a tab view controller . I could change my application delegate to a tabview controller but I still need to navigate inside each tab. I am unable get a clue how to navigate from my singleton class.
Please guide me. Please let me know if my query is not clear.
Thanks in advance.
Regards,
Malleswar
You shouldn't be using a singleton to manage the interface and even if you did, you wouldn't put the UI logic in a class method. You need to rethink your design from scratch.
The normal pattern is to hold the navigation controller or the tabbar controller as an attribute of the application delegate. The app delegate itself should not be a subclass of any controller but just a NSObject subclass that implements the application delegate protocol.
Look at the Apple supplied template projects in Xcode to see the quick and dirty way to structure apps built around navigation and/or tabs.
Singletons should only be used when you have to ensure that one and only one instance of class is alive at one time. You don't need to make your own singleton to manage the UI. The application delegate is attached to the application object which is itself a singleton. This means the app delegate provides all the restriction on class for the UI you might need. You don't need another singleton in addition to that.
Overuse of singletons is dangerous and can cause your design to get trapped in a dead end resulting in a massive rewrite. Think carefully before employing them.