UIWebView get request URL path (html string) - ios

I am able to load a HTML string into the UIWebView with CSS obtained from an online source. The problem I have now is to get the path of the link that was clicked. If I use this:
webView.loadHTMLString(finalHtml, baseURL: baseUrl)
I get the path clicked to be "/" when I println(webView.request?.URL.path) in the UIWebViewDelegate shouldStartLoadWithRequest.
If I use this:
webView.loadHTMLString(finalHtml, baseURL: nil)
I get "nil" when I println(webView.request?.URL.path)
Of course, I have set the baseURL to be the original website, but from my understanding, if the link has the full address, the baseURL is irrelevant.
Any pros out there with any advice on how to get the actual path that is indicated in the tags of the link that was clicked? Thank you in advance =D
EDIT When I long click the link, there will be a popup with the correct link shown. I have tried everything, including absoluteString, but I still won't get the path.

If webView.loadHTMLString(finalHTML, baseURL: baseURL) and println(webView.request?.URL.path) are called in the same code block, the webView won't have a non-nil request property yet, and so you can't get the URL path on that request. UIWebView is loading your HTML string asynchronously. You can implement UIWebViewDelegate to see when a link is tapped in your UIWebView. Use the following delegate method:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == .LinkClicked {
println(request.URL.path)
}
return true
}
Leave out the if navigationType == .LinkClicked conditional if you want to see the path of all requests including navigation backwards, forwards, etc.

Have a look at the Documentation for NSURL.
Besides path, there is a property absoluteString that includes the host, base URL etc.

So it seems that it eluded everyone's eyes that I made a silly mistake in my code. Instead of webView.request?.URL.path, I should use request.URL.path. Really small thing that caused me so much agony. So in case anyone is as silly as me, in your shouldStartLoadWithRequest, use request instead of webView.request.

Related

Swift 4 app crashes when open file from icloud

I have a problem that my app crashes when it is opening a file from iCloud. If I open this file from my app with a Document Picker, everything is fine. But if I try to open from outside my app, for example from iCloud or safari download it crashes. If I open it from local storage "my iphone" it is also working. It is interesting because it was good one week ago :)
So in AppDelegate, I've implemented the following method:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {}
According to the logs the crash occurs because the file does not exist.
file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Desktop/twic1121.pgn
This is the result if I print the URL from the parameter. I think this means that the file is there.
But if i do this: print(fm.fileExists(atPath: url.path)) then this is false.
So it is obvious that after let dataFromFile = fm.contents(atPath: url.path)
this is nil.
I have no idea what could be the problem here. So the real question here is why this is nil?
It appears that the error can be many things, all not related to the class you are applying the code (AppDelegate) nor the methods you are calling.
My guess is that the URL you are calling is not correctly built (not pointing to the correct object you are trying to point to). For many reasons.
See if one of this reasons fix your issue:
(1) The end of the URL you are calling had the suffix "pgn". If you are looking to load a picture, maybe the suffix is wrong. In that case it could have been some known and supported format like "png", "jpeg" or "jpg".
(2) The "%20"symbol at the middle of your code also lifts a flag. Does not seem to be a correct URL object of swift. Maybe the URL you are using is not represented in the correct way.
(3) com~apple~CloudDocs also lifts a flag, since it would unlikely have a "~" symbol in a URL passed. This also strongly suggests that maybe the URL you are using is not represented in the correct way.
I think your URL is not pointing to where you are trying to point to, resulting in the "does exist" method return false and the loading resulting in nil.
If all of this does not fix your issue, post more details of the code. Specially what method you are calling to build/create this URL object you are using, that points to: file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Desktop/twic1121.pgn

WKWebView fails to load images and CSS using loadHTMLString(_, baseURL:)

