3

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?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kevin Maldjian
  • 147
  • 1
  • 10

1 Answers1

6

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)
Gwendal Roué
  • 3,949
  • 15
  • 34
  • Thankyou for this code. Once the database is modified, would it be possible to write changes to the main bundle? Or will i have to use this same process even when attempting to read to the DB in order to get the newly added data. – Kevin Maldjian Dec 11 '17 at 16:53
  • No, because you will never be able to copy changes to the main bundle, because resources can not be modified, ever. But read the sample code more carefully, and try to understand it : it 1. copies the resource database if and only if the copy does not exist yet, and then 2. opens the copied writable database. This means that the changes performed after the unique initial copy will remain launch after launch: the app will not overwrite the changes with a copy of the resource database. – Gwendal Roué Dec 11 '17 at 17:04
  • One last comment: since the copy of the database bundled with the application happens *only once*, one may wonder what will happen when your app is eventually updated. The updated resource database that ships with the new app version will not be used at all when its previous version has already been copied. I don't know if this is OK or not. It depends on your particular app. If it is not OK, then you'll have to design a strategy for allowing both local modifications, and updates from future database versions. This is less trivial, and another question entirely. – Gwendal Roué Dec 11 '17 at 17:22
  • Would it just be smart to save things into core data? The data that would be saved is an array of favorited quotes. – Kevin Maldjian Dec 11 '17 at 19:19