I have data being refreshed in a modalViewController and when that data gets refreshed, the parent controller needs to refresh its data as well. I tried doing a [tableView reloadData]; but it didn't work properly since the actual array values aren't being refreshed. Is there a way for me to reload a controller without the user seeing any animation?
Thanks for any help!
Are you refreshing the data off the main thread? If so, you need to call the reloadData method using the following method:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
So for a tableView it would be something like:
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
The underlying view can get released while the modal view is showing, so you shouldn't try to modify it directly. Instead, you can use the viewWillAppear: method on the main view controller to make any necessary updates.
I'm not sure what you mean that "the actual array values aren't being refreshed". How the two view controllers exchange data depends a lot on what you are trying to accomplish. If the modal view is only used in one place, the main controller can access its properties directly. Or maybe you want a dedicated class to hold the state... Again, depends on the context.
Related
i initialize tables, data etc in my main ViewController. For more settings, i want to call another Viewcontroller with:
DateChangeController *theController = [self.storyboard instantiateViewControllerWithIdentifier:#"dateChangeController"];
[self presentViewController:theController animated:YES completion:^{NSLog(#"View controller presented.");}];
And some clicks later i return with a segue (custom:)
NSLog(#"Scroll to Ticket");
[self.sourceViewController presentModalViewController:self.destinationViewController animated:YES];
My Problem:
After returning to my main ViewController, viewDidLoad is called (everytime).I read, that the ARC releasing my mainView after "going" to the other ViewController and calling the viewDidUnload Method, but i want to keep all my data and tables i initialize at the beginning..
Any solution? Thank you very much!
The problem is that you are doing this:
main view controller ->
date change controller ->
a *different* main view controller
In other words, although in your verbal description you use the words "returning to my main ViewController", you are not in fact returning; you are moving forward to yet another instance of this main view controller every time, piling up all these view controllers on top of one another.
The way to return to an existing view controller is not to make a segue but to return! The return from presentViewController is dismissViewController. You do not use a segue for that; you just call dismissViewController. (Okay, in iOS 6 you can in fact use a segue, but it is a very special and rather complicated kind of segue called an Unwind or Exit segue.)
If you do that, you'll be back at your old view controller, which was sitting there all along waiting for your return, and viewDidLoad will not be called.
So, this was a good question for you to ask, because the double call of viewDidLoad was a sign to you that your architecture was all wrong.
I think you're taking the wrong approach - viewDidLoad is supposed to be called when it is called - it's a notification to you that the view is being refreshed or initially loaded. What you want to do is move that table initialization code somewhere else, or, at least, set a Boolean variable so that it is only called once. Would it work to create an object that has your table data when viewDidLoad is first called, then to check it to see if it's already been called?
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.
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.
I have an app which is targeted to iOS 4 and above.I have a custom view, which is having
1.Scroll view
2.Custom drawing inside scroll view.
Now when my app is resuming from background,I want to refresh/reload this scroll view.
setNeedsDisplay is not working here as it usually meant for loading custom drawings and here I want to reload my scroll view contents.
Thanks in advance.
If I understand your question correctly, setNeedsDisplay does not work for you in this case because you need to "reload" the data that the UIScrollView handles before doing the redraw.
If this is right, I would define a method in your controller that does the reloading part; then, I would call this method from applicationDidEnterForeground or applicationWillEnterForeground; the reload method will need to call setNeedsDisplay after reloading the data.
I am sorry if this answer seems very generic to you. If you explain more or post some code, I can try and be more specific.
Have you tried redraw inside viewDidAppear?
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[yourScrollView setNeedsDisplay];
}
setNeedDisplay calls the drawRect which is repainting operation. If you want to refresh the content only simply create one method in viewController and notify this method using performSelectorOnMainThread if you created new thread whenever thread complete (or you receive Data).
I have a UITableview that I load with data async so the tableview might appear without data.
I have tired the ReloadData method but the tableview remains empty until I scroll the tableview, suddenly the data appears.
The same thing happens when I load a tableview as a detailedview and switching between items, the previoud items data appears first and as soon as I scroll in the table view it shows the correct data.
My guess is that the ReloadData method works just fine, but I need to redraw the tableview somehow, any suggestions on how to solve this?
/Jimmy
You said you're populating content asynchronously but did you invoke the reloadData in the context of the main thread ? (and not via the thread that populates the content)
Objective-C
[yourUITableView performSelectorOnMainThread:#selector(reloadData)
withObject:nil
waitUntilDone:NO];
Swift
dispatch_async(dispatch_get_main_queue(), { self.tableView.reloadData() })
Monotouch
InvokeOnMainThread(() => this.TableView.ReloadData());
Yonels answer is perfect when your view is currently visible to the user (e.g: User presses a reload button which populates your UITableView.)
However, if your data is loaded asynchronously and your UITableView is not visible during the update (e.g: You add Data to your UITableView in another View and the UITableView is displayed later by userinput), simply override the UITableViewController's viewWillAppear method.
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
The positive effect is that your UITableView only reloads it's data once when the user actually want's to see it, not when new items are added.
I had the similar issue today(Storyboard in iOS 7.0).
I was using table View inside a UIViewController.When the view was loaded everything was working fine; all the delegates were getting called.However ; When the underlying dataSource(in my case,Array) was modified; the visible table rows were not getting updated.They were updated; only when I was scrolling the table view.
Tried everything; calling reloadData on Main thread;calling reload in ViewWillAppear; nothing worked.
The issue that I found ; was that I had not made the connection in storyboard; for the table view with the reference Outlet.The dataSource and delegate were set though.
I did not think that it could be the issue; as everything was working fine at the first go.
Hope it helps someone.I had some terrible time to find this out.
I guess it wasn't reloaded.
When you scroll the cells to out of screen, then…
tableView: cellForRowAtIndexPath:
…will be called. So it will be reloaded.
I guess UITableView variable is not validated.
If you use UITableView as a main view, you can try this.
[self.view reloadData];
or
[self.tableView reloadData];
Swift 4:
DispatchQueue.main.async { self.tableView.reloadData() }
After somewhat naively copying in yonel's solution and calling it good I realized that calling performSelectorOnMainThread:withObject:waitUntilDone: fixed the symptom, but not the problem. The bigger problem is that you are making UI updates while still in the context of the asynchronous or background thread.
This is what my code looked like:
dispatch_queue_t queue = dispatch_queue_create("com.kyleclegg.myqueue", NULL);
dispatch_async(queue, ^{
// Make API call
// Retrieve data, parse JSON, update local properties
// Make a call to reload table data
});
When it should look like this:
dispatch_queue_t queue = dispatch_queue_create("com.kyleclegg.myqueue", NULL);
dispatch_async(queue, ^{
// Make API call
// Retrieve data, parse JSON, update local properties
dispatch_async(dispatch_get_main_queue(), ^{
// Now make the call to reload data and make any other UI updates
[self.tableView reloadData]
});
});
If the only thing you need to do is call [self.tableView reloadData] it's probably fine to use performSelectorOnMainThread:withObject:waitUntilDone: since it accomplishes the same goal, but you should also recognize what's happening in the big picture. Also if you are doing more UI work than just reloading the table then all of that code should go on the main queue as well.
Reference: A concise example of using GCD and managing the background vs. main thread.