WKWebView not loading local files under iOS 8 - ios

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.

Related

AVPlayer Stops Playing AES encrypted offline HLS Video in online mode

I have written a code to download HLS video and play it in offline mode.
This code works fine for encoded video. Now I have a video which is AES encrypted and we are having custom encryption key for it. After downloading AES encrypted HLS video I am using below given code to supply key for decryption of video.
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
NSString *scheme = loadingRequest.request.URL.scheme;
if ([scheme isEqualToString:#"ckey"]) {
NSString *request = loadingRequest.request.URL.host;
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:request];
if (data) {
[loadingRequest.dataRequest respondWithData:data];
[loadingRequest finishLoading];
} else {
// Data loading fail
}
}
return NO; }
I am intercepting a request for a key and passing key stored in UserDefaults for decryption.
This AES encrypted HLS video with custom key plays well when my device's wifi or data connection is off.
If I start playing this video when my device's wifi or data connection is enabled or if
I enable my device's wifi or data connection while playing video; video stops playing immediately without any error and never plays again.
I have checked accessLog and errorLog of playerItem but haven't found anything helpful.
To provide a custom URL key after downloading of HLS content I am updating a content of .m3u8 file by replacing
URI="..."
string with
URI="ckey://..."
Is this a correct way to provide key for AES encrypted video?
and what could be the reason of this behaviour and how to solve this issue?
Thanks in advance.
Finally I managed to solve this issue. Rough package structure of downloaded HLS video is like given below:
HLS.movpkg
|_ 0-12345
|_ 123.m3u8
|_ StreamInfoBoot.xml
|_ StreamInfoRoot.xml
|_ <>.frag
|_ boot.xml
boot.xml contains network URL for HLS (which is https: based)
StreamBootInfo.xml contains mapping between HLS URL (which is https: based) and .frag file downloaded locally.
In offline mode HLS video was playing perfectly. But when network connection was enabled it was referring to https: URL instead of local .frag files.
I replaced https: scheme in these files with custom scheme (fakehttps:) to restrict AVPlayer going online for resources.
This thing solved my issue but I don't know the exact reason behind it and how HLS is played by AVPlayer.
I referred this and got some idea so tried something .
I am updating this answer further to explain how to play encrypted video in offline mode.
Get the key required for video decryption.
Save that key some where.
You can save that key as NSData or Data object in UserDefault I am using video file name as key to save key data in UserDefaults.
Use FileManager API to iterate over all the files inside .movpkg.
Get the content of each .m3u8 file and replace URI="some key url" with URI="ckey://keyusedToSaveKeyDataInUserDefaults"
You can refer code given below for this process.
if let url = asset.asset?.url, let data = data {
let keyFileName = "\(asset.contentCode!).key"
UserDefaults.standard.set(data, forKey: keyFileName)
do {
// ***** Create key file *****
let keyFilePath = "ckey://\(keyFileName)"
let subDirectories = try fileManager.contentsOfDirectory(at: url,
includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants)
for url in subDirectories {
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) {
if isDirectory.boolValue {
let path = url.path as NSString
let folderName = path.lastPathComponent
let playlistFilePath = path.appendingPathComponent("\(folderName).m3u8")
if fileManager.fileExists(atPath: playlistFilePath) {
var fileContent = try String.init(contentsOf: URL.init(fileURLWithPath: playlistFilePath))
let stringArray = self.matches(for: "URI=\"(.+?)\"", in: fileContent)
for pattern in stringArray {
fileContent = fileContent.replacingOccurrences(of: pattern, with: "URI=\"\(keyFilePath)\"")
}
try fileContent.write(toFile: playlistFilePath, atomically: true, encoding: .utf8)
}
let streamInfoXML = path.appendingPathComponent("StreamInfoBoot.xml")
if fileManager.fileExists(atPath: streamInfoXML) {
var fileContent = try String.init(contentsOf: URL.init(fileURLWithPath: streamInfoXML))
fileContent = fileContent.replacingOccurrences(of: "https:", with: "fakehttps:")
try fileContent.write(toFile: streamInfoXML, atomically: true, encoding: .utf8)
}
} else {
if url.lastPathComponent == "boot.xml" {
let bootXML = url.path
if fileManager.fileExists(atPath: bootXML) {
var fileContent = try String.init(contentsOf: URL.init(fileURLWithPath: bootXML))
fileContent = fileContent.replacingOccurrences(of: "https:", with: "fakehttps:")
try fileContent.write(toFile: bootXML, atomically: true, encoding: .utf8)
}
}
}
}
}
userInfo[Asset.Keys.state] = Asset.State.downloaded.rawValue
// Update download status to db
let user = RoboUser.sharedObject()
let sqlDBManager = RoboSQLiteDatabaseManager.init(databaseManagerForCourseCode: user?.lastSelectedCourse)
sqlDBManager?.updateContentDownloadStatus(downloaded, forContentCode: asset.contentCode!)
self.notifyServerAboutContentDownload(asset: asset)
NotificationCenter.default.post(name: AssetDownloadStateChangedNotification, object: nil, userInfo: userInfo)
} catch {
}
}
func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
This will update your download package structure for playing encrypted video in offline mode.
Now last thing to do is implement below given method of AVAssetResourceLoader class as follows
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
NSString *scheme = loadingRequest.request.URL.scheme;
if ([scheme isEqualToString:#"ckey"]) {
NSString *request = loadingRequest.request.URL.host;
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:request];
if (data) {
loadingRequest.contentInformationRequest.contentType = AVStreamingKeyDeliveryPersistentContentKeyType;
loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
loadingRequest.contentInformationRequest.contentLength = data.length;
[loadingRequest.dataRequest respondWithData:data];
[loadingRequest finishLoading];
} else {
// Data loading fail
}
}
return YES;
}
This method will provide key to video while playing to decrypt it.

