Refreshing the content of TabView - iphone

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.

Related

How to switch view controller under this circumstance

I have a view controller that I need to refresh it self so, I basically reload it with the following code.
-(void)check{
GameController*myNewVC = [[GameController alloc] init];
[self presentModalViewController:myNewVC animated:NO];
}
I can call the method above in gamecontroller and it works fine, but in a button sub class I use the method below and it doesn't work because nothing happens.
.h
#interface CellButton : UIButton {
}
.m
GameController*myNewVC = [[GameController alloc] init];
[myNewVC check];
What can I do to get this working?
I have a view controller that I need to refresh it self so, I basically reload it
Don't do that. Your view controller isn't refreshing itself, it's replacing itself, and it's hard to think of a reason that it should need to do that.
Put the code the loads the data in a separate method, and call that method on the existing view controller instead of creating a whole new view controller. For example, many view controllers that manage a UITableView will call the table's -reloadData method to tell the table to discard any cells that are currently visible and request new ones. No matter what kind of view(s) your view controller manages, you can do something similar.
I can call the method above in gamecontroller and it works fine, but
in a button sub class I use the method below and it doesn't work
because nothing happens.
That's most likely because you say you're using the code in a UIButton subclass, and the code says:
[self presentModalViewController:myNewVC animated:NO];
So, the button is telling itself to present the view controller. However, UIButton doesn't have a presentModalViewController:animated: method. I'm surprised that "nothing happens" -- I'd expect an exception due to the unimplemented method. It should work fine if you replace self above with a pointer to your view controller. Or, much better, put the code in an IBAction method in the view controller, set the buttons action to that method, and its target to the view controller.
(from your comment...)
There is a function in the button class that will dictate weather or
not the view controller will refresh it self.
That sounds like a poor plan -- in a well designed MVC application, logic that controls whether the view controller will refresh belongs in the view controller. Have the view controller enable/disable or show/hide the button based on whatever conditions control the refreshing behavior.

Reloading a table's data

I have 5 view controllers, which are in a navigation controller hierarchy. When I reach my last view controller, there's a button that lets me go back to the "Home" view controller (named CardWalletViewController) which is a table view controller. Here's the method I have in my last VC (PointsResultsVC)
- (IBAction)homePressed:(id)sender {
NSArray *VCs = [self.navigationController viewControllers];
[self.navigationController popToViewController:[VCs objectAtIndex:0] animated:YES];
}
CardWalletVC's cells is being filled up by values coming from the saved instances of a Card in NSUserDefaults and it works fine.
Now, what I want is to update the value in my CardWalletViewController, which is the points of a card coming from my PointsResultsVC. Note that this points is saved in NSUserDefaults.
Upon the process of trying to update of the value shown in my CardWalletVC, I placed [self.tableView reloadData]; inside -viewDidLoad, -viewWillAppear, and -viewDidAppear of the said class. I tried placing it one by one in each of this methods, yet it doesn't seem to work.
Advice Please.
EDIT: problem solved
This one served as my guide Save data from one tab and reloadData in another tab.
And as I have discovered, -viewDidLoad of a certain class will only be called once, all throughout runtime of app. Whereas -viewWillAppear, it will be called every time the view appears.
So, I just moved the way I am loading the values saved in NSUserDefaults from -viewDidLoad to -viewWillAppear. Also, inside -viewWillAppear I placed the [self.tableView reloadData]. Then there, problem solved.
There's a lot of things that could be going wrong so you'll need to provide more info to narrow it down.
First of all, viewDidLoad will not be called so you can remove it from here. Both viewWillAppear and viewDidAppear will be called, so you only need it one of these 2 methods.
Next, you need to determine what the actual issue is.
1 - is self.tableView a proper reference to the table? Are you properly maintaining the reference?
2 - is the table actually reloading but using the old data, or is it not reloading at all? You can log the willDisplayCell method to see what is going on when the view appears.
With that info, the root cause can be narrowed down to a solvable problem.

How do I implement an automatic jump to a detailed page if the user was on this previously (or fix my code for doing this which has a design flaw)

