Determinate finish loading website in webView with Swift in Xcode - ios

i'm trying to create a webapp with swift in xcode, this is my current code:
IBOutlet var webView: UIWebView!
var theBool: Bool = false
var myTimer = NSTimer()
#IBOutlet var progressBar: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "http://stackoverflow.com")
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
}
I've a question, How i can determinate the finish loading of the page in webView?
Do you know a documentation that contains all the definitions of the WebView? I mean .. (start / end load, the current url, title page, etc ..)?
(Sorry for my English).

Through the UIWebView delegate call.
You need to set your webViews delegate to the current controller, and conform to the UIWebViewDelegate protocol. When the webView finished loading the page func webViewDidFinishLoad(_ webView: UIWebView) will get called.

For WKWebview there is also wknavigationdelegate didfinish, but not do the trick as the SO question WKWebView didn't finish loading, when didFinishNavigation is called - Bug in WKWebView? and this answer show.
I also found when the page to load is very complicate and dinamic, UIWebview's webViewDidFinishLoad(- webView: UIWebView) also not works.
And I think use native swift or objective-c to detect when the page is loaded is a bad choice. The more flexable and effective way is to use javascript to call the native swift code when page finishing loading. What's more this also work for a specific dom finishing loading event and very dynamic content.
For how to call swift from javascript refer to this post.
Here is a sample code for dynamic content with anjularjs.
js,
var homeApp = angular.module('home', ['ui.bootstrap', 'ngAnimate']);
homeApp
.directive("printerinfo",
function() {
return {
restrict: "E",
link: function() { //call navite swift when page finishing loading
try {
window.webkit.messageHandlers.testHandler.postMessage("Hello from JavaScript");
} catch(err) {
console.log('The native context does not exist yet');
}
},
templateUrl: "/static/tpls/cloud/app/printerinfo.php",
}
})
swift3,
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.name)
if(message.name == "testHandler") {
//webpage content loaded
print(Date())
print("javascript test call swift")
}
Here I use angularjs to check element ready, you can also refter how-to-call-a-function-after-a-div-is-ready or check-if-a-given-dom-element-is-ready or jquery-ready-equivalent-event-listener-on-elements for more.

Related

How to make a webview NOT clickable in Swift iOS

I want to implement a webview in Swift for an iOS application where I just want to display the content of a given website. The implementation below works but I have 2 requirements that I don't know how to implement:
I want the user not to be able to interact with the page displayed in any way. So if for example there is a link, the user cannot click on that. Same goes for any other element of the page, like images, etc. Is there a way to do so? In practice, the website content would need to be served as if it was a picture of the content, without the possibility to interact with individual elements of the page.
I want that the address of the website is never visible to the user. In my implementation below, that seems to be the case, but I am not sure if that would work in any condition and if there is a safer way to ensure that the web address is never displayed to the user (even when for example iOS informs the user that there was an issue loading the page, etc).
What I have in ViewController.swift is below:
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "my-website-here")!
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
}
After the closing bracket of the func override func viewDidLoad() try implementing this
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(.cancel)
}
also just under webView.load(URLRequest(url: url))
add
navigationController?.isToolbarHidden = true
navigationController?.isNavigationBarHidden = true

Instance member 'webView' cannot be used on type 'MyWebView(UIview)'

