Pre-loading pages with a `UIPageViewController`, including on the first page turn - ios

When swiping between pages, a UIPageViewController nicely makes sure the next/previous content pages are loaded and ready to go. This is great, but there's a catch: it never does this on the first page turn after a call to setViewControllers (see below). I suspect this may be an efficiency saving where it's reasoned a common case is where a user never swipes. Either way, I'm not taking this as a bug.
So I can (almost) resign to a noticeable delay on the first page turn. After this, everything should be smooth. However my problem is that in one scenario I often make further calls to setViewControllers dynamically depending on user input, and so in this case pre-loading is effectively broken.
What I would like is an API to tell UIPageViewController to always pre-load neighbouring pages - I know a user will always swipe. Failing that, my best workaround is this:
let page = getContentPage()
myPageViewController.setViewControllers([page], direction: .forward,
animated: true, completion: nil)
// Here we must manually pre-load
let nextPage = pageViewController(myPageViewController, viewControllerAfter: page)
nextPage?.loadViewIfNeeded() // 1.
nextPage?.view.layoutIfNeeded() // 2.
This half-works: I now get pre-loading of the view (1.) which saves noticeable time.
My problem is Auto Layout. It's evident that UIPageViewController's pre-loading also nicely handles layout in advance, whereas my manual approach still has a perceptible delay immediately on a page turn. Adding layoutIfNeeded in (2.) does indeed prompt a layout but I still observe a further call on the page turn regardless (to be clear: this only happens for my manual approach on an as-yet unseen page). Worse, when I call (2.) pages don't layout correctly - e.g. UILabels don't properly wrap their text content.
How can I either effectively emulate or prompt a UIPageViewController to always pre-load neighbouring pages, including on the first page turn?

Related

When WKWebView displays content, it will be white for about 1~2 second

When WKWebView displays content, it will be white for about 1~2 second. Not a memory problem, I use [self loadHTMLString:string baseURL:baseURL]; show the HTML content, but each time need to wait 1~2 second will be normal display. UIWebview does not have this problem.
Is there any way to solve it? Or optimize this blank time?
Sometimes it takes a while to load a page over the network. The page is white until the data can be loaded. A simple solution is to issue your load() command early. Before it needs to be called. I anticipate the next page, but don't display it until it is needed. When it is needed, it is already loaded, I just need to add it:
viewController.view.addSubview(webView)
I use two different methods, depending on my needs: I either load the view early and change the alpha to 0 so it is invisible:
webView.alpha = 0
Of course, I change it to 1.0 when I want it to display.
Or I remove the web view from the superview (it needs to be a class property so it is retained), like this:
func removeWebView(_: UITapGestureRecognizer) {
print("removewebview tap")
webView.removeFromSuperview()
}
If you follow this approach, you will need to call a method that adds the subview (web view) back, and if you are using auto layout, you need to reapply constraints because they are lost when you remove the web view from the superview. If you do that, don't forget to call .setNeedsLayout() on the parent view.

PageViewController scroll bug (over scrolls)

I was following the tutorial here and had to make a few changes to fit my project (code of my variation here, sorry I put it on GitHub because it's a bit lengthy to copy and paste). Sometimes when I swipe right to get to the last VC, it automatically closes on sight. Other times it works properly where if I swipe to the last VC it stays there until I swipe right again to close it. I've placed print statements in the code to help me find where the bug is. Here are the print statements when it works and when it doesn't work. Looking at the logs, since the exact same code is accessed in the same order, I'm not sure how to fix this bug. This problem doesn't occur when the transition style is PageCurl instead of Scroll.
First you need to know how pageViewControllers work.
After the first page is loaded, the moment you start to scroll it will try to load the next page. this is just for the first page. after that as soon as you reach a page it will preload the next page (depending on the direction) and have it ready.
In your case when you reach page 5 the pageViewController will try to preload the next page which leads to this part of your code
guard orderedViewControllersCount != nextIndex else {
print("last item in coachamrks going to dismiss")
self.dismissViewControllerAnimated(true, completion: nil)
return orderedViewControllers.first
}
and it will dismiss the viewController as soon as you reach page 5.
This is not a bug. It's the default behavior of pageViewController.
I think the reason that you sometimes find it behaving the way you wrongly expected is that when you scroll through the pages fast enough the pageViewController stops preloading pages for performance reasons.
The only way you could achieve what you want is a hackish way of accessing the scrollView inside of the pageViewController but it is not safe and might break your app if Apple changes the architecture of pageViewControllers in a future update.
I suggest you put a button in the last page and use it to dismiss the pageViewController.

Unintentional lag from when program executes .addSubview to when view actually appears- Swift

I am building a fairly simple iOS app that, when it boots up, loads a few views and stacks them on top of each other.
However I am experiencing a very long lag between when the app boots and when the views appear. These views are not complicated and take very little time to build.
At first I thought it might have been the image causing the delay, as I grab it via a URL. But after logging some checkpoints, the image load happens fairly quickly.
I did similar checkpoint logging when builidng the UIView and again, it's fast.
So finally, I checked to see if there was a gap between when the call to .addSubview is made and when the views actually appear, and in fact there is.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let returned = feedMngr.retrieveFeed()
if (returned) {
self.constructFeedStack()
self.view.addSubview(self.firstCardView!)
self.view.insertSubview(self.secondCardView!, belowSubview: self.firstCardView!)
self.view.insertSubview(self.thirdCardView!, belowSubview: self.secondCardView!)
self.view.insertSubview(self.fourthCardView!, belowSubview: self.thirdCardView!)
NSLog("fin")
}
}
After "fin" is logged (roughly 5 seconds after app launch), there is a 5-10 second lag before the views are actually presented.
Anyone know why this may be the case?
You're modifying the view hierarchy off of the main queue ("in the background"). This isn't allowed. Your UIKit calls all need to be on the main queue.

