Calling a method in a View Controller via a TTURLAction? - iphone

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.

Related

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

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.

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.

Change the delegate of MGTwitterEngine

I have setup and successfully logged in via xAuth using an extended class of MGTwitterEngine, my question is if I want to pass this to another view controller, how can I change the delegate class, as it is some sort of weak reference
#interface MGTwitterEngine : NSObject <MGTwitterParserDelegate> {
__weak NSObject <MGTwitterEngineDelegate> *_delegate;
Am I best wrap this up into a singleton class and pass around that way, seems overkill to login in each time, or have I missed a painstakingly obvious way of sharing this object around
At the moment I have added a setDelegate method to the MGTwitterEngine but feel as though I am fighting the framework unnecessarily
If you're sharing the engine across multiple objects then you would want to have some other object/singleton wrap the engine and act as its sole delegate. If you've done database programming then think of it like a database connection -- you probably wouldn't have each view controller create its own database connection. Instead you'd create some sort of data manager object that is shared by the views and possibly abstracts away some of the DB internals.
If different view controllers handle different tasks -- like login, looking up users, querying messages, etc. then the delegate methods in your wrapper should be able to pass the responses along to the appropriate view controller.
If you have different view controllers calling the same methods (and if so, why?), you could still route responses back to the corresponding view controllers. As the MGTwitterEngine docs say, "Each Twitter API method returns an NSString which is a unique identifier for that connection." You would just need to pass an object (your view controller) or a block as an extra parameter to each of your wrapped methods. You can cache the twitter id string and this object/block in a mutable dictionary when your wrapper sends the response, then look up the connection id in the cache when it's time to handle the response.
actually, you can.
The delegate, is nothing but a variable in the MGTwitterEngine. Just add a instance of it in the next view controller adding the proper header and inplementation calls.
after instatiating the new view controller set:
nextViewController._mgTwitterEngine = self.mgTwitterEngine;
nextViewController.mgTwitterEngine.delegate=nextViewController;
then call the nextViewController.
Do not forget to set the delegate back to the original view controller when you return to it (either on viewDidAppear or viewWillAppear)
Hope that helps...
Best Of luck!
Use NSNotifications in the delegate.
Make the view controller where you wish the delegate to be add an observer. Have the delegate method for MGTwitterEngine post the notification.

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.

How do I access the managedObjectContext from a controller deep in the UI?

I'm still a little fuzzy on understanding iPhone/Cocoa in general so this is probably a simple question.
I have a CoreData Window-Based App for the iPhone. The rootController is a UITabBarController. The first tab view has a UINavigationController attached to it with a table in it's main view.
When the App starts the objectContext is set up, which makes sense to have the App do that once. But now I have the managedObjectContext in the main Controller but I want to get that passed down to the Controller of the View inside the navcontroller, inside the first item in the TabBarController's tab list. How do I do this?
Would naming the one of the fields in the UI Inspector Tool allow me to do something like:
tabcontroller.navcontroller.manageObjectContext = self.managedObjectContext;
Would this only work if the controller was instantiated and 'live'. (Do the controllers not get instantiated until they are needed?) What if this was in a view that was for the most part hidden?
Anyway this is probably a simple thing I'm just not understanding things properly yet.
What is the general right way to share the manageObjectContext that is created and setup in the rootController to the many sub-controllers in the app?
I'm guessing this is the preferred method assuming the core-data initialization is done in the AppDelegate:
[[[UIApplication sharedApplication] delegate] managedObjectContext]
I usually give controllers a - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context init method and a corresponding variable.
If a controller creates another controller in turn, it will, if needed, pass the NSManagedObjectContext to that controller in the same manner.
If you don't want to create an extra init method, just give the controllers a property for the NSManagedObjectContext and set that property directly after creating them.
I usually try to limit the number of controllers that directly deal with and "know about" Core Data though.
The answers to this question provide several means of accessing the Core Data stack deep within your application. As I indicate in one of the comments, I prefer to use a singleton DatabaseController that can be accessed from wherever, similar to how the NSUserDefaults' standardUserDefaults works.