I can't find a good solution to having the UIPageViewController preload the surrounding ViewControllers.
Right now what happens is when the user starts to turn the page, nothing happens, the code calls
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
and once its loaded thennnn it shows the animation. This takes too long and is not smooth.
Anyone have a solution?
Create a mutable container and when you show the first view, create the two viewController that would be needed if the user pages, once you have the viewController object ask it for its view (to get it to load the nib and call the controller's "viewDidLoad:" method. You need to figure out a system on identifying the viewControlers so you can retrieve the viewController you need). You might be able to do the heavy lifting here in a block on a dispatch_queue.
From then on, when the user pages, you look first in the container for the viewController, and if not found, you have to do it in real time.
You probably want to use a dispatch_group so that you can wait on it for pending blocks to finish before paging.
Everytime the user pages, you will look and see if the pages surrounding that page are in the container or not. You could also pre-fetch more viewControllers - like two forward two reverse at each page shown.
Related
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 2 views controlled by a tab bar controller.
The issue that I am having is that on returning to my 1st tab from my 2nd, I ideally need all of the 1st view's data to have reloaded based on the changes I made in the 2nd tab.
It is possibly better not to explain why, as my fumbled attempts at over-complicated code with numerous work-arounds will likely alarm you.
So basically, can i get viewDidLoad to redo itself when i go back to the view from another tab?
EDIT: and the answer is: viewWillAppear instead of viewDidLoad i think (can't answer own question for 8 hours)
Rather than reload every time on viewDidAppear, in most cases you're better off sending a message to view 1 telling it to reload next time it appears. This avoids unnecessary reloads (which can be bad for user experience and use unnecessary bandwidth). There are a number of ways to do this depending on the screen flow in your app. One way to do it would be:
-create an ivar BOOL shouldReload on view 1
-whenever something happens on view 2 (or anywhere else in the application) that requires a reload of view 1 post a message to [NSNotificationCenter defaultCenter] called something like "View1ShouldReload".
-on load of view 1 set the shouldReload flag to NO and start listening
-on the notification selector, set shouldReload to YES
-on viewDidAppear check if (shouldReload) then do the reload.
There are many other ways to do this (like delegates, singletons, whatever), but this is a simple way to make your app work more efficiently.
I'm doing an app that uses a TabBarController and each Tab uses its own navigation controller.
The app has dynamic content and I use viewDidDisappear viewDidAppear methods to create or destroy the objects that I need each time I enter or exit into the ViewController.
My problem is when I start to sail very fast and I don't give time to load the Threads that I use for uploading content such as XML peta app or destroy objects when I leave the ViewController.
How I could control the tabs of the navigationbar or tabbarviewcontroller for not respond until the viewcontroller has loaded all contents?
Excuse me if I'm not well expressed. Thanks!
No matter you use synchronous request or asynchronous request, just show an UIAlertView while loading the data. This will both serve as a notification to the user that something is being loaded, and the it will block the interactions with all the other views on the screen.
As others have suggested in comments, I believe that what you want to do is rearrange the order in which things are triggered. Perhaps something like this:
On viewWillAppear:, clear (or disable or whatever is appropriate) your objects that are no longer valid and begin the load-new-content thread. Perhaps display a UIActivityIndicator or similar.
On viewWillDisappear:, tell the load-new-content thread that it can stop, its results are no longer needed. If you put up an activity indicator, take it down.
At the end of the load-new-content thread, take down any activity indicator, update the UI with the new contents and activate.
I don't really see any way around this -- if the UI is not valid until the new content is loaded, then you have to wait for it.
Another solution might be to cache the contents from the previous fetch, and always display those on viewDidLoad. Then, at the end of your new-content-thread, cache the new contents, and update the UI.
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.
I have a UIViewController subclass that loads a bunch of images for each cell in a tableview asynchronously which is handled by a separate download class. I keep a list of all of these download requests in a dictionary which is keyed to the index of the cell that is requesting the image.
My question is i where should i put the code that cancels the image download if the viewcontroller is popped off the navcontroller? I need to do this because if the user hits back while there are still images being downloaded (which could take a while) then when they are finished downloading the viewcontroller has already been released.
I cant put it in the viewWillDisappear method because i do not want to stop the download if the user clicks on a separate tab and only when the hit the back button. For now i put this code in the viewcontrollers dealloc method which works fine although it doesnt seem right for some reason. I thought of using the viewDidUnload method but it seems this is only called when there is a low memory warning?
Any ideas?
dealloc is the perfect place for this as the view controller gets deallocated and you are responsible to clean your stuff up.
You may want to consider putting the code in viewWillDisappear: anyway, after all if the screen you are navigating to needs anything loaded it will be slowed by the background image load...