UIWebViewDelegate: webViewDidFinishLoad not called during in-page navigation - ios

I have an app where users can navigate a pile of locally stored HTML files. I have a UIWebView, configured up correctly with a UIWebViewDelegate. Usually, when the user follows a link, shouldStartLoadWithRequest is called, followed by webViewDidFinishLoad a bit later.
But, if the link is pointing to an anchor on the same page as the one which is currently displayed, only shouldStartLoadWithRequest is called. webViewDidFinishLoad does not fire.
In a sense, I see that this might be expected behaviour, because in-page navigation should not require a page reload. However, I really need a place to hook into the call stack after in-page navigation is complete. The optimal solution would let me know when any sort of navigation has ended, both from another page, in-page and forward/backward actions.
My best hackaround so far has been to call performSelector: withObject: afterDelay: at the end of my shouldStartLoadWithRequest method, but I'm not happy with this.
Does anyone know how I can solve this correctly? Any insight appreciated!

You can try to use NSURLConnectionDataDelegate, it allows you to handle incoming data. Maybe you can determine if the page is loaded manually by adding a sign to your html files.
NSURLConnectionDataDelegate Reference
Edit: gist.github.com/buranmert/7304047 I wrote a piece of code and it worked, that may not be the way you wanted it to work but maybe it helps. Every time user clicks a URL with anchor, it creates another connection and as connection finishes loading web view loads the data, that marks the point where web view finished loading the page. As you use only local html files, I do not think creating connections will create problems

What you are describing is intended behavior. Just as AJAX or resource requests are never passed to the delegate, only root page changes will ever hit webViewDidFinishLoad:. But I have good news, and it doesn't involve saving a bunch of money on car insurance.
Loads performed within an iFrame DO trigger the full delegate methods and this gives you a solution. You can use this mechanism to post a notification to the native code, just as is often done for console.log() as described in this post. Alternatively, Native Bridge would work well to call into your Objective C code from JavaScript.

Just check weather u got the delegate function DidStartLoading if it is called no doubt that DidFinish also should get called

Are you sure your shouldStartLoadWithRequest always returns an YES???
I always add
return YES;
at the end of shouldStartLoadWithRequest implementation.And that works for me.
By returning YES, it denotes that the webview has loaded and would call the webViewDidFinishLoad

if([webView isLoading]==1)
{
//your webview is loading
}
else
{
//your webview has loaded
}

Here is a Swift Implementation of Mert Buran's code incase anybody is looking for it. (Although NSURLConnection is deprecated as of iOS 9)
But it does not solve my problem. When i click on a jquery link that popups a video, it does not fire the webViewDidFinishLoad.
class WebViewController: UIViewController, UIWebViewDelegate, NSURLConnectionDelegate {
var menuURL: String?
var response: NSURLResponse?
var data = NSData()
// MARK: Properties
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
// From Web
let url = NSURL (string: menuURL!)
let urlRequest = NSURLRequest(URL: url!)
let connection = NSURLConnection(request: urlRequest, delegate: self)
self.data = NSData()
connection!.start()
// if this is false, page will be 'zoomed in' to normal size
webView.scalesPageToFit = false
}
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == .LinkClicked && request.URL!.fragment != nil {
let connection = NSURLConnection(request: request, delegate: self)
connection!.start()
return false
}
return true
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
self.response = response
}
func connection(connection: NSURLConnection, didReceiveData data: NSData) {
let oldData = NSMutableData(data: self.data)
oldData.appendData(data)
self.data = oldData
}
func connectionDidFinishLoading(connection: NSURLConnection) {
self.webView.loadData(self.data, MIMEType: self.response!.MIMEType!, textEncodingName: "utf-8", baseURL: self.response!.URL!)
self.data = NSData()
}
}

Related

WKWebView does not open https://itunes.apple.com/app/id123456 link

