Iphone Access rootviewcontroller from subviews - iphone

I have a navigation based application, from any of my subUIviewcontrollers when they click a button on the toolbar, I want to send rootviewcontroller the checked rowId or entered text. So I want to manage everything from Rootviewcontroller(e.g which page to show next)
But this code below does not hit the answeredValues in my rootcontroller, even gives a warning that it might not respond. why is that? and if there is a better way to this things like from delegete class?
in interface
-(void)answerValues:(NSMutableArray*)values;
in implementation
-(void) answerValues:(NSMutableArray*)values {
//get answer value
//edit insert xml with new answer
//make connection
//Get XML
//Parse and get the last page of questions
//Return a variable object filled with question and answers
}
RootViewController *root = (RootViewController*)[self.navigationController.viewControllers objectAtIndex:0] ;
[root answerValues:values];

As I commented above, you mispelled the name of the method. In the original question, you called a method named answeredValues, but in your updated question your method is named answerValues.

Related

Design pattern for UI controls' state management in iOS

Similar to this question, but I am looking for a generic solution or design pattern or framework.
Q. How to add state management into all UI controls in my iOS app automatically without the need to rewrite the existing controls' class?
Example:
e.g. When I click on a UIButton, it will create a new UIWebView showing Google home page. That is easy, but problem arise when user sometimes.. click the button just too fast, so two webview will be displayed.
To solve this question, I would need to make a singleton class which contain the webview, and have a state variable isOpended and if it is true, reuse the existing webview instead of creating a new one.
But the problem is: If I want this behavior in other controls also, then I would need to create many many singleton classes..I am just thinking if there is better way to handle this without the new to re-invent the wheel.
Thanks.
I think you're solving the wrong problem here. Why don't you disable the button until the UIWebView is done processing. That way the user cannot click it twice.
- (IBAction)showMapHomepage:(UIButton*)sender
{
sender.enabled = NO;
[self taskThatTakesALongTimeWithCompletion:^{
sender.enabled = YES;
// Finish processing
}];
}
You are misinterpreting the best way to go about solving your problem. First of all, you should never find yourself in a situation where you are creating many many singletons. Singletons are a necessary evil, but you should not overuse nor abuse them. Here is a good post about singletons in objective-c.
There are numerous ways you could go about preventing a second UIWebView from being displayed when the user clicks your button.
As someone else stated, one solution would be to disable the button so that the user cannot "double-click" it. You do this using:
button.enabled = NO;
You could also hide your button using:
button.hidden = YES;
Or, in the header of the class that contains your UIButton, you could create a boolean that will handle the logic of whether or not the button has been pressed;
// declare this in your header
BOOL buttonPressed;
// this is the IBAction that your button hooks up to
- (IBAction)createWebViewButtonPressed:(id)sender {
if(!buttonPressed) {
buttonPressed = YES;
// insert code here to create your UIWebView
}
}
Again, there are numerous ways to accomplish what you are trying to do. You just have to determine which method is the best for you.
I agree with other answers that you should probably disable the control if you don't want it to be activated twice. However, if you do want an answer for your actual question about a generic pattern that you can use on all controls then you can use associated objects...
- (IBAction)buttonAction:(UIButton*)sender
{
NSString* webViewKey = #"AssociatedWebView";
// See if there is web view already
id webView = objc_getAssociatedObject(sender, webViewKey);
if(webView == nil)
{
// There is no existing web view, create it
webView = [self theWebView];
// Associate it with the button
objc_setAssociatedObject(sender, webViewKey, webView, OBJC_ASSOCIATION_RETAIN);
// Add the web view
[self.view addSubview:webView];
}
}
The above shows a generic way to associate an object to an instance of UIButton so you can check if it is already associated and re-use the existing one. I provide this answer in case you intend to use this in some other way that isn't fully described in your question, but in practice, you could use a property of your controller for the webView that lazy-loads the webView if it isn't already loaded.
If you really want to simulate the singleton style that you discuss in your question (so that you can have many UIButton instances that all share the same webView object if it already exists) then you could associate the webView onto the [UIButton class] object or even the [UIControl class] object instead of your specific instance. You would do that by replacing the sender with [UIControl class] in the code above.
One possible solution is to store a pointer to the webview in a property of the viewController. In the getter for the webview, create the webview if it doesn't exist already. The button action just need to display the webview, as it will just redisplay the webview if it already exist, and it will create the webview if it doesn't. When you are done with the webview, just set it to nil.

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.

Adding Objects to persistent store from user input

