WKWebView does not load NSURLs created with fileURLWithPath:relativeToURL: - ios

I noticed that WKWebView does not load (via loadFileURL:allowingReadAccessToURL:) an NSURL created with fileURLWithPath:relativeToURL:.
I created a github repo showing this behavior: https://github.com/davidkraus/WKWebViewNSURL

As a workaround you can just pass the absolute string of the url to a new NSURL.
var theURL = NSURL(fileURLWithPath: "www/index.html", relativeToURL: folder)
// create a new NSURL
theURL = NSURL(string: theURL.absoluteString)!
webView.loadFileURL(theURL, allowingReadAccessToURL: theURL)

Related

How can I load file with hash with WKWebview

I use the code to load URL path hash fragment (such as: index.html#/some-hash-path; but WKWebview not load url.
let path = "www/index.html"
let page = "#/some-hash-path"
let url = Bundle.main.bundleURL.appendingPathComponent(path + page)
self.webView.loadFileURL(url, allowingReadAccessTo: Bundle.main.bundleURL)
It seem that it convert # -> %23 which make invalid path
I got stuck too trying to build the url in an apparently correct way for local files.
The url must be created with URL(string:) initializer, prefixing it manually with file://, and loaded with loadFileURL().
let url = URL(string: "file://\(indexPath)#\(section)")
webView.loadFileURL(url, allowingReadAccessTo: Bundle.main.bundleURL)
Construct URL from string work as expected:
let path = "www/index.html"
let page = "#/some-hash-path"
guard let parsedUrl = URL(string: Bundle.main.bundleURL.absoluteString + path + page) else {
return
}
self.webView.loadFileURL(parsedUrl, allowingReadAccessTo: Bundle.main.bundleURL)

Issue deep linking to MS Excel from iOS app

I am trying to open an Excel document that is located on a server. I wrote the following code but it always returns false for UIApplication.shared.canOpenURL(url as URL)
I think I am missing some requirement for deep linking to Excel. Why is iOS not able to understand ms-excel:ofe|u| format?
#objc static func openExcel() {
let originalString = "http://s000.tinyupload.com/download.php?file_id=23290165129849240725&t=2329016512984924072514118"
let encodedString = originalString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
let encodedURLString = "ms-excel:ofe|u|" + encodedString! + "|n|TestDoc.xlsx|a|App"
if let url = NSURL(string: encodedURLString),
UIApplication.shared.canOpenURL(url as URL) {
UIApplication.shared.openURL(url as URL)
} else if let itunesUrl = NSURL(string: "https://itunes.apple.com/us/app/microsoft-excel/id586683407?mt=8&uo=4"), UIApplication.shared.canOpenURL(itunesUrl as URL) {
UIApplication.shared.openURL(itunesUrl as URL)
}
}
I have analyzed your code and found some mistakes. First, your URL was redirecting to somewhere, as per Microsoft documentation it can't handle redirecting URL's
The URL has to be encoded and must be a direct link to the file (not a
redirect). If the URL is in a format that Office cannot handle, or the
download simply fails, Office will not return the user to the invoking
application.
Here is Microsoft Documentation Link
The second mistake was you are only encoding the URL string containing site URL, you should consider the part after the scheme ms-excel: as a URL and should be encoded.
Because of improper encoding the let url = URL(string: encodedURLString) results nil that's why it is not working as expected.
Here is an example working code:
#objc static func openExcel() {
//replace the below url with yours. may be this one dosen't work
let originalString = "ofe|u|https://pgcconline.blackboard.com/webapps/dur-browserCheck-bb_bb60/samples/sample.xlsx"
let encodedString = originalString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let encodedURLString = "ms-excel:" + encodedString!
if let url = URL(string: encodedURLString),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.openURL(url)
} else if let itunesUrl = NSURL(string: "https://itunes.apple.com/us/app/microsoft-excel/id586683407?mt=8&uo=4"), UIApplication.shared.canOpenURL(itunesUrl as URL) {
UIApplication.shared.openURL(itunesUrl as URL)
}
}
Note: From iOS 9 you must whitelist any URL schemes your App wants to query in Info.plist under the LSApplicationQueriesSchemes key (an array of strings):
For example in our case:
When i try to open the URL in the question above I get redirected to this URL, so my guess would be that your code is fine, it just might be that your excel file you're trying to open is really an HTML page since tinyupload apparently blocks direct links to the files.
Maybe try opening a direct excel file download link, https://pgcconline.blackboard.com/webapps/dur-browserCheck-bb_bb60/samples/sample.xlsx (it was the first google result for 'xlsx file sample download')

