Swift - Listing contents of a non-local directory - swift

I'd like to list the contents of an external directory in Swift 2. It seems easy enough for a local directory, but I just can't figure it out with an external directory. Here is my code:
var theUrlString = "https://great-castles.com/images/berkeley/"
var theUrl = NSURL(string: theUrlString)!
do {
let directoryContents = try NSFileManager.defaultManager().contentsOfDirectoryAtURL(theUrl, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions())
print(directoryContents)
} catch let error as NSError {
print(error.localizedDescription)
}
In the playground, I am getting the error:
The file “berkeley” couldn’t be opened because there is no such file
Note that I just threw a random URL in there as an example. But it is accessible, so I should be able to get a list of the files, right? Does NSFileManager.defaultManager think I am referring to a local file?
What I'd really like to do is copy the contents of an online directory into the document directory on the device. If the above isn't possible, any ideas on the best way to do that?

Related

Using Swift Playgrounds to read a file from a user path

I have downloaded a file using my iPad into the user Downloads folder, not in the resource bundle for the Playground [footnote].
Is there any way to read it using a Playground? I’ve tried a whole bunch of different things and nothing seems able to access the file.
Here’s an example of what doesn’t work for me:
// Playground to analyze my finances downloaded from my bank
import SwiftUI;
import Foundation;
// Read in transactions
var filename = "filename.txt"
do {
print("Letting dir")
let dir = try! FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: false);
print("Dir set, appending file")
var fileurl = dir.appendingPathComponent(filename)
print("Letting contents of file \( fileurl )");
let contents = try String(contentsOf: fileurl, encoding: .utf8)
// Process Transactions
print(contents)
// Display results
}
catch {
print("Error opening file: \( error )")
}
I think the problem is that it’s accessing the downloads folder of the resources bundle, instead of the system bundle, but I don’t know how to fix that or if it even can be done.
Thanks for any help. This has been unbelievably frustrating and it’s the very first step of my project.
David
[footnote] I can’t even figure out how to access the resource bundle - all the GUI lets me do is edit the Playground code. And I understand the resource paths frequently change, so that doesn’t seem like the right thing to do anyway.

Swift GRDB Writing to Database Error "attempt to write a readonly database"

I am attempting to write to a database of quotes I have created. If the user selects the "favorite" button, it will add a 1 (or true) value into the favorite column on the SQLITE database. For some reason I cannot write to the database and when I execute this code:
#IBAction func addFavorite(_ sender: Any) {
var configuration = Configuration()
configuration.readonly = false
let dbPath = Bundle.main.path(forResource: "data", ofType: "db")!
let dbQueue = try! DatabaseQueue(path: dbPath, configuration: configuration)
try! dbQueue.inDatabase { db in
try db.execute("""
UPDATE quotes SET favorite = 1 WHERE quote = ?;
""",
arguments: [quoteLabel.text])
}
I expect the result to write to the database and place a 1 in the favorite column but sadly this is not the case. I get this error:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: SQLite error 8 with statement UPDATE quotes SET favorite = 1 WHERE quote = ? arguments ["Abstinence is the great strengthener and clearer of reason."]: attempt to write a readonly database
I have tried to chmod 755 the database and that did not work either. Am I missing something?
The path to your database is a path to an application resource. App resources can not be modified in iOS.
See File System Basics for more information. Particularly relevant quote:
[...] the app’s bundle. This directory contains the app and all of its resources.
You cannot write to this directory. To prevent tampering, the bundle directory is signed at installation time. Writing to this directory changes the signature and prevents your app from launching. You can, however, gain read-only access to any resources stored in the apps bundle. For more information, see the Resource Programming Guide.
Now, how do you write in your database, if it can't be modified?
Well, you copy it in a place where you can modify it. You'll never modify the bundled database resource, and only ever modify the copy.
There is available documentation again: the How do I open a database stored as a resource of my application? GRDB FAQ says:
If the application should modify the database resource, you need to copy it to a place where it can be modified. For example, in the Application Support directory. Only then, open a connection:
let fileManager = FileManager.default
let dbPath = try fileManager
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("db.sqlite")
.path
if !fileManager.fileExists(atPath: dbPath) {
let dbResourcePath = Bundle.main.path(forResource: "db", ofType: "sqlite")!
try fileManager.copyItem(atPath: dbResourcePath, toPath: dbPath)
}
let dbQueue = try DatabaseQueue(path: dbPath)

Swift 4 | Referencing database file for SQLite queries "unable to open database file"