Ok, lemme start out by saying Im new to this! LOL I have done my due diligence of studying the topics (4 books and numerous videos to date) and searching for hours on end, and still havent found an answer.
I feel like I have a solid understanding of Core Data, or at least the back end DB side of it. I have my app built and I have my model built. My app has a Tabbar controller as well as a navigation controller for each separate tab.
My app will have an item table view which populates the name of those items from Core Data. Upon selecting the item, the navController pops to a detail view which loads the rest of the data for that item.
When a user clicks + to add an item, I need to pop to another View Controller with fields to add the name and details (which it does). However, I cant seem to get these details to save. I think I need to cast the user inputs as an NSSet, then bring that NSSet into the persistent store, but the method declaration for this is eluding me! Currently, my code looks like so...
- (IBAction) save:(id)sender {
NSLog(#"Save pressed");
if (itemName != nil) {
[itemName removeObject:itemName];
self.item = nil; //This will release our reference also
}
//Create a new item set for the new values
NSSet* newItem = [[NSSet alloc] initWithSet:newItem];
[self didChangeValueForKey:#"itemName"];
[self didChangeValueForKey:#"detailItem1"];
[self didChangeValueForKey:#"detailItem2"];
//Add it to the master item array and release our reference
[itemArray addObject:newItem];
[newItem release];
//Sort the array since the name might have changed with an existing item or a new one
NSSortDescriptor *nameSorter = [[NSSortDescriptor alloc] initWithKey:#"itemName" ascending:YES selector:nil];
[itemArray sortUsingDescriptors:[NSArray arrayWithObject:nameSorter]];
NSLog(#"Array sorter");
[nameSorter release];
//then pop the detailed view controller
[self dismissModalViewControllerAnimated:YES];
}
All of the documentation I have found on Core Data points more in the direction of populating an already existing database, not accepting user inputs. So if I am WAY off in my approach and the answer is more than just a simple one, please point me in the right direction!!
Also, Ive added items to my Core Data store that successfully persist. However, an sqlite DB hasn't been created in my app, which I thought happened automatically. So I may have more problems than I thought!
So far I have found this site to be a tremendous help, even though my reputation doesnt allow me to rate answers!
Anyway, thanks in advance for the help.
In most Core Data implementations, you don't deal with sets directly unless you are simultaneously adding multiple managed objects to a relationship. I'm not really sure what you are trying to do here.
In the code shown, you don't do anything related to Core Data. You do not have a context and you do not insert into the context a new managed object that you can populate with your new data. In fact, you don't seem to have any managed objects at all.
I suggest you look at the Navigation based project template in Xcode. It shows how to set up the Core Data stack and how to add and remove objects displayed in a tableview.

Getting a variable into another class through properties or button tags with Iphone SDK?

I have been pounding my head against a wall trying to figure out what I think is a simple thing to do, but I have, so far, been unable to figure it out with the iPhone SDK. I have a view with 4 buttons. When I push the buttons, I have another view called and come up to take over the screen. I want to have a simple variable passed from the old view to the new view. The purpose of this is to know what button was pressed out of the four identical ones. (trying to get an int value of 1 through 4) Here is what I have tried so far:
I initially tried to call the variable from the class itself. My understanding was that my new view was only sitting on top of the old view, so it should not have been released. I could not get access to the variable as the variable was said to not be declared.
I then tried to create a method that would return the int value. Again, the view class was not seen and was being declared as a "first use" of it.
I then attempted to follow another similar post on here that suggested to try and send the button tag number to some part of the new view. I attempted to do this by setting the new views button title but I still cannot figure out a way to code it without coming up with errors.
I am just looking for what method would be the best for me to pursue. I have no problem going back and reading the books on Objective-C, but I'm just trying to figure out which way I should concentrate on. Thank you for any insight.
Something like this should work:
button.tag = <whatever the tag value should be>;
[button addTarget: self selector: #selector(onTouchDown:) forControlEvents: UIControlEventTouchDown];
- (void) onTouchDown: (id) sender
{
int tag = ((UIView*)sender).tag;
UIViewController *vc = [[MyVC alloc] initWithID: tag];
// display the view for the vc
}
Incidentally, I find NSNotification's very useful for passing results back to a parent controller.

Can't understand iPhone view layout

I have an iphone app that I built based off a tutorial (for a different framework so I had to modify things a bit) that uses a TabBar and a NavigationBar on the same View that also contains a UITable populated from an SQLite db.
I built it last night and when you select an item in the UITable it was redirecting to a view that displayed the detail of my item (at that point, just a city name). I went in and tried to modify that DetailView...and nothing I change works.
So I have a few questions/problems:
1) Why is it that my CityDetailView.xib looks like this in Interface Builder:
alt text http://www.jamespwright.com/images/public/detailviewinterfacebuilder.jpg
But looks like this when the app is run:
alt text http://www.jamespwright.com/images/public/wrongdetailview.jpg
I am new to iPhone development, so I'm not even sure where to begin looking for this problem.
I know that the TableViewController is set to CitiesTableViewController and within that controller code in my didSelectRowAtIndexPath I run this code:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
City *city = (City *)[appDelegate.cities objectAtIndex:indexPath.row];
// Initialize a new CityDetailView if there isn't one already
if (self.cityView == nil) {
CityDetailViewController *viewController = [[CityDetailViewController alloc] initWithNibName:#"CityDetailViewController" bundle:[NSBundle mainBundle]];
self.cityView = viewController;
[viewController release];
}
[appDelegate.citiesNavController pushViewController:cityView animated:YES];
self.cityView.city = city;
self.cityView.title = city.name;
[self.cityView.cityName setText:city.name];
[self.cityView.stateName setText:city.state];
[self.cityView.population setText:city.population];
And I have the labels linked up in Interface Builder to the properties in the Controller.
I also have the CityDetailView.xib class identity set to CityDetailViewController which has all the properties correctly declared.
One thing I can't figure out, if I rename CityDetailView.xib to "CityDetailView.xib1" the program still runs the same, but if I change [self.cityView.title = city.name]; to [self.cityView.title = #"Bob's Your Uncle!"]; it displays that change within the program.
On a completely unrelated note, on my TableView (my 2nd tab) I have this odd bar at the bottom that I don't know what it is or how to get rid of it, I'm pretty sure it has to do with the NavigationController but I don't know why or how it's there. It is this one:
alt text http://www.jamespwright.com/images/public/tabandnavigationview.jpg
Can anyone offer me any advice on these problems?
When "nothing happens" when you change source files, there are usually only a few things you're doing:
Dates are screwed up somewhere and you need to clean (Build → Clean)
You're editing the wrong file
You're building the wrong target
No, seriously, you're editing the wrong file, or building the wrong target
Cleaning is definitely the first thing to try. Especially if you can rename the XIB and everything still cheerfully goes on.
The simplest explanation is that self.cityView.stateName and the subsequent labels are not hooked up in interface builder. The controller must have a hooked up outlet to each label in order to populate it. Objective-C won't raise an error if you send a message to an object it can't find.