Any advice on how to fix this issue I have, or a better implementation design perhaps?
Requirement
Needed a way for the application at start up to take the user to the previous details page, if this was what they were on prior to quiting the application in their last session
If they were on the main screen of the app, then at restart they can stay here
The assumption is I'm working with UINavigationController and the main screen and details screen are built on a UITableViewController
My Implementation Concept
Put a check in "viewdidLoad" to see whether they were on a detailed screen, and then if so jump to this (refer to code below)
Issue
Works fine normally, however when I trigger a memory warning things screw up and I get nav bar weird behavior. For example I see the main page nav buttons, when it looks like I'm on the detail page content (UITableView)
My Analysis
From what I see when I am on the details page (appointmentsListController) and cause a memory warning in the simulator I see:
(a) the main page "viewDidLoad" actually gets called, which my concept didn't expect, so whilst I had hit the BACK button from the detailed view (UINavigationController) to go to the main view (RootViewController), in fact my code is run and it try's to throw the user back to the details page again
(b) I note in my logs after this point that [AppointmentListController viewDidLoad] seems to get called before the previous AppointmentListController dealloc method is called (i.e. like I was in controller A, went back to controller B, but got thrown back to A - and the initial dealloc for the first part didn't kick in until late...)
So I guess it's obvious my idea isn't too great
Question
Any suggestions on how to better implement my requirement? (how to check, which method to put them in)
Code
- (void)viewDidLoad {
[super viewDidLoad];
// My Implementation of the Requirements which seems flawed in the case there is memory warning triggered
if ( previousSelectedScreen >= 0 ) {
// Setup New Controller
AppointmentListController *appointmentListController = [[AppointmentListController alloc] initWithNibName:#"AppointmentListController" bundle:nil];
appointmentListController.screenToShow = previousSelectedScreen;
// Push new view onto stack
[[self navigationController] pushViewController:appointmentListController animated:NO];
[appointmentListController release];
}
}
Here's what I'd suggest: rather than having this logic in your view controller, but it in your application delegate. By constructing your navigation stack before displaying it you will hopefully avoid some of the weird things that can happen with nav bars, etc. To get rid of the memory warnings you may need to look at how your app allocates memory: it may not necessarily be to do with this.
Anyway - in your application delegate you can perform your check to see if the user was on a detail page when they exited. If they are, you can create an array containing the navigation stack (ie, Main Screen -> Details Page). You can then pass this into a navigation controller using its setViewControllers method. Once this is done, you can display your window and finish launching the app.

iPhone/UIViewController: when navigating away from a view, what is the best way to handle data entries

I should know this by now, but I am still a bit confused. When my app navigates from one view controller to the next (via the navigation controller) I want to "finalize" the data for the current VC before going to the next VC. The only way I see to intercept the "page swap" is in the [old view viewWillDisappear] -> [newView viewWillAppear] transition. It seems weird, though I guess it works okay.
Is that really the right way to handle navigation transitions? My app is a bunch of VCs which collectively build a database file. Each VC deals with a different aspect of the data.
I don't know your exact setup, so this may not be useful to you, but I have good experiences with saving data in -(void)textFieldDidEndEditing:(UITextField*)tf, using tf.tag to index the fields. From there I commit the data to a storage class, and have no worries about what happens in the UI.
What exactly is involved in the "finalizing" part? I assume you are storing some state in the view controller for various fields and then you want to write that to the database file before going on to the next view?
When it comes to "edit view controllers" I find a nice way to do it is to have the view controller directly write to a simple model object which is injected through a property before pushing it to the nav controller.
So something like:
/* Somewhere in the app delegate, like application:didFinishLaunching */
DatabaseFileModel *model = ...;
viewController1.model = model;
viewController2.model = model;
/* ... */
[self.window makeKeyAndVisible];
Then each view controller writes to this model by setting properties etc. when a text field ends editing or whatever. Having the view controller write straight to the object means you don't need to handle viewWillDisappear etc.
If you do still need to do this however you can add a delegate to the navigation controller and handle these two methods:
– navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
See the UINavigationControllerDelegate documentation for more information.
This will let you keep the logic in one place rather than spread out in each view controller.

viewDidAppear called twice on the same instance, but only the first time this class loads form NIB

I have a navigation controller. One of the views adds custom subviews in its viewDidAppear:. I notice that the first time I navigate to an instance of this view controller after launching the app, viewDidAppear: invokes twice. If I pop this view off the stack and navigate to it again, viewDidAppear: invokes only once per appearance. All subsequent appearances invoke viewDidAppear: once.
The problem for me is that the first time I get to this view I end up with twice the number of subviews. I work around this problem by introducing a flag variable or some such, but I'd like to understand what is happening and how come I get two invocations in these circumstances.
You should never rely on -viewWillAppear:/-viewDidAppear: being called appropriately balanced with the disappear variants. While the system view controllers will do the best they can to always bracket the calls properly, I don't know if they ever guarantee it, and certainly when using custom view controllers you can find situations where these can be called multiple times.
In short, your -viewWillAppear:/-viewDidAppear: methods should be idempotent, meaning if -viewDidAppear: is called twice in a row on your controller, it should behave properly. If you want to load custom views, you may want to do that in -viewDidLoad instead and then simply put the on-screen (if they aren't already) in -viewDidAppear:.
You could also put a breakpoint in your -viewDidAppear: method to see why it's being called twice the first time it shows up.
maybe you invoke viewDidAppear in viewDidLoad (or some other stuff is going on there), since it's invoked only once during loading the view from the memory. It would match, that it's invoked two times only the first time.
This was not an iOS 5 bug, but a hidden behavior of addChildViewController:. I should file a radar for lack of documentation, I think
https://github.com/defagos/CoconutKit/issues/4
If you have a line like this in your AppDelegate
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
make sure you DON'T have a "Main nib file base name" property in your plist set to "Window.xib" or whatever your custom window nib is named. If you do, remove that row from your plist and make sure you something like
yourRootVC = [[UIViewController alloc] init];
[window setRootViewController:yourRootVC];
in your AppDelegate after instantiating your window. In most cases, you could then safely delete the Window.xib as well.
You definitely should provide more info.
Is this the root view controller?
Maybe you initiate the navigation controller with this root view controller and then push it to the navigation controller once again?
Another solution that may have been your underlying cause: Be sure you are not presenting any new view controllers from within your viewWillAppear: method.
I was calling:
[appDel.window.rootViewController presentViewController:login animated:YES completion:nil];
from within viewWillAppear and seeing my originating VC's viewDidAppear: method called twice successively with the same stack trace as you mention. And no intermediary call to viewDidDisappear:
Moving presentViewController: to the originating VC's viewDidAppear: method cleared up the double-call issue, and so now the flow is:
Original viewDidAppear: called
Call presentViewController here
Original viewDidDisappear: called
New view is presented and no longer gives me a warning about "unbalanced VC display"
Fixed with help from this answer: https://stackoverflow.com/a/13315360/1143123 while trying to resolve "Unbalanced calls to begin/end appearance transitions for ..."
it's such an annoying problem, you'd think it runs once but then I now found out about this which is causing mayhem... This applies to all 3 (ViewDidAppear, ViewDidLoad, and ViewWillAppear), I am getting this when integrating with a payment terminal; once it finish calling the API, the window is being re-loaded when it's already on-screen and all it's memory is still there (not retained).
I resolved it by doing the following to all the routines mentioned above, below is a sample to one of them:
BOOL viewDidLoadProcessed = false;
-(void)viewDidLoad:(BOOL)animated
{
if (!viewDidLoadProcessed)
{
viewDidLoadProcessed = YES;
.
.
.
... do stuff here...
.
.
}
}
Repeat the above for all the other two, this prevents it from running twice. This never occurred before Steve Jobs died !!!
Kind Regards
Heider Sati
Adding [super viewDidAppear:animated]; worked for me:
//Called twice
- (void)viewDidAppear:(BOOL)animated{
}
//Called once
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}