Apple's recommendation:
In apps that run in iOS 8 and later, use the WKWebView class instead of using UIWebView.
Thus, I have replaced my good old UIWebView with a shiny new WKWebView. But what I thought to be an easy exercise (simply swapping the classes and replacing the delegate methods) turned out to be a real mess.
The Problem
When I load an HTML string using
loadHTMLString(String, baseURL: URL?)
the web view loads and renders the pure HTML but it doesn't load any images or CSS files referenced inside the htmlString.
This happens only on a real device!
In Simultor all referenced resources are loaded correctly.
Example
I have defined a simple htmlString in my view controller class:
let imageName = "image.png"
let libraryURL: URL // The default Library URL
var htmlString: String {
return "<html> ... <img src=\"\(imageName)\" /> ... </html>"
// "..." represents more valid HTML code incl. header and body tags
}
The image is stored in the root Library folder so its URL is:
let imageURL = libraryURL.appendingPathComponent(imageName)
Now I load the htmlString into the web view:
webView.loadHTMLString(htmlString, baseURL: libraryURL)
and it doesn't load the image even though the baseURL is set correctly.
Ideas for a Solution
Maybe WKWebView has a problem with resolving relative paths so my first idea was to use absolute paths inside the HTML string instead.
→ ❌ Doesn't work.
Two answers to another SO post suggested that using
loadFileURL(URL, allowingReadAccessTo: URL)
instead of loadHTMLString(...) works in iOS 9+.
→ ✅ That works.
However, I cannot use solution 2 because my HTML files are encrypted and the decrypted files must not be stored on the disk.
Question
Is there any way to load local resources like images and styles using the WKWebView's
loadHTMLString(String, baseURL: URL?)
function? Or is still a bug in iOS 9+?
(I just cannot believe that Apple provides and recommends using a web view that cannot load any local web content from inside an HTML string?!)
Without taking a look at your actual project it's difficult to give some hundreed percent sure advices.
However:
class ViewController: UIViewController {
var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
webView.translatesAutoresizingMaskIntoConstraints = false
let views = [
"webView" : webView
]
view.addSubview(webView)
var constraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[webView]|", options: [.AlignAllLeading, .AlignAllTrailing], metrics: nil, views: views)
constraints.appendContentsOf(NSLayoutConstraint.constraintsWithVisualFormat("V:|[webView]|", options: [.AlignAllTop, .AlignAllBottom], metrics: nil, views: views))
NSLayoutConstraint.activateConstraints(constraints)
let path = NSBundle.mainBundle().pathForResource("ios - WKWebView fails to load images and CSS using loadHTMLString(_, baseURL_) - Stack Overflow", ofType: "htm")
let url = NSURL(fileURLWithPath: path!)
webView.loadHTMLString(try! String(contentsOfURL: url), baseURL: url.URLByDeletingLastPathComponent)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I think the key point here is baseUrl parameter, you should setup it correctly. In my case i've used html's url without last path component - e.g. containing folder. This works fine on both device & simulator - check device snapshot. I've uploaded sample project to https://github.com/soxjke/WKWebViewTest so you can take a look (i've removed codesigning info from git)
So, to recap - method is working, functionality is working, just you do something wrong with it. To help you get what's wrong with your solutions, i'll add some suggestions:
1. Remember, that simulator filesystem is case-insensitive, device filesystem is case-sensitive. So if you have your filenames in html in lowercase - this won't work on device. 8fFsD.png != 8ffsd.png
2. Remember, that when copying resources, XCode ignores your folder structure. So if your html has <img src="./img/1.png"> and your XCOde project has folder structure like
test.htm
img/
1.png
2.png
After build it will be flattened, so test.htm and 1.png and 2.png will reside on same level
test.htm
1.png
2.png
I'm almost sure, after you verify these two assumptions, you'll get this method working.
I had this problem today, I've found the solution and potentially the cause:
loadHTMLString(String, baseURL: URL?)
This function doesn't allow the rendered HTML to access local media, as far as I'm aware, this is because it would be an injection risk, this could allow rendered HTML to access and manipulate your local file system. With a html string, that could come from anywhere or anyone.
loadFileURL(URL, allowingReadAccessTo: URL)
With this function, you point the WKWebview to the html file in your FileManager, and to the containing folder with 'allowingReadAccessTo'. Because the html is stored within the FileManager, it will allow the rendered HTML to access locally stored media.
If you don't have the html file stored locally for some reason(I assume you do), You could write the html sting into a .html file, then point to the URL of that file. However, this is just subverting Apple's protection, so do it at your own peril (don't do it).
This is just the solution that worked for me and my understanding of why we're having the problem to begin with.
Edit #1: Typo.
Edit #2: I've since found another nuance, When stating the 'allowingReadAccessTo:' URL, if the HTML itself needs to access things in parent folders (ie: .css, .js files), you need to specify the parent folder, not necessarily the location of the HTML itself, this will then implicitly allow access to the child folders as required also. For me, this problem was only apparent on a physical device, this didn't seem to have an effect whilst running in simulator, likely another discrepancy between how permissions work on simulator and a physical device.
Personally, I had to switch to using XWebView as the out-of-the-box behavior of WKWebView does not allow loading of local files. XWebView tricks it by loading up a local web server in the background and directing local traffic thru it. (XWebView is based on top of WKWebView)
Seems a bit overkill, but that is what I ended up having to do.
I've been experimenting with this as well, with similar restrictions, and the problem appears to be that paths aren't resolved unless baseURL references the application bundle. It doesn't work if you, for example, have something in the application's documents.
Edit: I have filed a radar for this rdar://29130863
Well you should be able to use local images and CSS files (and JavaScript files for that matter) with WKWebViews with the function that you have already found. My guess is that the problem is with your baseURL variable.
Update 7.5.2017:
I have completely updated the code from another SO answer of mine that used to be linked to my answer here. I have a working project for loadHTMLString() and .loadFileURL()
Try to create baseURL using:
let baseURL = URL(fileURLWithPath: "#path#")
instead of:
let baseURL = URL(string: "#path#")
The main difference is that the first method adds file:// prefix before the path.
You can base64 encode the images... I know that works. Not sure if it will be appropriate for your use case though.
Kind of funny, I just ran into this problem while doing the opposite - moving from base64 encoded to image files.
When I used UIWebview, I used baseURL as,
let baseUrl = NSURL(string: Bundle.main.path(forResource: "cms", ofType: "html")!)! as URL
webView.loadHTMLString(bodyPage, baseURL: baseUrl)
But for the WKWebView, I used baseURL as
let baseUrl = Bundle.main.bundleURL
webView.loadHTMLString(bodyPage, baseURL: baseUrl)
This works for me.
I know this is quite old already, but I ran into the exact same problem and it took me hours of trials and even to find this thread with the same problem (Xamarin Forms App)
My issue was: parsing remote HTML content into a string and also adding locally saved images (also downloaded dynamically, no resource of the app). On the simulator all works well, but on acutal device the local images are not showing (also no ? or anything indicating an error, just a blank frame). The Xamarin webview also offers the "BaseURL" option which didn't help, also not to use the BaseURL on the custom iOS wkWebView.
The only working solution as pointed out by Scott above, is to write the HTML into a file and then use the "LoadFileUrl" function and allow read access to the base directory. This also works with absolute file paths for images in the HTML (not only relative to the basedir, but of course somewhere within the basedir).
My custom webview renderer to load web and local content looks like this now:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) {
base.OnElementPropertyChanged(sender, e);
NSUrl baseURL = new NSUrl(App.dirNews, true);
string viewFile = Path.Combine(App.dirNews, "view.html");
NSUrl fileURL = new NSUrl(viewFile, false);
switch (e.PropertyName) {
case "Url":
System.Console.WriteLine("--- Loading Web page ---");
System.Console.WriteLine("--- " + Element.Url + " ---");
NSUrlRequest myRequest = new NSUrlRequest(new NSUrl(Element.Url), NSUrlRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData, 120);
Control.LoadRequest(myRequest);
break;
case "HTML":
System.Console.WriteLine("--- Showing HTTP content ---");
File.WriteAllText(viewFile, Element.HTML, System.Text.Encoding.UTF8);
Control.LoadFileUrl(fileURL, baseURL);
break;
}
}
I was able to reproduce a similar issue. WKWebView loads my images specially if they are located remotely, apart from my app server.
For servers that are not SSL-secured (http instead of https), you can set your info.plist as per below:
App Transport Security Settings
- Allow Arbitrary Loads in Web Content (Set to YES)
- Allow Arbitrary Loads (Set to YES)
The problem was actually in the server. The server application was either:
Changing the image src from "http://IP-or-domain/uploads/file.jpg" to "../../uploads/file.jpg"
- OR -
The image src was "http://localhost/uploads/file.jpg" or "http://127.0.0.1/uploads/file.jpg" instead of "http://YOUR-SERVER-IP-ADDRESS/uploads/file.jpg"
In these cases, the actual device wont be able to locate the image. This only works with iOS Simulator because the virtual device is the same as the server and development machine. It can read LOCALHOST and 127.0.0.1.
In my server, I was using a Rich Text Editor (TinyMCE) and it automatically removes the IP address after it detects that it's the same source.
WKWebView can load image or css file from NSTemporaryDirectory, so you can copy your files to NSTemporaryDirectory, and then load it. It works for me on iOS 14! see this issue. ios-wkwebview-loadhtmlstring-baseurl-fails-to-load-images-and-read-css
It took me a while to figure this out, but based on this answer I got it working:
https://stackoverflow.com/a/73519282/5868066
Try this:
let htmlPath = URL(fileURLWithPath: "")
let htmlDirectory = htmlPath.deletingLastPathComponent()
let htmlString = try! String(contentsOfFile: htmlPath.path, encoding: .utf8)
let baseURL = URL(fileURLWithPath: htmlDirectory)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[.zero]
webView.loadFileURL(htmlPath, allowingReadAccessTo: documentsDirectory)
webView.loadHTMLString(htmlString, baseURL: baseURL)