How do I convert an assets URL into filesystem URL?

TLDR; how to convert NSURL of form "assets-library://...." to "file:///...."
In this scenario, the user takes a video in app and saves it to their images with the ALAssetsLibrary method (writeVideoAtPathToSavedPhotosAlbum). From this method upon competion, I get an assetURL in the form "assets-library://asset/asset.mov?id=F60213D1-3C39-44A8-8D21-80587D1CF843&ext=mov".
I need to convert this asset URL to a file system url with format file:///... so that I can upload the file. (fileuploader requires file system NSURL of asset)
I've tried doing this to convert the asset URL into a filesystem URL but it doesn't work
writeVideoAtPathToSavedPhotosAlbum(outputURL, completionBlock: { (assetURL:NSURL!, error:NSError!) }
......
let pathDocumentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let filePath = "\(pathDocumentDirectory)/\(assetURL.lastPathComponent!)"
let fileUrl = NSURL(fileURLWithPath: filePath)
Any ideas?

Loading an HTML file into a UIWebView using ParseConfig from Parse.com

I am currently trying to load an html file into a UIWebView via ParseConfig. As of now I am loading the file locally within the app, but I want to be able to update the content of the html file without having to submit for an Apple update each time. I was wondering if anyone had an experience loading files from ParseConfig? I have included a block of code that I am currently using to load a URL as a String from ParseConfig that works perfectly. I have also included the code I use now to load the html file locally.
Code for loading URL as String from ParseConfig
override func viewDidLoad()
{
super.viewDidLoad()
PFConfig.getConfigInBackgroundWithBlock
{
(config: PFConfig!, error: NSError!) -> Void in
let menu = config["menuLink"] as String
NSLog("Yay! The number is %#!", menu)
let requestURL = NSURL(string: menu)
let request = NSURLRequest(URL: requestURL!)
self.webView.loadRequest(request)
}
}
Code used for loading html file locally
func loadURL()
{
var requestURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("contact_your_ra", ofType: "html")!)
let request = NSURLRequest(URL: requestURL!)
webView.loadRequest(request)
}
I have loaded an HTML file stored in ParseConfig with no trouble at all. Simple store your HTML file as a string value in the Parse Config table, then load that file into the UIWebView using that loads via loadHTMLString
Something like this:
PFConfig *config = [PFConfig currentConfig];
NSString *htmlString = [config objectForKey:#"htmlString"];
[webView loadHTMLString: htmlString baseURL: nil];

WKWebView not loading local files under iOS 8

For previous iOS 8 betas, load a local web app (in Bundle) and it works fine for both UIWebView and WKWebView, and I even ported a web game using the new WKWebView API.
var url = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource("car", ofType:"html"))
webView = WKWebView(frame:view.frame)
webView!.loadRequest(NSURLRequest(URL:url))
view.addSubview(webView)
But in beta 4, I just got a blank white screen (UIWebView still work), looks like nothing is loaded or executed. I saw an error in the log:
Could not create a sandbox extension for /
Any help to guide me to the right direction? Thanks!
They finally solved the bug! Now we can use -[WKWebView loadFileURL:allowingReadAccessToURL:].
Apparently the fix was worth some seconds in WWDC 2015 video 504 Introducing Safari View Controller
For iOS8 ~ iOS10 (Swift 3)
As Dan Fabulish's answer states this is a bug of WKWebView which apparently is not being solved any time soon and as he said there is a work-around :)
I am answering just because I wanted to show the work-around here. IMO code shown in https://github.com/shazron/WKWebViewFIleUrlTest is full of unrelated details most people are probably not interested in.
The work-around is 20 lines of code, error handling and comments included, no need of a server :)
func fileURLForBuggyWKWebView8(fileURL: URL) throws -> URL {
// Some safety checks
if !fileURL.isFileURL {
throw NSError(
domain: "BuggyWKWebViewDomain",
code: 1001,
userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("URL must be a file URL.", comment:"")])
}
try! fileURL.checkResourceIsReachable()
// Create "/temp/www" directory
let fm = FileManager.default
let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("www")
try! fm.createDirectory(at: tmpDirURL, withIntermediateDirectories: true, attributes: nil)
// Now copy given file to the temp directory
let dstURL = tmpDirURL.appendingPathComponent(fileURL.lastPathComponent)
let _ = try? fm.removeItem(at: dstURL)
try! fm.copyItem(at: fileURL, to: dstURL)
// Files in "/temp/www" load flawlesly :)
return dstURL
}
And can be used as:
override func viewDidLoad() {
super.viewDidLoad()
var fileURL = URL(fileURLWithPath: Bundle.main.path(forResource:"file", ofType: "pdf")!)
if #available(iOS 9.0, *) {
// iOS9 and above. One year later things are OK.
webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
} else {
// iOS8. Things can (sometimes) be workaround-ed
// Brave people can do just this
// fileURL = try! pathForBuggyWKWebView8(fileURL: fileURL)
// webView.load(URLRequest(url: fileURL))
do {
fileURL = try fileURLForBuggyWKWebView8(fileURL: fileURL)
webView.load(URLRequest(url: fileURL))
} catch let error as NSError {
print("Error: " + error.debugDescription)
}
}
}
WKWebView can't load content from file: URLs via its loadRequest: method. http://www.openradar.me/18039024
You can load content via loadHTMLString:, but if your baseURL is a file: URL, then it still won't work.
iOS 9 has a new API that will do what you want, [WKWebView loadFileURL:allowingReadAccessToURL:].
There is a workaround for iOS 8, demonstrated by shazron in Objective-C here https://github.com/shazron/WKWebViewFIleUrlTest to copy files into /tmp/www and load them from there.
If you're working in Swift, you could try nachos4d's sample instead. (It's also much shorter than shazron's sample, so if you're having trouble with shazron's code, give that a try instead.)
An example of how to use [WKWebView loadFileURL:allowingReadAccessToURL:] on iOS 9.
When you are moving the web folder to a project, select "Create folder references"
Then use code that is something like this(Swift 2):
if let filePath = NSBundle.mainBundle().resourcePath?.stringByAppendingString("/WebApp/index.html"){
let url = NSURL(fileURLWithPath: filePath)
if let webAppPath = NSBundle.mainBundle().resourcePath?.stringByAppendingString("/WebApp") {
let webAppUrl = NSURL(fileURLWithPath: webAppPath, isDirectory: true)
webView.loadFileURL(url, allowingReadAccessToURL: webAppUrl)
}
}
In the html file use filepaths like this
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
not like this
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
An example of directory that is moved to a xcode project.
Temporary workaround: I'm using GCDWebServer, as suggested by GuidoMB.
I first find the path of my bundled "www/" folder (which contains an "index.html"):
NSString *docRoot = [[NSBundle mainBundle] pathForResource:#"index" ofType:#"html" inDirectory:#"www"].stringByDeletingLastPathComponent;
... then start it up like so:
_webServer = [[GCDWebServer alloc] init];
[_webServer addGETHandlerForBasePath:#"/" directoryPath:docRoot indexFilename:#"index.html" cacheAge:3600 allowRangeRequests:YES];
[_webServer startWithPort:port bonjourName:nil];
To stop it:
[_webServer stop];
_webServer = nil;
Performance appears fine, even on an iPad 2.
I did notice a crash after the app goes into the background, so I stop it on applicationDidEnterBackground: and applicationWillTerminate:; I start/restart it on application:didFinishLaunching... and applicationWillEnterForeground:.
[configuration.preferences setValue:#"TRUE" forKey:#"allowFileAccessFromFileURLs"];
This solved the problem for me
iOS 8.0+ dev.apple.com
also this seems to worked just fine too...
NSString* FILE_PATH = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"htmlapp/FILE"];
[self.webView
loadFileURL: [NSURL fileURLWithPath:FILE_PATH]
allowingReadAccessToURL: [NSURL fileURLWithPath:FILE_PATH]
];
Besides solutions mentioned by Dan Fabulich, XWebView is another workaround. [WKWebView loadFileURL:allowingReadAccessToURL:] is implemented through extension.
I cannot comment yet, so I am posting this as a separate answer.
This is an objective-c version of nacho4d's solution. The best workaround I've seen so far.
- (NSString *)pathForWKWebViewSandboxBugWithOriginalPath:(NSString *)filePath
{
NSFileManager *manager = [NSFileManager defaultManager];
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"www"];
NSError *error = nil;
if (![manager createDirectoryAtPath:tempPath withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(#"Could not create www directory. Error: %#", error);
return nil;
}
NSString *destPath = [tempPath stringByAppendingPathComponent:filePath.lastPathComponent];
if (![manager fileExistsAtPath:destPath]) {
if (![manager copyItemAtPath:filePath toPath:destPath error:&error]) {
NSLog(#"Couldn't copy file to /tmp/www. Error: %#", error);
return nil;
}
}
return destPath;
}
In the case that you are trying to display a local image in the middle of a larger HTML string like: <img src="file://...">, it still does not appear on device so I loaded the image file into NSData and was able to display it by replacing the src string with the data itself. Sample code to help build the HTML string to load into WKWebView, where result is what will replace what's inside the quotes of src="":
Swift:
let pathURL = NSURL.fileURLWithPath(attachmentFilePath)
guard let path = pathURL.path else {
return // throw error
}
guard let data = NSFileManager.defaultManager().contentsAtPath(path) else {
return // throw error
}
let image = UIImage.init(data: data)
let base64String = data.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
result += "data:image/" + attachmentType + "base64," + base64String
var widthHeightString = "\""
if let image = image {
widthHeightString += " width=\"\(image.size.width)\" height=\"\(image.size.height)\""
}
result += widthHeightString
Objective-C:
NSURL *pathURL = [NSURL fileURLWithPath:attachmentFilePath];
NSString *path = [pathURL path];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:path];
UIImage *image = [UIImage imageWithData:data];
NSString *base64String = [data base64EncodedStringWithOptions:0];
[result appendString:#"data:image/"];
[result appendString:attachmentType]; // jpg, gif etc.
[result appendString:#";base64,"];
[result appendString:base64String];
NSString *widthHeightString = #"\"";
if (image) {
widthHeightString = [NSString stringWithFormat:#"\" width=\"%f\" height=\"%f\"", image.size.width, image.size.height];
}
[result appendString:widthHeightString];
I'm using the below. Has some extra stuff I'm working on but you can see where I've commented out the loadRequest and am substituting loadHTMLString call. Hope this helps until they fix the bug.
import UIKit
import WebKit
class ViewController: UIViewController, WKScriptMessageHandler {
var theWebView: WKWebView?
override func viewDidLoad() {
super.viewDidLoad()
var path = NSBundle.mainBundle().pathForResource("index", ofType: "html", inDirectory:"www" )
var url = NSURL(fileURLWithPath:path)
var request = NSURLRequest(URL:url)
var theConfiguration = WKWebViewConfiguration()
theConfiguration.userContentController.addScriptMessageHandler(self, name: "interOp")
theWebView = WKWebView(frame:self.view.frame, configuration: theConfiguration)
let text2 = String.stringWithContentsOfFile(path, encoding: NSUTF8StringEncoding, error: nil)
theWebView!.loadHTMLString(text2, baseURL: nil)
//theWebView!.loadRequest(request)
self.view.addSubview(theWebView)
}
func appWillEnterForeground() {
}
func appDidEnterBackground() {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func userContentController(userContentController: WKUserContentController!, didReceiveScriptMessage message: WKScriptMessage!){
println("got message: \(message.body)")
}
}
For who must workaround this issue under iOS8:
If your page is not complicated, you might choose to make the page as a Single Page Application.
In other words, to embed all the resources into the html file.
To do:
1. copy your js/css file's content into / tags in the html file respectively;
2. convert your image files into svg to replace the accordingly.
3. load the page as before, using [webView loadHTMLString: baseURL:], for example
It was a bit different to styling a svg image, but it should not block you so much.
It seemed that the page render performance decreased a bit, but it was worthy to have such a simple workaround worked under iOS8/9/10.
In the same line of GCDWebServer, I am using SImpleHttpServer (http://www.andyjamesdavies.com/blog/javascript/simple-http-server-on-mac-os-x-in-seconds) and then loadRequest with the localhost url. With this approach you do not have to add any library, but the website files won't be in the bundle so It will not be deliverable. Because of that, this would be more appropriate for Debug cases.
I’ve managed to use PHP’s web server on OS X. Copying to the temporary/www directory did not work for me. The Python SimpleHTTPServer complained about wanting to read MIME types, probably a sandboxing issue.
Here’s a server using php -S:
let portNumber = 8080
let task = NSTask()
task.launchPath = "/usr/bin/php"
task.arguments = ["-S", "localhost:\(portNumber)", "-t", directoryURL.path!]
// Hide the output from the PHP server
task.standardOutput = NSPipe()
task.standardError = NSPipe()
task.launch()
#nacho4d solution is good. I want to change it a little but I don't know how to change it in your post. So I put it here I hope you don't mind. thanks.
In case you have a www folder there are many other files such as png, css, js etc. Then you have to copy all files to tmp/www folder.
for example, you have a www folder like this:
then in Swift 2.0:
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().resourcePath! + "/www";
var fileURL = NSURL(fileURLWithPath: path)
if #available(iOS 9.0, *) {
let path = NSBundle.mainBundle().pathForResource("index", ofType: "html", inDirectory: "www")
let url = NSURL(fileURLWithPath: path!)
self.webView!.loadRequest(NSURLRequest(URL: url))
} else {
do {
fileURL = try fileURLForBuggyWKWebView8(fileURL)
let url = NSURL(fileURLWithPath: fileURL.path! + "/index.html")
self.webView!.loadRequest( NSURLRequest(URL: url))
} catch let error as NSError {
print("Error: \(error.debugDescription)")
}
}
}
the function fileURLForBuggyWKWebView8 is copied from #nacho4d:
func fileURLForBuggyWKWebView8(fileURL: NSURL) throws -> NSURL {
// Some safety checks
var error:NSError? = nil;
if (!fileURL.fileURL || !fileURL.checkResourceIsReachableAndReturnError(&error)) {
throw error ?? NSError(
domain: "BuggyWKWebViewDomain",
code: 1001,
userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("URL must be a file URL.", comment:"")])
}
// Create "/temp/www" directory
let fm = NSFileManager.defaultManager()
let tmpDirURL = NSURL.fileURLWithPath(NSTemporaryDirectory())
try! fm.createDirectoryAtURL(tmpDirURL, withIntermediateDirectories: true, attributes: nil)
// Now copy given file to the temp directory
let dstURL = tmpDirURL.URLByAppendingPathComponent(fileURL.lastPathComponent!)
let _ = try? fm.removeItemAtURL(dstURL)
try! fm.copyItemAtURL(fileURL, toURL: dstURL)
// Files in "/temp/www" load flawlesly :)
return dstURL
}
Try using
[webView loadHTMLString:htmlFileContent baseURL:baseURL];
Seems it's still working. Yet.

Resources