I am creating one SDK to get the url from user and load. So ai m creating my own class with WKWebview. But i am getting few issues about Instance member 'webView' cannot be used on type 'MyWebView(UIview)'
Code :
import Foundation
import WebKit
public class MyWebView: UIView, WKNavigationDelegate {
// initialize the view
var webView: WKWebView!
// load the view
private func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
}
// get the url and load the page
public static func loadUrl(Url: String) {
MyWebView.webView.load(URLRequest(url: URL(string: Url)!))
}
}
In my loadUrl, what ever user sending i need to use that url and load the url. Same in my view controller will look like :
import UIKit
class ViewController: UIViewController {
var webView: MyWebView!
override func loadView() {
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Any help would be great.
Your loadUrl function should not be static, since it needs access to an instance property, webView. Making the function non-static solves the issue.
Also a couple of minor improvements: don't force unwrap the URL init, since with an incorrect input that will crash. Use optional binding to safely unwrap it instead. I'd also suggest renaming the input argument label on loadUrl, since there's no point in having to right out loadUrl(Url:) every time you call the func, loadUrl( reads more naturally.
public func loadUrl(_ urlString: String) {
guard let url = URL(string: urlString) else { return }
webView.load(URLRequest(url: url))
}

Swift: How do unit test UIWebView loading?

My app has a basic webview controller to perform some operations. This view in the storyboard is not much besides a wrapper around a UIWebView. The controller itself has various public functions that can be called to load pages in the webview, like so:
class WebViewController: UIViewController, UIWebViewDelegate {
// MARK: Properties
#IBOutlet var webView: UIWebView!
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
loadHomePage()
}
// MARK: Public
public func loadHomePage() {
navigateWebView(to: HOME_PAGE)
}
public func loadSettingsPage() {
navigateWebView(to: SETTINGS_PAGE)
}
public func loadSignOutPage() {
navigateWebView(to: SIGN_OUT_PAGE)
}
// MARK: Private
private func navigateWebView(to url: String) {
let request = URLRequest(url: URL(string: url)!)
webView.loadRequest(request)
}
I'm trying to write unit tests that verify that the proper URL is sent to the loadRequest function of the webview. Note that I don't actually care about loading the URL; this is just a unit test, so all I really want to test is that loadSettingsPage sends a URLRequest with the SETTINGS_PAGE URL to the webview to load, for example.
I tried something like this, with no success:
_ = webViewController.view // Calls viewDidLoad()
XCTAssertEqual(webViewController.webView.request?.url?.absoluteString, HOME_PAGE)
The value of the first part of the assertEqual was nil.
I assume I need to mock out the webView somehow but I'm not sure how to go about that. Any suggestions?
As a follow-up, I'd also like to be able to test when things like webView.reload() and webview.goBack() are called, so any pointers there would be appreciated as well. Thanks!

Scroll to bottom of webview programmatically with swift

I have the following webview:
#IBOutlet weak var webView_MyContent: UIWebView!
and load custom html content like so:
self.webView_MyContent.loadHTMLString(html, baseURL: nil)
I would like to scroll to the very bottom of the page programmatically when my content loads. How would this be accomplished in swift?
You can use scrollView property of UIWebView for that.
func webViewDidFinishLoad(_ webView: UIWebView) {
let scrollPoint = CGPoint(x: 0, y: webView.scrollView.contentSize.height - webView.frame.size.height)
webView.scrollView.setContentOffset(scrollPoint, animated: true)//Set false if you doesn't want animation
}
Note: Don't forgot to set delegate of your webView.
Didn't work for me with Swift 4 / WKWebView.
Instead I found the method webView.scrollToEndOfDocument()
To scroll down after loading is finished you can put it into this function:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.scrollToEndOfDocument(self)
}
Don't forget to import WebKit and to make your class the delegate of your WebView:
// ...
import WebKit
// ...
class ViewController: NSViewController, WKNavigationDelegate {
// ...
Advanced: scroll down after finishing AJAX requests
Now in my case I wanted to scroll down a page that used infinite scroll (when nearly reaching the end of the page it starts loading additional content).
This can be done by injecting JavaScript and overwriting the XMLHttpRequest method:
override func viewDidLoad() {
super.viewDidLoad()
// ...
String javascript = String(contentsOfFile: Bundle.main.path(forResource: "script", ofType: "js"))
webView.configuration.userContentController.add(self, name: "injectionHandler")
webView.configuration.userContentController.addUserScript(WKUserScript.init(source: javascript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
// ...
}
And in the file script.js in your Xcode project you'll put this:
var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
this.addEventListener("load", function() {
var message = {"status": this.status, "requestUrl": url, "response": this.responseText, "responseURL": this.responseURL};
webkit.messageHandlers.injectionHandler.postMessage(message);
});
open.apply(this, arguments);
};
To handle this event (and if you want also catch the AJAX response) you have to add this method:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "injectionHandler", let dict = message.body as? Dictionary<String, AnyObject>, let status = dict["status"] as? Int, let response = dict["response"] as? String {
if status == 200 {
webView.scrollToEndOfDocument()
}
}
}
and make your class extend WKScriptMessageHandler:
class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler {
// ...

DELAY issues: how to apply styles immediately or not show loaded webpage until styles are applied?

Technologies Used: XCode 6, iOS8, Swift
I'm loading a webpage in a uiwebview and I'm also appending a new stylesheet to the body of that webpage and overwriting some of its styles. But, there is a delay (maybe 1 second or 2) between when the webpage loads and the styles are applied so you can see the webpage before its restyled. I'm using javascript to append the new styles to the body of the webpage. How can I fix this so that the webpage will only show with the styles are already applied? Here is my code:
import UIKit
class SecondViewController: UIViewController, UIWebViewDelegate {
#IBOutlet var website: UIWebView!
var url = "http://www.fake-website-url.net"
func loadUrl() {
let requestURL = NSURL(string: url)
let request = NSURLRequest(URL: requestURL!)
website.loadRequest(request)
}
override func viewDidLoad() {
super.viewDidLoad()
website.delegate = self
loadUrl()
}
func webViewDidFinishLoad(website: UIWebView) {
var loadStyles = "var script = document.createElement('link');script.type = 'text/css';script.rel = 'stylesheet';script.href = 'http://fake-url.styles.css';document.getElementsByTagName('body')[0].appendChild(script);"
website.stringByEvaluatingJavaScriptFromString(loadStyles)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Note, I'm using Swift.
What I would do is create a property to store the downloaded page. Then override the property setter to add your custom style sheet after the page is saved to that property. Then finally load it into your Web View.
Hope that makes sense.

Resources