How to talk with main thread swift files to access main thread variables

I am a very experienced Android dev but very new to iOS dev using swift. Right now I have setup a UIWebview and a NSURLProtocol using canInitWithRequest (2 separate swift files). The canInitWithRequest method works great but I need to acccess the UIWebview object in order to call functions on the UIWebview object.
For example. if request == google.com then UIWebview.something().
In Android in order to talk with the Main activity thread I just simply create a interface, implement it in my main thread and now I have access to everything on that main activity class.
Thanks for any help.
You may be looking for the method -webView(_:shouldStartLoadWithRequest:navigationType:) in the UIWebViewDelegate protocol.
By implementing this interface, you can have a different object (e.g. the object in your "separate Swift file") determine whether or not to load an NSURLRequest and even do run custom behavior first:
extension MyObject: UIWebViewDelegate {
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.URL == NSURL(string: "google.com") {
// Do your custom behavior...
webView.something()
}
// Allow the webView to load the request
return true
}
}

NSURL/NSURLRequest is unexpectedly nil

I'm trying to load a PDF into a UIWebView, but when using loadRequest, I'm getting the error unexpectedly found nil while unwrapping an Optional. My relevant code is below:
let url = NSURL(string: "http://www.urartuuniversity.com/content_images/pdf-sample.pdf")!
println(url.fileURL)
println("The URL is \(url.absoluteURL!)")
let request = NSURLRequest(URL: url)
webView.loadRequest(request)
The second line of code outputs false (unsurprisingly), but the third outputs the correct URL. webView is a connected UIWebView.
I've looked at a few examples of similar errors yet none seemed applicable, especially because the NSURL appears to be working due to the URL.absoluteURL! correctly outputting, as did numerous other similar commands.
I've also seen references to RFC 2396, but when looking at W3's documentation, my URL appears to be following these guidelines, but if it doesn't, then please tell me what the format is and how to change the URL (because I will have to literally change thousands more, and this PDF is merely an example PDF I found online).
When looking in the debug section, url is a NSURL and the place holding its value says the URL of the PDF, rather than a memory address, which could contribute partly to some of my problems.
Edit: To make my situation even weirder, because I'm using a universal app with a split-view and a table view, when I load an iPad, the PDF loads correctly, but the moment I go back to the master view and click on something, reloading the detail view, the fatal exception is found. If this made no sense, please tell me and I'll try to make it more comprehensible.
The problem is that you do not know what is nil. Add more logging, like this:
let url = NSURL(string: "http://www.urartuuniversity.com/content_images/pdf-sample.pdf")!
let request = NSURLRequest(URL: url)
println(url)
println(request)
println(webView)
webView.loadRequest(request)
In this way, by putting your app through its paces and trying to reproduce the crash, you will discover, just before the crash, exactly what is nil. I'm guessing it's the webView, but guessing is not programming — and the problem is that, so far, you are just guessing. Don't guess. You have a debugger! Debug!!!

GET UIWebView URL ! (myWebView.request.URL.absoluteString is not working)

I want to get the URL of my webview. I searched for solutions, the only one was
NSString *myUrl = webView.request.URL.absoluteString ;
This is not working anymore ! it's giving me (null) as a result.
I'm developing for iOS 5.
There is nothing wrong with the absoluteString method, but webView.request is probably nil as well. It seems that the request property of UIWebView is nil until the request has been loaded.
If it's not a problem to wait for the webview to complete the loading, use the delegate method -(void)webViewDidFinishLoad:(UIWebView *)webView to get your URL. If you need it before that, you should get it from another place in your code (for example, where the webview's loadRequest is being called).

Resources