Scroll to bottom of webview programmatically with swift - ios

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 {
// ...

Related

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))
}

Check if URL has changed in WebView?

I'm trying to check if the URL has changed in the webView. For example if I were to initially load a page like a Wordpress Sign In page, and I wanted to know when it changed and got redirected to the login page. I tried using this resource enter link description here but the answer seems to be incomplete and does not work.
func validateUrl (stringURL : NSString) -> Bool {
var urlRegEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))+"
let predicate = NSPredicate(format:"SELF MATCHES %#", argumentArray:[urlRegEx])
var urlTest = NSPredicate.predicateWithSubstitutionVariables(predicate)
return predicate.evaluateWithObject(stringURL)
}
urlTest is never called so i'm not sure the purpose of it.
if (validateUrl(stringURL: "http://google.com")) {
//will return true
print("Do Stuff");
}
else {
print("OTHER STUFf")
//If it is false then do stuff here.
}
And then to call this function
func webView(WebViewNews: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {
if (validateUrl(request.URL().absoluteString())) {
//if will return true
print("Do Stuff");
}
}
I added a return function at the end of my code, but the example does not include a return. I have very little experience in Webview, so any advice or help would be appreciated.
Use the webViewDidFinishLoad function of the UIWebViewDelegate to get the current URL loaded in your webview. Everytime the webview loads a URL the function webViewDidFinishLoad is called.
class YourClass: UIViewController, UIWebViewDelegate {
let initialURL = URL(string: "https://www.google.com.pe/")
#IBOutlet weak var webView:UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.loadRequest( URLRequest(url: initialURL) )
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if webView.request != URLRequest(url: initialURL!) {
//DO YOUR STUFF HERE
}
}
}

WKNavigationDelegate Crashing App on Assignment

I'm working on a simple web wrapper application for iOS, and I'm having some issues with WKWebView and WKNavigationDelegate. I want to use the didFinishNavigation function from WKNavigationDelegate, so I can grab information from the URL query on navigation (namely a session GUID). My program launches correctly and loads my webpage when I comment out the "webView.navigationDelegate = self" line, but when I include that, my app crashes immediately with the following errors:
" -[UIWebView setNavigationDelegate:]: unrecognized selector sent to instance
0x7f8251e058f0"
"*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[UIWebView setNavigationDelegate:]: unrecognized selector sent to
instance 0x7f8251e058f0'"
I noticed that both of these error messages include "UIWebView," when I'm trying to use WKWebView, so I tried to use the "custom class" field on the webview from the identity inspector part of the storyboard, but when I try to run after that, I get "(lldb)." Any help/insight would be appreciated, I'm including my code below:
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
print("view did load")
webView.navigationDelegate = self
loadURL("http://mydomain/login.html")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func loadURL(targetURL: String){
if let url = NSURL(string: targetURL) {
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
}
}
private func checkDomainGetSessionGUID(){
print ("we are here")
if let query = webView.URL?.query {
let queryArr = query.componentsSeparatedByString("&")
var parsedQuery : [String: String] = [:]
for field in queryArr {
let parts = field.componentsSeparatedByString("=")
parsedQuery.updateValue(parts[1], forKey: parts[0])
print ("key = \(parts[0]) and value = \(parts[1])")
}
}
else {
print ("didn't enter the if let")
}
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
print ("delegate")
checkDomainGetSessionGUID()
}
}
PROCEDURE 1:
Step-1: Check UIElements that you are using in Storyboard design. if you had used Web view instead of using **WebKit View this error might come.Check your IBOutlet connection.
Step-2: Check your IOS deployment target, It must be IOS 11 and above because of UIWebKit View was released in IOS 8 but it contains a bug that was fixed in IOS 11 only. So set your deployment target 11 and above.
Step-3: Check your info.plist property. The following property should add in the listApp Transport Security Settings -> Allow Arbitrary Loads -> YES
PROCEDURE 2:
If in case you want deployment target as IOS 10 or below IOS 11 means you can implement like this
Step-1: Create a new UIViewcontroller with its swift ViewController file. And add below-given code to run your URL:
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string:"https://www.apple.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}}
I hope this might be helpful to you...
I was getting this error inside (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
I removed the line [super webView:webView didFinishNavigation:navigation]; and everything worked as I expected, I'm not sure if it's hacky though.

iOS UIWebView leaked

class MyViewController: UIViewController {
#IBOutlet weak var webView: UIWebView?
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: urlString)
let request = NSURLRequest(URL: url!)
SVProgressHUD.show()
webView?.loadRequest(request)
webView?.scrollView.header = MJRefreshNormalHeader(refreshingBlock: {
[weak self] in
if let strongSelf = self {
strongSelf.webView?.reload()
}}) }
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
SVProgressHUD.dismiss() } }
extension MyViewController: UIWebViewDelegate {
func webViewDidFinishLoad(webView: UIWebView) {
webView.scrollView.header.endRefreshing()
SVProgressHUD.dismiss() } func webView(webView: UIWebView, didFailLoadWithError error: NSError?) {
webView.scrollView.header.endRefreshing()
SVProgressHUD.dismiss() } }
The view controller was pushed by a navigation controller, when I pop it, I got leaks. In instrument.Leak I saw these.
#
Leaked Object # Address Size Responsible Library Responsible Frame NSMutableArray 1 0x137a6ddb0 48 Bytes UIKit -[_UIKeyboardTextSelectionGestureController init]
_UIKeyboardTextSelectionController 1 0x137a6e800 96 Bytes UIKit -[UIWebSelectionAssistant addNonEditableForceTextSelectionGestureRecognizersToView:]
_UIKeyboardBasedNonEditableTextSelectionGestureController 1 0x137a6dcd0 160 Bytes UIKit -[UIWebSelectionAssistant addNonEditableForceTextSelectionGestureRecognizersToView:]
#
I'm sure that the webView, myViewController were delayed, but when pop the myViewController, 4M increased and not release. Please help and thanks.
List item
According to an answer posted here, there is a workaround that if you set configuration.selectionGranularity to WKSelectionGranularityCharacter, the leaks stop:
let config = WKWebViewConfiguration()
config.selectionGranularity = .character //WKSelectionGranularityCharacter
let myWebview = WKWebview(frame: frame, configuration: config)
This worked for me, but then, when selecting text, there was no selection rectangle in the webview. This may or may not be a viable workaround in your case.
Edit I just noticed your question is for UIWebView, not WKWebView. It doesn't look like you can set this on UIWebView. I'll leave this answer for now since WKWebView folks googling this memory leak will probably find this thread...

Determinate finish loading website in webView with Swift in Xcode

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.

Resources