How does UIAutomation determine whether a UIAElement.isVisible()

I have a view with the following structure:
GrandView
-Parent View1
-Parent View2
--Child View1
--Child View2
The child views take up almost all of the bounds of Parent View2, but there is still some space around the edges. I can select Parent View2 in the simulator with the accessibility inspector if I click on the edges. I can also tap Parent View2 in UIAutomation if I use:
tapWithOptions({tapOffset:{x:0.15, y:0.95}});
However, my calls to isVisible() always return 0. I expect that if I can tap the element, or select it with the accessibility inspector, it should return 1.
How does UIAutomation determine whether a UIAElement is visible?
Every operation you perform against an element has a timeout. This isn't obvious from the Apple documentation, but if you look at: setTimeout , it tells us:
The timeout value establishes a grace period for object resolution. If an object representing a UI element becomes available within the grace period, an attempt is made to instantiate that object from information retained by the instrument.
setTimeout itself just changes the default value (as do push and pop). What you really want to do is perform your action on your view, and fail on the timeout if it never becomes available (the default timeout is 5 seconds). The WWDC 2010 session "Automating User Interface Testing with Instruments" does go into this a little, it's available on the ADC WWDC 2010 page, with both video and slides. In your case, you'd want to execute the tap() on your view. If, for some reason, that view isn't available to UIAutomation within 5 seconds, you should see an exception. Experiment with changing the timeout by doing:
var oldTimeout = target.timeout();
target.pushTimeout(10);
before your code, and
target.popTimeout(oldTimeout);
after.
If it's a UIView, it should be driven the the hidden property. If it's not a view, and it's a container, it should be driven by accessibilityElementsHidden .
In general though, you don't want to use this for UIAutomation. Instead, whatever you were going to do on the view - in this case, a tap() - go ahead and do it, and let the system throw an error if it times out. In general this is the model you want to follow in your scripts rather than testing whether something is available first. Sine UIAutomation is DOM scripting the UIAccessibility information, when things like animated view transitions happen things get out of sync. Sometimes the script executes faster than the UI animates, and sometimes the opposite! waitForInvalid may be a shortcut to a solution for you.
Alex Vollmer's tuneup.js library for UIAutomation makes writing tests much easier, and is easy to extend.
https://github.com/alexvollmer/tuneup_js

UIViewController viewDidLoad vs. viewWillAppear: What is the proper division of labor?

I have always been a bit unclear on the type of tasks that should be assigned to viewDidLoad vs. viewWillAppear: in a UIViewController subclass.
e.g. I am doing an app where I have a UIViewController subclass hitting a server, getting data, feeding it to a view and then displaying that view. What are the pros and cons of doing this in viewDidLoad vs. viewWillAppear?
viewDidLoad is things you have to do once. viewWillAppear gets called every time the view appears. You should do things that you only have to do once in viewDidLoad - like setting your UILabel texts. However, you may want to modify a specific part of the view every time the user gets to view it, e.g. the iPod application scrolls the lyrics back to the top every time you go to the "Now Playing" view.
However, when you are loading things from a server, you also have to think about latency. If you pack all of your network communication into viewDidLoad or viewWillAppear, they will be executed before the user gets to see the view - possibly resulting a short freeze of your app. It may be good idea to first show the user an unpopulated view with an activity indicator of some sort. When you are done with your networking, which may take a second or two (or may even fail - who knows?), you can populate the view with your data. Good examples on how this could be done can be seen in various twitter clients. For example, when you view the author detail page in Twitterrific, the view only says "Loading..." until the network queries have completed.
It's important to note that using viewDidLoad for positioning is a bit risky and should be avoided since the bounds are not set. this may cause unexpected results (I had a variety of issues...)
This post describes quite well the different methods and what happens in each of them.
currently for one-time init and positioning I'm thinking of using viewDidAppear with a flag, if anyone has any other recommendation please let me know.
Initially used only ViewDidLoad with tableView. On testing with loss of Wifi, by setting device to airplane mode, realized that the table did not refresh with return of Wifi. In fact, there appears to be no way to refresh tableView on the device even by hitting the home button with background mode set to YES in -Info.plist.
My solution:
-(void) viewWillAppear: (BOOL) animated { [self.tableView reloadData];}
Depends, Do you need the data to be loaded each time you open the view? or only once?
Red : They don't require to change every time. Once they are loaded they stay as how they were.
Purple: They need to change over time or after you load each time. You don't want to see the same 3 suggested users to follow, it needs to be reloaded every time you come back to the screen. Their photos may get updated... you don't want to see a photo from 5 years ago...
viewDidLoad: Whatever processing you have that needs to be done once.
viewWilLAppear: Whatever processing that needs to change every time the page is loaded.
Labels, icons, button titles or most dataInputedByDeveloper usually don't change.
Names, photos, links, button status, lists (input Arrays for your tableViews or collectionView) or most dataInputedByUser usually do change.

Resources