How do I retrieve cached files in iOS swift? - ios

Thanks for the help in my last question. This time I would like to ask for help again for an application whose contents need to be downloaded and cached when it's opened for the first time.
Indeed it's a web app where the view controller consists of a WebView. In order to cache the whole website (which consists of "index.htm", "first.htm, "second.htm" and etc), I have scraped the whole site using the Kanna library and hence generated numerous links (generatedURL). Then I write the HTML of each link into a single file using the approach answered here. Read and write data from text file
Here is my code in the didFinishLaunchingWithOptions of AppDelegate.swift.
// get the documents folder url
let documentDirectoryURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
for index in 0..<generatedURL.count {
let fileDestinationUrl = documentDirectoryURL.URLByAppendingPathComponent(String(index)+".htm")
cachedURL[index] = fileDestinationUrl //store the cached urls
let fileURL = NSURL(string: generatedURL[index])
//if (NSFileManager.defaultManager().fileExistsAtPath(fileDestinationUrl)) {
let data = NSData(contentsOfURL: fileURL!)
if (data != nil) {
//writing to disk
data?.writeToURL(fileDestinationUrl, atomically: true)
// saving was successful. any code posterior code goes here
//reading from disk
do {
let mytext = try String(contentsOfURL: fileDestinationUrl, encoding: NSUTF8StringEncoding)
print(fileDestinationUrl)
print(mytext) // "some text\n"
} catch let error as NSError {
print("error loading from url \(fileDestinationUrl)")
print(error.localizedDescription)
}
}
// } else {
// print("The files already exist")
// //reading from disk
// do {
// let mytext = try String(contentsOfURL: fileDestinationUrl, encoding: NSUTF8StringEncoding)
// //print(fileDestinationUrl)
// //print(mytext) // "some text\n"
// } catch let error as NSError {
// print("error loading from url \(fileDestinationUrl)")
// print(error.localizedDescription)
// }
//
// }
}
When running the program, the HTMLs of all the links are stored locally in those files. There's no problems in loading the HTML and thereby displaying the cached page in the WebView.
file:///var/mobile/Containers/Data/Application/.../Documents/0.htm
file:///var/mobile/Containers/Data/Application/.../Documents/1.htm
file:///var/mobile/Containers/Data/Application/.../Documents/2.htm
.
.
.
However, the current problem is that I lost the linkage between the cached pages. For example, in the website, there is a button on "index.htm" that links to "first.htm".
Now after loading the cached "index.htm" which is now "file:///var/....../0.htm", I won't be able to go to the cached "first.htm" because "file:///var/....../1.htm" is not in the HTML of the button.
So how do I retrieve the cached files in their original urls? Should I change the approach of generating the file or just create a new version of the website with all the cached file paths?
Thanks for reading my question.

OK, i think I can answer my own question now. Using the following function in the ViewController.swift containing the webView object, I can prompt the webView to load the cached url if the original url is clicked.
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.LinkClicked {
if (request.URL!.absoluteString == generatedURL[index] {
let requestObj = NSMutableURLRequest(URL: appDelegate.cachedURL[index]!);
webView.loadRequest(requestObj)
//return false
}
}
return true
}

Related

Swift: Ask user to input the text file name after each stop button is clicked