To begin, here is an image of my current project file set-up
I am using the following in this project:
import UIKit
import SQLite
import GRDB
When I execute the following code :
{
tagString = x
let dbQueue = try! DatabaseQueue(path: "data.db" )
try! dbQueue.inDatabase { db in
let rows = try Row.fetchCursor(db, "SELECT * FROM quotes WHERE genre = ? AND LENGTH(quote) < 100",
arguments: [tagString])
I get the following error "unable to open database file" as well as the warning "could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available."
I have tried changing "data.db" to simlpy "data", but that does not work.
The only solution I have found is when I write the complete path from my computer AND run it on a simulated app. If I try to use the entire path (ex. /Users/myName/Desktop/AppName/data.db) on my phone, the program crashes ( I assume because that memory location does not exist on the phone)
Question: Is my calling of the database correct? If so, any ideas of why It might not be able to find it?
Thankyou.
You get the "unable to open database file" error because you don't specify a valid path to your database.
On iOS, application files must be created at a valid path. Valid paths include paths inside the application's Documents folder. For example:
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let dbPath = documentsPath.appendingPathComponent("data.db")
let dbQueue = try DatabaseQueue(path: dbPath)
Please refer to the File System Basics chapter of iOS documentation for more information.

Accessing File saved in today extension from project

I am trying to access a file I saved from my today extension.In my today extenson I did this to save the file:
func startRecording() {
let audioFilename = getDocumentsDirectory().appendingPathComponent("recording.m4a")
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
recordButton.setTitle("Stop", for: .normal)
} catch {
finishRecording(success: false)
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
I then tried to get the data for my AVAudio Player in the main part of the project using this code:
let path = Bundle.main.path(forResource: "recording.m4a", ofType:nil)!
let url = URL(fileURLWithPath: path)
However, it gave the error: unexpectedly found nil while unwrapping an Optional value.
Thanks for the help.
Your extension saves the file to its document directory and your app code is looking for the file in the app bundle. The app bundle only contains the resources that are distributed with the app. You'll need to delve into the file system.
However, there's another problem. The extension and containing app don't share a documents directory. They each have their own container for writing data local to themselves. If you want to share data between them, it's a little more work. In summary:
Create an app group identifier for the app and the extension to share.
Use FileManager.containerURL(forSecurityApplicationGroupIdentifier:) to get the file URL for the shared container directory.
From the container URL, append the file name.
In the extension, you'll set up the AVAudioRecorder as usual and start recording.
In the main app, you'll want to use the NSFileCoordinator API to ensure that only one process is writing to the file at a time. Hopefully, the AVAudioRecorder uses the NSFileCoordinator API internally, although I didn't immediately find confirmation of this.
For more details about shared containers, see this blog post.
I just tried the same - record audio from a Today Extension. The code looks sooo familiar, so I'm taking a wild guess: you want to capture voice and send the file to the Google Speech API, correct?
Nonetheless, I think we're hitting restrictions of extensions: judging by https://developer.apple.com/library/content/qa/qa1872/_index.html extensions cannot record audio. The article has been writting for iOS 8, but I don't believe Apple ever lifted the restriction. Please correct me if I'm wrong, since I keep running into problems doing what OP does myself.
btw, check the result of audioRecorder.record(). It might be false and indicate that the capture never started (that's my error currently).

NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg") returning null

NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg")
The above code is returning null.
In order to check if the file exists or not, I used below code:
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(savepath) {
println("exist")
}
The above code returns that file exists in directory.
So I don't understand why the first code is returning null
Your problem is that NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg") returns an optional NSURL. You need to use if let to unwrap it and extract your file path from the returned url as follow:
if let resourceUrl = NSBundle.mainBundle().URLForResource("bach1", withExtension: "jpg") {
if NSFileManager.defaultManager().fileExistsAtPath(resourceUrl.path!) {
print("file found")
}
}
Swift 3.x
if let resourceUrl = Bundle.main.url(forResource: "bach1", withExtension: "jpg") {
if FileManager.default.fileExists(atPath: resourceUrl.path) {
print("file found")
}
}
I found that you have to actually go into Build Phases -> Copy Bundle Resources and manually add your item sometimes to the bundle. In my case it was a video file that didn't get added to the bundle correctly on its own.
NSBundle.URLForResource only returns files that are bundled in your application at build/compile time, like when you drag a file into your Xcode project and add it to your app target. You can typically use this when your app comes preloaded with resources you need, like images, movies, databases, and other files.
It seems like savepath is a path your application wrote to while running (maybe into your app's Documents directory?). Because of this, the file is outside of the bundle and you will need to store that path/URL somewhere (or build it up again) if you need to reference the file in the future.
I had this issue too. My fix was:
Selecting the file in project navigator
Open the 'File Inspector' (on the right hand side pane)
Checking the app target in the Target Membership