WKWebView in iOS app takes a long time to load - ios

I have a UIView that has a WKWebView as a subview. The web view shows up all right, but it takes a long time (more than a second) to display the HTML document. The document is a local file (an HTML file in the project) so there is no Internet latency, and it's a relatively simple HTML document. The HTML document does have eight small images on it, but there is a similar problem with another HTML document that doesn't have any images.
Here's the code that loads the HTML document into the web view:
override func viewDidLoad() {
super.viewDidLoad()
let localHtmlFile = Bundle.main.url(forResource: "place", withExtension: "html");
let request = URLRequest(url: localHtmlFile!);
webView.load(request);
}
I am using Xcode 9.3, Swift 4.1, and iOS 11.2.
The delay happens every time I go to that screen. If it isn't possible to prevent the delay the first time, is it possible to keep the web view around so that the delay only happens once?

Apparently the delay is caused by the time it takes to make a new instance of WKWebView, not the time it takes to load an HTML document. To avoid that delay I figured out a way to reuse a web view.
First I removed the web view from the storyboard scene so that a new web vew wouldn't be created every time the view was loaded. I made a generic view named container that is the same size that I wanted the web view to be.
Then I made a static variable to keep a pointer to the web view:
static var webView: WKWebView? = nil
In my case this static variable is in a class called GameController.
Next I changed the code to check to see if the static webView variable is nil. If webView is nil, the code creates a new web view and sets the static variable to point to that web view. Then the code programmatically adds the web view as a subview of a container view in the storyboard scene.
To set up the storyboard and write this code I used the explanation on the following web site:
http://www.onebigfunction.com/ios/2016/12/14/iOS-javascript-communication/
The basic code in the view controller for the scene that uses the web view (WebViewController in my code) looks like this:
override func loadView() {
super.loadView()
if GameController.webView == nil {
var webFrame = self.container!.frame
webFrame.origin.x = 0
webFrame.origin.y = 0
let config = WKWebViewConfiguration()
webView = WKWebView(frame: webFrame,
configuration: config)
GameController.webView = webView
} else {
webView = GameController.webView
}
self.container!.addSubview(webView)
}
In my case, I wanted to send information from JavaScript code in the web view to Swift code in my app, so I had to work more with configurations. I also wanted the web view to be transparent, so I added a statement to do that.
override func loadView() {
super.loadView()
if GameController.webView == nil {
var webFrame = self.container!.frame
webFrame.origin.x = 0
webFrame.origin.y = 0
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "scriptHandler")
webView = WKWebView(frame: webFrame,
configuration: config)
webView.isOpaque = false
GameController.webView = webView
} else {
webView = GameController.webView
webView.configuration.userContentController.removeScriptMessageHandler(
forName: "scriptHandler")
webView.configuration.userContentController.add(self,
name: "scriptHandler")
}
self.container!.addSubview(webView)
}
Originally I only set the script handler when I first made the web view, but that didn't work. Apparently a new view controller object was made each time the scene was loaded, so the old script handler didn't work. This code deletes the script handler that pointed to the old view controller and adds a script handler that points to the new view controller.

Just keep a reference to the ViewController, perhaps in a parent controller, app delegate, or even a singleton/global. It should be somewhat faster on subsequent loads.

Related

WKWebView history serialization on iOS

I'm trying to restore WKWebView navigation after app will restarts. I can inherit from base web view and take more control with it.
class WebViewHistory: WKBackForwardList {
// additional control with history items
// can perform custom init
}
class WebView: WKWebView {
var history: WebViewHistory
override var backForwardList: WebViewHistory {
return history
}
init(frame: CGRect, configuration: WKWebViewConfiguration, history: WebViewHistory) {
self.history = history
super.init(frame: frame, configuration: configuration)
}
}
For serialization I need serialize at least one WKBackForwardListItem. So here I face up with troubles.
class WebNavigationItem: WKBackForwardListItem {}
// but I cant create this objects
let item1 = WKBackForwardListItem() // 'init()' is unavailable
let item2 = WebNavigationItem() // 'WebNavigationItem' cannot be constructed because it has no accessible initializers
How to serialize history of WKWebView?
So for now I see only 1 option: make custom navigation with serialization support and forgot about allowsBackForwardNavigationGestures. And it required more code for implementing all logic of default web view history.
If you're targeting iOS 15 or above, you should look into WKWebView.interactionState.
It's erased to Any? but appears to be a plain old [NS]Data value behind the scenes. Maybe with luck you can serialise and restore that over app launches?