Here is a little problem I just noticed in one of my iOS apps.
I use Xcode Version 10.1 and Swift 4.2.
The app has a button which when pushed brings up a view controller, this VC is in charge of opening a link to the app itself in itunes. I have done this many times with no problems in the past.
But this time, a blank pages opens up and nothing else. I have tried to replace only the URL I am interested in by "https://www.google.com/" and it works perfectly as expected (i.e. the Google page shows up). Of course I have verified that my URL is correct.
Can anybody see what could be the issue?
Thanks in advance for any relevant tip.
Here is the code for the whole view controller.
import UIKit
import WebKit
class appStore_ViewController: UIViewController, WKNavigationDelegate {
let appDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let appStoreURL = URL(string: "https://itunes.apple.com/app/id\ (appDelegate.applicationID)?mt=8")
//let appStoreURL = URL(string: "https://www.google.com/") // This works as expected.
print("The link: \(appStoreURL!.absoluteString)") // This shows what is expected (a working URL).
webView.load(URLRequest(url: appStoreURL!))
}
}
You should see what happen while visiting the AppStore Url with safari browser in your iPhone. then you will find the it still show blank page but show an alert to open the Appstore.
If you want to do the same function in your app with webView. you have to handle the alert the by yourself.
You need to implement the decidePolicyForNavigationAction method of WKNavigationDelegate for this to work.
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)
(WKNavigationActionPolicy))decisionHandler {
/* Code to handle other schemes such as file, https, http, etc. if required
*/
if ([[[navigationAction.request URL] scheme] isEqual:#"itms-appss"])
{
[[UIApplication sharedApplication] openURL:[navigationAction.request URL]];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
WKWebview does not inherently support different schemes such as "itms-appss" and you need to check and implement the url scheme you want to support.
You also need to check if you are running in iPadOS and set WKWebpagePreference to use mobile content mode if you do so.

How to set localStorage item in webView before loading a URLRequest?

I have to access a web page that has token on its localStorage
let javascript = "localStorage.setItem('token', 'abc')"
let url = URL(string: "https://test.com/abc")
webView.evaluateJavaScript(javascript) { (_, err) in
print(err?.localizedDescription)
// This will return 'A JavaScript exception occurred'
}
let request = URLRequest(url: url!)
webView.load(request)
I dont think this is possible. You get this error:
Error Domain=WKErrorDomain Code=4
"A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0,
WKJavaScriptExceptionMessage=SecurityError: The operation is
insecure., WKJavaScriptExceptionColumnNumber=0,
NSLocalizedDescription=A JavaScript exception occurred}
And there nothing you can do about for the WKWebView.
i solve it but i think it's a bad practice and the right way was to be able to send token in request's header
the main problem was that you can't run javascript script that add localStorage item before the web view load so i had to wait utile that page is loaded then then run javascript script that add needed token then reload that page Here is my code but again it's bad practice i think and i believe that front end team should allow me to end that token in request header
fist there was that method with reload the web view only one time
var loaded = false
func load() {
if !loaded {
webView.reload()
}
loaded = true
}
then i had to confirm to WKNavigationDelegate delegate to reload that page when it's loaded and here is my code
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript(javascript) { (_, err) in
self.load()
}
}
}

Changing the requests from HTTPS to HTTP from WKWebview?

My requirement is to redirecting the HTTPS request to HTTP from WKWebView.
But the request initiated inside the webview(Ajax call) are not being captured.
After that, the requests being made from that webpage are not captured.
Please share your ideas. Much appreciated.
You would need to implement a WebPolicyDelegate with a decidePolicyForNavigationAction and then load the page yourself without https. This should work, but I haven't tested it.
Have a look at this post for some insides, on UIWebView it would have worked with shouldStartLoadWithRequest:
Migrating from UIWebView to WKWebView
Use the following steps for UIWevView to load a remote address
Load the url address
let urlString = "http://www.example.com"
webView.loadRequest(NSURLRequest(url: NSURL(string:"url) as! URL) as URLRequest)
Conform UIWebViewDelegate in ViewController
After conforming UIWebViewDelegate, use the following delegates
/* Delegates for WebView */
fun webViewDidStartLoad(_ webView: UIWebView){
// Control loading the url
}
func webViewDidFinishLoad(_ webView: UIWebView){
// statements when loading finishes
}
fun webView(_ webView: UIWebView, didFailLoadWithError error: Error){
// Handle error here
}
If still does not work, add a row for "App Transport Security Settings" in your project Info.plist. Here is screenshot for it settings.

Instagram Oauth Redirect on iOS

I'm writing an iOS Application that needs to use Oauth to connect to Instagram. The standard iOS mechanism for o-auth is to provide a redirect URL with a custom scheme. For instance, myapp://oauth/redirect -- And register that in your iOS app. Which is all fine and dandy, except the management portal for Instagram seems to prevent any scheme but http or https when you setup your valid redirect URI's.
Any thoughts on how to make this work? Am I forced to add a server component to capture / redirect?
There are two approaches to doing this without using a custom URL Scheme, each with positives and negatives:
Universal Links - PROS: Redirects to Safari so you don't have to build in a web browser yourself. Safari redirect also allows for using the users iCloud keychain which can make the login experience easier for your user. CONS: Setting it up is kind of a bear, and requires your own server and a published app. Doesn't seem to work in simulator.
Setup and present a UIWebView, and in webView:shouldStartLoadWithRequest:navigationType: catch the access token from the fragment, and prevent loading / redirection. This allows you to use any URL you want as the redirect URL because it doesn't matter :)
Example:
import UIKit
class OAuthVC : UIViewController, UIWebViewDelegate {
#IBOutlet var webView : UIWebView?
public var success : ((URLRequest) -> Void)?
public var presentVC : UIViewController?
func handle(_ url: URL) {
view.tag = 1 // Make sure our view and thus webview is loaded.
webView!.loadRequest(URLRequest(url: url))
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.url?.fragment?.contains("access_token") == true {
success!(request)
dismiss(animated: true, completion: nil)
return false
}
return true
}
func webViewDidFinishLoad(_ webView: UIWebView) {
// If we stop on a page before the redirect closes us, user interaction is required. Present.
presentVC?.present(self, animated: true, completion: nil)
}
}
List item

Custom http header on UIWebView requests causing back button to break

I'm using the following code to inject a custom http header into my UIWebView requests:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
let headerFields = request.allHTTPHeaderFields
var headerIsPresent = contains(request.allHTTPHeaderFields?.keys.array as [String], "X-Test-App")
if headerIsPresent || navigationType == UIWebViewNavigationType.Other {
return true
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
dispatch_async(dispatch_get_main_queue(), {
let url = request.URL
var newRequest: NSMutableURLRequest = request as NSMutableURLRequest
// set new header
newRequest.addValue("MyValue", forHTTPHeaderField: "X-Test-App")
// reload the request
self.webView.loadRequest(newRequest)
})
})
return false
}
}
The problem is that the back button history is not being kept. So if I click like 3 requests deep. When hitting back, it will only go to the previous page and then hitting back again will result in it going back to the page you just went back from. It's basically an endless loop between the 2 most recent requests. Any idea how I can modify the above code to be able to keep the entire web history and allow the back/forward buttons to function as expected?
The problem is that UIWebView's built-in goBack() only goes back 1 page, but by intercepting the loadRequest and replacing it with a new request, webView is seeing this as a new, second item in the history. So more work will be required to combine 1) UIWebview built-in history, 2) UIWebView goBack(), and 3) this method of intercepting load requests.
The button calling goBack() could just call goBack() twice-- if in the same code block, I assume UIWebView would jump to the second item in the history. But this is fragile in other ways, not recommended.
You could create a stack of unmodified requests. Set up an array, and call append() to add the unmodified request before creating the new one. But first, for every unmodified request, check if it's the lastObject in the array. If so, they probably want the one before that, so remove it from the end of the array and create a modified request with the new lastObject.

Resources