I created a stop button that can collect data, which will be saved to the defined path after clicking the stop button. However, if I want to continue collecting after clicking the stop button, the data will be added to the original text file. (This makes senses as I only know how to define one path)
My question is: Would it be possible to ask the user and input a new file name and save as a new text file after each stop so that the data is not added to the original file?
Below is what I have for one defined path and stacking up the data:
#IBAction func stopbuttonTapped(_ btn: UIButton) {
do {
let username:String = user_name.text!
fpsTimer.invalidate() //turn off the timer
let capdata = captureData.map{$0.verticesFormatted}.joined(separator:"") //convert capture data to string
let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
let url = dir.appendingPathComponent("testing.txt") //name the file
try capdata.appendLineToURL(fileURL: url as URL)
let result = try String(contentsOf: url as URL, encoding: String.Encoding.utf8)
}
catch {
print("Could not write to file")
}
}
And the extension I use for string and data:
extension String {
func appendLineToURL(fileURL: URL) throws {
try (self).appendToURL(fileURL: fileURL)
}
func appendToURL(fileURL: URL) throws {
let data = self.data(using: String.Encoding.utf8)!
try data.append(fileURL: fileURL)
}
func trim() -> String
{
return self.trimmingCharacters(in: CharacterSet.whitespaces)
}
}
extension Data {
func append(fileURL: URL) throws {
if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(self)
}
else {
try write(to: fileURL, options: .atomic)
}
}
}
Do I need to set a default file name (maybe texting.txt) and then popped up a user input for saving the text file? (That's where I am not too sure how to integrate to what I already have). I thank you so much for your time and suggestions in advance.
You could generate unique names.
For example:
let url = dir.appendingPathComponent("testing-\(Date.timeIntervalSinceReferenceDate).txt")
or
let url = dir.appendingPathComponent("testing-\(UUID().uuidString).txt")

how to read and write to the same file using swift with Xcode developing an iOS App

I am entering the Swift/Xcode-World coming from the Java-World and are struggling with File -path and -access. My App (iOS) needs to open a number of json-Files, encodes them to structs, and on every change which occurs - the user edits data - the same structs should be saved again as the same json files.
This instant structs/json-synchronization is essential to not loose data in case of a crashed App or iPad.
Everything works as it should - but the simulator saves the files at a different location as the input files, although the path looks to same (to me at least..). I know I should use observable classes instead of #state structs, but the model is rather complex and it would be a pain in the a. to convert the structs to classes. Anyway I guess this is not causing my file problem.
This is where I load the json`s:
func load_fuelHeader (_ filename: String) -> fuelheader{
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(fuelheader.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(fuelheader.self):\n\(error)")
}
}
This is the call to this func:
#State var FuelData=load_fuelHeader ("FuelHeader.json")
This is how I save the structs back to json`s:
func saveData(){
var jsonString:String?
let jsonEncoder = JSONEncoder()
do{
let jsonData = try jsonEncoder.encode(myFuelData)
jsonString=String(data: jsonData, encoding: String.Encoding.utf8)
} catch {
print (error)
}
guard let SaveFile = Bundle.main.url(forResource: "FuelHeader.json", withExtension: nil) else { fatalError("Couldn't find \("FuelHeader.json") in main bundle.") }
do {
try jsonString?.write(to: SaveFile, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
}
}
During debugging the loaded file is
file:///Users/.../Library/Developer/CoreSimulator/Devices/BBEDCD2E-05E0-4261-A08F-D214D101980F/data/Containers/Bundle/Application/8C92D2FB-344F-4516-AA22-32F54EE1386C/ofp.app/FuelHeader.json
while the saved file is the same, as it should be:
file:///Users/.../Library/Developer/CoreSimulator/Devices/BBEDCD2E-05E0-4261-A08F-D214D101980F/data/Containers/Bundle/Application/8C92D2FB-344F-4516-AA22-32F54EE1386C/ofp.app/FuelHeader.json
I checked the file, it is correct, it contains the changed data.The Problem is, the app does not load the above file, instead it loads the one within the xcode-project folder:
/Users/.../Documents/swift-projects/ofp/ofp/ressources/FuelHeader.json
for sure I missed some basics here, but I can`t find a solution yet.

Showing image from URL in iOS app [duplicate]

This question already has answers here:
Loading/Downloading image from URL on Swift
(39 answers)
Closed 5 years ago.
EDIT 3: Please also read my comment in the "answered" tagged answer. I think I won't use my synchronous method but change to the suggested asynchronous methods that were also given!
Ok I am struggling with some basic concepts of showing images from an URL from the internet on my app.
I use this code to show my image on an UIIamgeView in my ViewController:
func showImage() {
let myUrlImage = URL(string: linkToTheImage)
let image = try? Data(contentsOf: myUrlImage!)
imageView1.image = UIImage(data: image!)
}
Now basically I have the following question:
Is the whole image downloaded in this process?
Or works the UIImageView like a "browser" in this case and doesn't download the whole picture but only "positions" the image from the URL into my UIImageView?
EDIT:
The reason I asked is, I am basically doing a quiz app and all I need in the view is an image from a URL for each question - so it's no difference if I do it asynchronous or synchronous because the user has to wait for the image anyways. I am more interested in how do I get the fastest result:
So I wanted to know if my code really downloads the picture as a whole from the URL or just "Positions" it into the UIImageView?
If in my code the picture is downloaded in its full resolution anyways, then you are right, I could download 10 pictures asynchronously when the player starts the quiz, so he hopefully doesn't have to wait after each answer as long as he would wait when I start downloading synchronously after each answer.
Edit 2:
Because my Question was tagged as similar to another some more explanation:
I already read about synchronous and asynchronous downloads, and I am aware of the downsides of synchronous loading.
I am more interested in a really basic question, and I get the feeling I had one basic thing really wrong:
My initial thought was that if I open a link in my browser, for example this one,
https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/68dd54ca-60cf-4ef7-898b-26d7cbe48ec7/10-dithering-opt.jpg
the browser doesn't download the whole picture. But I guess this isn't the case? The whole picture is downloaded?
Never use Data(contentsOf:) to display data from a remote URL. That initializer of Data is synchronous and is only meant to load local URLs into your app, not remote ones. Use URLSession.dataTask to download image data, just as you would with any other network request.
You can use below code to download an image from a remote URL asynchronously.
extension UIImage {
static func downloadFromRemoteURL(_ url: URL, completion: #escaping (UIImage?,Error?)->()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil, let image = UIImage(data: data) else {
DispatchQueue.main.async{
completion(nil,error)
}
return
}
DispatchQueue.main.async() {
completion(image,nil)
}
}.resume()
}
}
Display the image in a UIImageView:
UIImage.downloadFromRemoteURL(yourURL, completion: { image, error in
guard let image = image, error == nil else { print(error);return }
imageView1.image = image
})
You can do it this way. But in most cases it is better to download the image first by yourself and handle the displaying then (this is more or less what the OS is doing in the background). Also this method is more fail proof and allows you to respond to errors.
extension FileManager {
open func secureCopyItem(at srcURL: URL, to dstURL: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: dstURL.path) {
try FileManager.default.removeItem(at: dstURL)
}
try FileManager.default.copyItem(at: srcURL, to: dstURL)
} catch (let error) {
print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
return false
}
return true
}
}
func download() {
let storagePathUrl = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("image.jpg")
let imageUrl = "https://www.server.com/image.jpg"
let urlRequest = URLRequest(url: URL(string: imageUrl)!)
let task = URLSession.shared.downloadTask(with: urlRequest) { tempLocalUrl, response, error in
guard error == nil, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("error")
return
}
guard FileManager.default.secureCopyItem(at: tempLocalUrl!, to: storagePathUrl) else {
print("error")
return
}
}
task.resume()
}

Opening apps for corresponding file type [duplicate]

I'm trying to open a .pdf file after download which is downloaded with Alamofire. But I've seen only using a "webview". Thus the application consumes lots of memory and is not viable.
What I want is to open it with the native device application. Any suggestions? Thank you.
Edit: This is my code for download file:
var localPath: NSURL?
Alamofire.download(.GET, url, destination: { (temporaryURL, response) in
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let pathComponent = response.suggestedFilename
localPath = directoryURL.URLByAppendingPathComponent(pathComponent!)
return localPath!
})
.response { (request, response, _, error) in
if error != nil
{
// got an error in getting the data, need to handle it
print("Error: \(error!)")
}
//print(response)
print("Download file en:\(localPath!)")
self.view.hideToastActivity()
//self.actioncall()
}
}
I need open file from localpath...
You should use UIDocumentInteractionController. You can read about it on this Apple documentation page.
By doing some Googling you should see even some example implementations. For example here you can see some code about this done by "mattneub".
I let you one more code that you can use:
var documentInteractionController: UIDocumentInteractionController!
#IBAction func openDocument(sender: UIButton) {
let URL: NSURL = NSBundle.mainBundle().URLForResource("yourPDF", withExtension: "pdf")!
if (URL != "") {
// Initialize Document Interaction Controller
self.documentInteractionController = UIDocumentInteractionController(URL: URL)
// Configure Document Interaction Controller
self.documentInteractionController.delegate = self
// Present Open In Menu
self.documentInteractionController.presentOptionsMenuFromRect(sender.frame, inView: self.view, animated: true)
//presentOpenInMenuFromRect
}
}
// UIDocumentInteractionControllerDelegate
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) -> UIViewController {
return self
}

Image URL to UIImage Not Working

let url = URL(string: (pinsFIREBASE[marker.snippet!]?.imageURL)!)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() {
self.postImage.image = UIImage(data: data)
}
}
task.resume()
I have the following code that takes a url from firebase, in the form http://i.imgur.com/nkomPpP.jpg, and is supposed to turn that url into a UIImage that can be placed on a view. However, while extracting the text from the firebase object works, parsing the image URL doesn't seem to be working as I get an empty view. What am I doing wrong?
I know why, your code works. The problem is your image link. Your imageURL's HTTP type. iOS don't like HTTP type request because it's not safe.
Plan A: Try a HTTPS type image link, it works.
Plan B: Add "App Transport Security Settings" in project info ,and set "Allow
Arbitrary Loads" yes in "App Transport Security Settings" dictionary.
I suggested use Plan A, that's Apple want iOSDev to do.
You need to remove the () from after DispatchQueue.main.async(). Try this:
let url = URL(string: (pinsFIREBASE[marker.snippet!]?.imageURL)!)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
self.postImage.image = UIImage(data: data)
}
}
task.resume()

Resources