View not loading from Storyboard on InstantiateViewController() call

I am trying to present a view controller in a Forms dependency service. But I keep getting a black screen. I can get it to work when I use Swift in Xcode, but not using Xamarin.
Controller outlets are not loading either. Strange thing is changes to vc.View do appear. It’s as though it’s creating a blank view and not actually loading from the storyboard.
My code is as follows:
var storyboard = UIStoryboard.FromName("StoryboardView", null);
var controller = storyboard.InstantiateViewController("StoryboardViewVC");
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
vc.PresentViewController(controller, true, null);
Adding a call to LoadViewIfNeeded() does not fix the issue.
I have double-checked that the storyboard and controller names match, which they do. I’ve searched for answers, but everything I’m finding revolves around not writing code to load the controller from the storyboard or nib.
var controller = (UIViewController)storyboard.InstantiateViewController("StoryboardViewVC");
use class you assigned to your view that may load your view.

iOS 10, QLPreviewViewController is not displaying files

I am working on a iOS app that displays lots of different files to the user. From video and audio files to html and Office/iWorks files. I have subclassed QLPreviewController, and want to use it to display some of these files. I create the object, pass it the file's url, set the ViewController's view's frame to replace the webView's frame in the parent viewcontroller. :
else if (QuickLookViewController.canOpenFile(previewItem: contentUrl as NSURL)) {
hideControls()
quickLook.contentURLs = [contentUrl as NSURL]
//add the QuickLookController's view to content display using webview's frame
self.view.addSubview(quickLook.view)
quickLook.view.frame = webview!.frame
quickLook.reloadData()
Using the view debug hierarchy tool, it shows the view set correctly, as well as setting quickLook.backgroundColor = UIColor.black just to test.
So I thought that maybe there was a problem with accessing the download and stored file, so in the viewDidLoad of QuickLookController I added some tests to ensure that the file is there:
super.viewDidLoad()
print("\nContent URL: \(contentURLs[0])")
print("\nContent URL Path: \(contentURLs[0].path!)")
self.delegate = self
self.dataSource = self
var error : NSError?
print("\nis reachable: \(contentURLs[0].checkResourceIsReachableAndReturnError(&error))")
if (error != nil) {
print("is reachable error -> \(error.debugDescription)")
}
print("\ndoes exist: \(FileManager.default.fileExists(atPath: contentURLs[0].path!))")
print("\nCan open: \(QuickLookViewController.canOpenFile(previewItem: contentURLs[0]))")
and the log statements come out as I expect:
Content URL: file:///var/mobile/Containers/Data/Application/B9D5C288-F889-4513-941E-2564F1C12F02/Documents/588c5a1e-dffe-47a8-9824-bc19463aafc2/d88a8dd5-40d1-4fdb-adf3-10fce1f6bf1f/fd73c162-5ac3-4269-8573-9c0b61bef7a7/fd73c162-5ac3-4269-8573-9c0b61bef7a7.pages
Content URL Path: /var/mobile/Containers/Data/Application/B9D5C288-F889-4513-941E-2564F1C12F02/Documents/588c5a1e-dffe-47a8-9824-bc19463aafc2/d88a8dd5-40d1-4fdb-adf3-10fce1f6bf1f/fd73c162-5ac3-4269-8573-9c0b61bef7a7/fd73c162-5ac3-4269-8573-9c0b61bef7a7.pages
is reachable: true
does exist: true
Can open: true
I even used a breakpoint in the the viewDidLoad to check that the quickLook's superview is set using 'po self.view.superview!.frame' as a llbd statement, and again received the output I expected.
I used this same class for another view stack in the app and it displays the files that are clicked, so it isn't making much sense to me. The only difference between the two uses, the second I am presenting the quickLook's view in a viewController that is presented modally.
ok I wanted to post the answer to the problem I was having. The issue was I was not adding the quickLook view controller to the ContentDisplayViewController with a parent-child relationship. So the updated code to add the view I wanted looks like:
//create the parent-child relationship for the view controllers
self.addChildViewController(quickLook)
//give the view it's frame
quickLook.view.frame = webview!.frame
//add the view and call the child's didMove() method.
self.view.addSubview(quickLook.view)
quickLook.didMove(toParentViewController: self)
This question gave me the answer:
Add child view controller to current view controller
Gave me me some reading material to read into, thanks for the post #Piyush Patel

How to resize a Maxpreps HTML widget to fit the UIWebView?

I am trying to use the Maxpreps widget (http://www.maxpreps.com/widgets/createwidget.aspx) in my app to show school sports updates.
I made a html file in xcode and pasted the code provided from Maxpreps. I created a webview and used the code on the view controller.
#IBOutlet weak var sportsTest: UIWebView!
sportsTest,loadRequest(NSURLRequest(URL: NSURL(fileURLWWithPath: NSBundle.mainBundle().pathForResource("widgetcode", ofType: "html")!)))
The problem is when the code is being shown it wont fit the UIWebView properly.
The issue you are having with the HTML not fitting in the UIWebView is not simple to resolve, but I can give you a hack that will get your content on the screen.
The problem is that the web page from Maxpreps loaded from the URL in their widget isn't designed to fit completely on an iPhone in portrait orientation. However it is a responsive HTML page so that is the good news.
First, you don't need to load the script tag or write an HTML file since all that gives you is a link that you need to click, right? You don't want that link, you want the content of what is behind the link, right? Hopefully!
This view controller implementation should work fine:
import UIKit
class ViewController: UIViewController {
#IBOutlet var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// This will force the UIWebView to downscale and fit your page, but
// it is hacky because 91% of original size might not always be the
// right amount. This is why I said it is hard.
self.webView.scrollView.minimumZoomScale = 0.3
self.webView.scrollView.maximumZoomScale = 2.0
self.webView.scrollView.setZoomScale(0.91, animated: false)
}
override func viewWillAppear(animated: Bool) {
if let url = NSURL(string: "http://www.maxpreps.com/local/team/home.aspx?gendersport=boys,baseball&schoolid=45823724-55bc-4d89-926b-b1974b3d8e36") {
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
}
else {
println("Failed to create URL")
}
}
}
The above works, but better solutions IMHO would be to:
1) Ask Maxpreps to fix their responsive HTML page so that it renders on an iPhone in portrait orientation
2) Ditch HTML altogether and query the information you need using a REST API if they make one available, and then write a native non-HTML screen
3) Attempt to get a dynamic scaling solution in place that works. These are prone to failure based on my experience
Here is what I see on my simulator when I run it:
Another example:

Creating a floating menu in an iOS application

Looking to create a floating menu in Swift for an iOS application I am developing. Something along the lines of the little red circle menu as shown in the following image.
My initial thoughts were to extend the UIViewController class and add the respective drawing/logic there, however, the application is comprised of a few other controllers, more specifically the UITableViewController which in itself extends UIViewController. Is there perhaps a good place for an extension perhaps? Or is there a more eloquent way of drawing the menu on specific views without the mass duplication of menu related code?
The menu itself will be shown on most screens, so I need to selectively enable it. It'll also be somewhat contextual based on the view/screen the user is currently on.
Any awesome ideas?
You can create your own with the animations and all the things, or you can check this library
https://github.com/lourenco-marinho/ActionButton
var actionButton: ActionButton!
override func viewDidLoad() {
super.viewDidLoad()
let twitterImage = UIImage(named: "twitter_icon.png")!
let plusImage = UIImage(named: "googleplus_icon.png")!
let twitter = ActionButtonItem(title: "Twitter", image: twitterImage)
twitter.action = { item in println("Twitter...") }
let google = ActionButtonItem(title: "Google Plus", image: plusImage)
google.action = { item in println("Google Plus...") }
actionButton = ActionButton(attachedToView: self.view, items: [twitter, google])
actionButton.action = { button in button.toggleMenu() }
}
There is another alternative with this great library :
https://github.com/yoavlt/LiquidFloatingActionButton
You just have to implement the delegate and the dataSource in your ViewController:
let floatingActionButton = LiquidFloatingActionButton(frame: floatingFrame)
floatingActionButton.dataSource = self
floatingActionButton.delegate = self
You could use view controller containment. The menu can be its own view controller with its view laid transparently over top the content view controller.
For example this can be set up in the storyboard by dragging out two container views into a vanilla view controller.

Resources