Retrieving and Parsing Text From Specific Webpage Using Swift

I need to retrieve the text from a specific website. However, I only need a few parts of it. How can I accomplish this using swift.
I have found the following in objective-c, but am not sure it provides how to reference it from a specific site:
NSString *webString = [webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.innerText"];
NSScanner *stringScanner = [NSScanner scannerWithString:webString];
NSString *content = [[NSString alloc] init];
while ([stringScanner isAtEnd] == NO) {
[stringScanner scanUpToString:#"Start of the text you want" intoString:null];
[stringScanner scanUpToString:#"End of the text you want" intoString:&content];
}`
I have put an example of what I mean below:
Again, I would like to accomplish this using Swift.
If your HTML was easily targetable with identifiers or class names, I would suggest using a library such as Kanna. But I've had a look at your page and the text you need is lost amidst an ocean of divs...
So I've quickly hacked a way to get your text with componentsSeparatedByString: I'm cutting the HTML in blocks until I get to the part we're interested in.
Note that it's far from being the most efficient way: instead of using componentsSeparatedByString you should come with a way of identifying the HTML block you want and search for it with NSScanner.
That being said, here's my example of a working hack, tested in a Playground:
enum CustomErrors : String, ErrorType {
case InvalidURL = "Invalid URL"
}
do {
let str = "http://www.golfwrx.com/328370/mizuno-to-offer-custom-grips-at-no-additional-charge/"
guard let url = NSURL(string: str) else { throw CustomErrors.InvalidURL }
let html = try String(contentsOfURL: url)
let separator1 = "<div class='mailmunch-forms-before-post' style='display: none !important;'></div><p>"
let temp = html.componentsSeparatedByString(separator1)
let separator2 = "</p>\n<p>"
let temp2 = temp[1].componentsSeparatedByString(separator2)
let separator3 = "</p><div class='mailmunch-forms-in-post-middle'"
let separated = temp2[1].componentsSeparatedByString(separator3)
let result = separated[0]
print(result)
} catch {
print(error)
}
Note: my example is in Swift 2 (Xcode 7).
Sorry about the specifics, I'm an Objective-C guy. but, here is an example of how to use NString to get the contents of a websites HTML
NSString *url = #"http://www.example.com"; // Your URL
NSURL *urlRequest = [NSURL URLWithString:url]; // Make a request with your URL
NSError *err = nil; // Error handler
NSString *html = [NSString stringWithContentsOfURL:urlRequest encoding:NSUTF8StringEncoding error:&err]; // Try to get the HTML in the string
if(err)
{
//Do something as it didn't work! Maybe a connection problem
}
else
{
// Use NScanner on html string
}
http://nshipster.com/nsscanner/ is a good place to learn about NScanner for swift
EDIT: Here is the above translated to swift
var err: NSError? // Error handler
let url: NSURL = NSURL(string: "http://www.example.com") // NSURL, put your website URL in here
let string = NSString(contentsOfURL: url, encoding: NSUTF8StringEncoding, error: &err) // String will now hold your HTML
// Now use NScanner (See Link) to parse the HTML output
My swift is rusty. but this might help you. This is roughly translated but outlines exactly what you need

AVAssetExportSession fails to convert .mov from photo library. Why?

Scenario:
I wish to reduce the size of individual videos from my iTouch photo library.
1. Collect videoAssets from library.
2. Get a thumbnail of the PHAsset - works.
3. Get the actual video from the library.
4. Request the AVAssetForVideo from the library.
5. Convert the video via ExportSessions... loading assorted parameters.
6. Attempt to run the export into a tmp directory for use.
* FAILS *
Here's the debug output:
Here's the error message:
func getVideoFromPhotoLibrary() {
let videoAssets = PHAsset.fetchAssetsWithMediaType(.Video, options:nil)
videoAssets.enumerateObjectsUsingBlock {
(obj:AnyObject!, index:Int, stop:UnsafeMutablePointer<ObjCBool>) in
let mySize = CGSizeMake(120,120)
let myAsset = obj as! PHAsset
let imageManager = PHImageManager.defaultManager()
var myVideo:BlissMedium?
// Request the poster frame or the image of the video
imageManager.requestImageForAsset(myAsset, targetSize:mySize, contentMode: .AspectFit, options: nil) {
(imageResult, info) in
let thumbnail = UIImage(named:"videoRed")
myVideo = BlissMedium(blissImage: imageResult, creationDate:myAsset.creationDate)
myVideo!.mediumType = .video
}
// Actual Video:
imageManager.requestAVAssetForVideo(myAsset, options: nil, resultHandler: {result, audio, info in
let asset = result as! AVURLAsset
let mediaURL = asset.URL
let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality)
let filename = "composition.mp4"
session.outputURL = NSURL(string: NSTemporaryDirectory());
session.outputFileType = AVFileTypeQuickTimeMovie;
session.exportAsynchronouslyWithCompletionHandler({ () -> Void in
dispatch_async(dispatch_get_main_queue(), {
if session.status == AVAssetExportSessionStatus.Completed {
println("Success")
}
else {
println(session.error?.localizedDescription)
//The requested URL was not found on this server.
}
})
})
})
if nil != myVideo {
self.gBlissVideoMedia.append(myVideo!)
}
}
}
I checked to be sure the target path/file exist; then I added the 'AVFileTypeMPEG4' output type to match the intended .mp4:
let targetDir = createTempDirectory("bliss/composition.mp4") as String?
if NSFileManager.defaultManager().fileExistsAtPath(targetDir!) {
println("*** file exists! ***")
} else {
return
}
session.outputURL = NSURL(string: targetDir!);
session.outputFileType = AVFileTypeMPEG4
I'm still having problems:
* file exists! *
Optional("The operation could not be completed")
What am I doing wrong; what's missing?
Update:
I'm able to successfully run the export to my NSHomeDirectory() vs NSTemporaryDictory() in Objective-C.
However... the same code written in Swift fails.
I notice a change in absolute path to the target output in Swift, not found in Objective-C:
Perhaps it's a Swift 1.2 bug???
I am not sure if you can save in the root of the temp directory, I normally use this function to create a new temp directory that I can use:
func createTempDirectory(myDir: String) -> String? {
let tempDirectoryTemplate = NSTemporaryDirectory().stringByAppendingPathComponent(myDir)
let fileManager = NSFileManager.defaultManager()
var err: NSErrorPointer = nil
if fileManager.createDirectoryAtPath(tempDirectoryTemplate, withIntermediateDirectories: true, attributes: nil, error: err) {
return tempDirectoryTemplate
} else {
return nil
}
}
Try to make your conversion in the directory returned by this function.
I hope that helps you!
I didn't quite understand what that last part of code did, where you find out if a file exists or not. Which file is it you are locating?
Since I didn't understand that then this might be irrelevant, but in your topmost code I notice that you set the filename to composition.mp4, but let the outputURL be NSURL(string: NSTemporaryDirectory()). With my lack of Swiftness I might be missing something, but it seems to me as if you're not using the filename at all, and are trying to write the file as a folder. I believe setting a proper URL might fix the problem but I'm not sure. An Objective-c-example of this could be:
NSURL * outputURL = [[NSURL alloc]
initFileURLWithPath:[NSString pathWithComponents:
#[NSTemporaryDirectory(), #"composition.mp4"]]];
The outputURL is supposed to point to the actual file, not the folder it lies in. I think..
Anyway, if that doesn't work I do have a few other thoughts as well.
Have you tried it on an actual device? There may be a problem with the simulator.
Also, sadly, I have gotten the error -12780 countless times with different root-problems, so that doesn't help very much.
And, I see you check if session.status == AVAssetExportSessionStatus.Completed, have you checked what the actual status is? Is it .Failed, or perhaps .Unknown? There are several statuses.
This might be a long shot, but in one of my apps I am using the camera to capture video/audio, then encode/convert it using AVAssetExportSession. There were strange errors when starting to record, as well as after recording(exporting). I found out that I could change the AVAudioSession, which apparently has something to do with how the device handles media.
I have no idea how to Swift, but here's my code (in viewDidAppear of the relevant view)
NSError *error;
AVAudioSession *aSession = [AVAudioSession sharedInstance];
[aSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
[aSession setMode:AVAudioSessionModeVideoRecording error:&error];
[aSession setActive: YES error: &error];
The category PlayAndRecord allowed me to start the camera much faster, as well as getting rid of the occasional hanging AVAssetExportSessionStatus.Unknown and the occasional crash .Failed (which also threw the -12780-error).

How to get the html content from UIWebView?

I'm loading a web page using UIWebView
let urlStr = "http://duckduckgo.com"
let urlReq = NSMutableURLRequest(URL: NSURL(string: urlStr)!)
webView.loadRequest(urlReq)
Then, when the page has finished loading, I want to access the html content
func webViewDidFinishLoad(webView: UIWebView) {
let href = webView.stringByEvaluatingJavaScriptFromString("window.location.href")
println("window.location.href = \(href)")
let doc = webView.stringByEvaluatingJavaScriptFromString("document")
println("document = \(doc)")
}
But document just return an empty string (Optional("")). The window.location.href part is working fine. What I'm I doing wrong?
I think you have to evaluate the javascript like this:
let doc = webView.stringByEvaluatingJavaScriptFromString("document.documentElement.outerHTML")
In this case you get the entire HTML.
Now, when UIWebView is deprecated, you'll need to use syntax like this:
webView.evaluateJavaScript("document.documentElement.outerHTML") { (html, error) in
guard let html = html as? String else {
print(error)
return
}
// Here you have HTML of the whole page.
}
After that you can create a function like this that'll easily get HTML from webView:
func getHTML(_ completion: #escaping (String) -> ()) {
webView.evaluateJavaScript("document.documentElement.outerHTML") { (html, error) in
guard let html = html as? String else {
print(error)
return
}
completion(html)
}
}
getHTML { html in
// Here you have HTML string
print(html) // for example...
}
I Found the solution of problem regarding jqxWidget.
That UIWebView content were referencing .js and .css file which is already bundled in App. I had just only add the base url of App's MainBundle.
NSString *html = [self.webview1 stringByEvaluatingJavaScriptFromString: #"document.documentElement.outerHTML"]; //document.body.innerHTML
NSString *path = [[NSBundle mainBundle] bundlePath];
NSURL *baseURL = [NSURL fileURLWithPath:path];
[objFullScreenViewCntrl.webview2 loadHTMLString:html baseURL:baseURL];

How to Clear PFFIle Cache (Parse.com)

I have an app that downloads and displays a lot of images from parse. An image is added to the database almost every minute. Since PFFile is automatically cached with no expiration date, even though I only need to display recent images, the older images still stay in cache thus occupying a lot of storage space. Because of this now the app takes about 5GB of storage on my iPhone. I have been doing a lot of research on this issue and found out that Parse does not have a public api for cleaning up PFFile Cache and also it doesn't allow setting expiration date on the cached files. Is there a workaround on this where I could manually delete older cache data?
Thank you in advance.
Here is a method you can use to clean up the PFFile Cache. If you call it when your app starts, before initializing Parse, I think it should be safe. You can check the file creation date in the for loop if you don't want to remove everything.
+ (void)cleanUpPFFileCacheDirectory
{
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *cacheDirectoryURL = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *PFFileCacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:#"Parse/PFFileCache" isDirectory:YES];
NSArray *PFFileCacheDirectory = [fileManager contentsOfDirectoryAtURL:PFFileCacheDirectoryURL includingPropertiesForKeys:nil options:0 error:&error];
if (!PFFileCacheDirectory || error) {
if (error && error.code != NSFileReadNoSuchFileError) {
NSLog(#"Error : Retrieving content of directory at URL %# failed with error : %#", PFFileCacheDirectoryURL, error);
}
return;
}
for (NSURL *fileURL in PFFileCacheDirectory) {
BOOL success = [fileManager removeItemAtURL:fileURL error:&error];
if (!success || error) {
NSLog(#"Error : Removing item at URL %# failed with error : %#", fileURL, error);
error = nil;
}
}
}
TimWhiting's answer translated to Swift 2.1:
Note: I have to say thought that is better to use file urls and use your own cache system as Matt S says, I'm using this just for testing purposes. I wish also that Parse could provide us with the correct path instead of having to hardcode it, that's why I think is better to use URLs.
func cleanUpParseDirectory(){
let parseFilesCache = "Parse/PFFileCache"
let fileManager = NSFileManager.defaultManager()
let cacheDirectoryURL = fileManager.URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
let PFFileCacheDirectoryURL = cacheDirectoryURL[0].URLByAppendingPathComponent(parseFilesCache, isDirectory: true)
do {
let PFFileCacheDirectory = try fileManager.contentsOfDirectoryAtURL(PFFileCacheDirectoryURL, includingPropertiesForKeys: nil, options: [])
print("number of cached files: \(PFFileCacheDirectory.count)")
for fileURL in PFFileCacheDirectory {
try fileManager.removeItemAtURL(fileURL)
}
print("Success removing items")
} catch let error {
print("Error: \(error)")
}
}
Per this answer from Hector: https://www.parse.com/questions/pffile-cache-size you can manually clear your applications ~/Library/Caches folder if you are insistent upon it. However, I'm fairly certain this will also impact things like NSURL/AFNetworking caches, amongst others.
My suggestion? Don't use PFFile to download the file. PFFile gives you back the remote URL to where it's hosted on Parse's site, so you can pass that to something like AFNetworking or NSURLSession to actually download the image for you, and then you can then assign cache lifetimes (or manage it yourself) since those systems actually support that, unlike PFFile.
deadbeef's answer translated to Swift for anyone that needs it.
func cleanUpParseDirectory(){
var error: NSError?
var fileManager: NSFileManager = NSFileManager.defaultManager()
var cacheDirectoryURL: [NSURL] = fileManager.URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask) as! [NSURL]
var PFFileCacheDirectoryURL: NSURL = cacheDirectoryURL[0].URLByAppendingPathComponent("Parse/PFFileCache", isDirectory: true)
var PFFileCacheDirectory: [AnyObject]? = fileManager.contentsOfDirectoryAtURL(PFFileCacheDirectoryURL, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.allZeros, error: &error)// [fileManager contentsOfDirectoryAtURL:PFFileCacheDirectoryURL includingPropertiesForKeys:nil options:0 error:&error];
if (PFFileCacheDirectory == nil || error != nil) {
if ((error != NSFileReadNoSuchFileError && error!.code != NSFileReadNoSuchFileError)) {
println("error finding path")
} else {
println("no error finding path")
}
return
}
println("number of cached files: \(PFFileCacheDirectory!.count)")
for fileURL in PFFileCacheDirectory! {
var success: Bool = fileManager.removeItemAtURL(fileURL as! NSURL, error: &error)
if ((!success != false || error != nil) ) {
println("error removing item")
error = nil
} else {
println("success removing item")
}
}
}

Resources