error Domain=FMDatabase Code=8 "attempt to write a readonly database" - ios
I'm using FMDatabase library to used prepped database sqlite.
I got this error:
Error Domain=FMDatabase Code=8 "attempt to write a readonly database" UserInfo={NSLocalizedDescription=attempt to write a readonly database}
2017-10-27 19:59:10.983238+0330 kashanmap[417:63718] Unknown error calling sqlite3_step (8: attempt to write a readonly database) eu
2017-10-27 19:59:10.983473+0330 kashanmap[417:63718] DB Query: insert into LocationInfoFa
it's my class:
import FMDB
class DatabaseManager {
private let dbFileName = "kashanmapDB_upgrade_3-4.db"
private var database:FMDatabase!
let TABLE_LOCATION_FA = "LocationInfoFa";
let TABLE_LOCATION_EN = "LocationInfoEn";
let TABLE_GREAT_PEOPLE_FA = "GreatPeopleInfoFa";
let TABLE_GREAT_PEOPLE_EN = "GreatPeopleInfoEn";
let TABLE_TAGS = "Tags";
let TABLE_RELATION_TAG_LOCATION = "RelationTagLocation";
let TABLE_NECESSARY_INFORMATION = "NecessaryInformation";
let TABLE_SLIDER_FA = "SliderFa";
let TABLE_SLIDER_EN = "SliderEn";
let DATABASE_VERSION = 4;
static var LANGUAGE = 1 ; //1:Fa , 2:En
var utilities = Utilities()
init() {
openDatabase()
if(utilities.getData(key: "lang") == "2")
{
DatabaseManager.LANGUAGE = 2
}
}
func openDatabase() {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let dbPath = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName)
let str_path = Bundle.main.resourceURL!.appendingPathComponent(dbFileName).path
let database = FMDatabase(path: str_path)
/* Open database read-only. */
if (!(database.open(withFlags: 2))) {
print("Could not open database at \(dbPath).")
} else {
print("opened database")
self.database = database;
}
}
func closeDatabase() {
if (database != nil) {
database.close()
}
}
path of my database:
my query:
do {
let db = database
let q = try db?.executeUpdate("insert into \(table) (catid,subcat_id,id,subcat_title,title,description,lat,lon,takhfif,images,wifi,apple_health,wc,full_time,pos,work_hours,phone,mobile,fax,website,email,address,facebook,instagram,linkedin,telegram,googleplus,twitter,publish,feature,manager,city,rating_sum,rate_count,lastip,parking,isMallID,mallID,discount_images,price_images,newProduct_images,services_images,order_online,out_upon,cat_title,cat_icon,last_modify,item_logo,cat_logo,rate_sum1,rate_sum2,rate_sum3,rate_count1,rate_count2,rate_count3,rate_title1,rate_title2,rate_title3,rate_enable,installments_text,installments_image) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", values:[ catid,subcat_id,id,subcat_title,title,description,lat,lon,takhfif,images,wifi,apple_health,wc,full_time,pos,work_hours,phone,mobile,fax,website,email,address,facebook,instagram,linkedin,telegram,googleplus,twitter,publish,feature,manager,city,rating_sum,rate_count,lastip,parking,isMallID,mallID,discount_images,price_images,newProduct_images,services_images,order_online,out_upon,cat_title,cat_icon,last_modify,item_logo,cat_logo,rate_sum1,rate_sum2,rate_sum3,rate_count1,rate_count2,rate_count3,rate_title1,rate_title2,rate_title3,rate_enable,installments_text,installments_image])
} catch {
print("\(error)")
}
there are some solutions in stack overflow but them don't accepted as true answer.
updated2
I got this error:
DB does not exist in documents folder
my code:
init() {
openDatabase()
if(utilities.getData(key: "lang") == "2")
{
DatabaseManager.LANGUAGE = 2
}
}
func copyDatabaseIfNeeded() {
// Move database file from bundle to documents folder
let fileManager = FileManager.default
let documentsUrl = fileManager.urls(for: .documentDirectory,
in: .userDomainMask)
guard documentsUrl.count != 0 else {
return // Could not find documents URL
}
let finalDatabaseURL = documentsUrl.first!.appendingPathComponent("kashanmapDB_upgrade_3-4.db")
if !( (try? finalDatabaseURL.checkResourceIsReachable()) ?? false) {
print("DB does not exist in documents folder")
let documentsURL = Bundle.main.resourceURL?.appendingPathComponent("kashanmapDB_upgrade_3-4.db")
do {
try fileManager.copyItem(atPath: (documentsURL?.path)!, toPath: finalDatabaseURL.path)
} catch let error as NSError {
print("Couldn't copy file to final location! Error:\(error.description)")
}
} else {
print("Database file found at path: \(finalDatabaseURL.path)")
}
}
func openDatabase() {
self.copyDatabaseIfNeeded()
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let dbPath = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName)
let str_path = Bundle.main.resourceURL!.appendingPathComponent(dbFileName).path
let database = FMDatabase(path: str_path)
/* Open database read-only. */
if (!(database.open(withFlags: 2))) {
print("Could not open database at \(dbPath).")
} else {
print("opened database")
self.database = database;
}
}
You are facing this error because you are trying to write (update) the .db file in the bundle directory which is not allowed:
AppName.app:
This is 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.
File System Basics - Table 1-1: Commonly used directories of an iOS app
If you are aiming to update the file, you should implement a logic to copy it -if it's not already has been copied before- into the documents directory, thus you'd be able to read/write transactions with the copied file.
Remark that for iOS 11 and above, you might want to copy the database file into the Application Support directory if you don't want to let be viewable to the end users when navigating to your app by the Files iOS app. For details, check the iOS Storage Best Practices Apple video session.
You'd notice that this logic should be applied to any file in the app main bundle, for instance it is also applicable for JSON files.
Related
SQLite db create error in real iOS device
I'm trying to learning swiftUi and I've included SQLIte in my project. I've successfully created db, tables and func to read and insert records and I tested it in Xcode emulator and all works fine. I'm using Xcode 12.1. When I try to launch my app to real device (iphone 11, software version 13.5.1), the db isn't create and I get this error: 2021-04-14 18:00:59.296690+0200 playToys[285:7409] [logging-persist] cannot open file at line 43353 of [378230ae7f] 2021-04-14 18:00:59.296742+0200 playToys[285:7409] [logging-persist] os_unix.c:43353: (0) open(/var/mobile/Containers/Data/Application/3D858B04-DA3A-488E-804A-DFD13DD882E3/Documents.playToysDb.sqlite) Undefined error: 0 There is error in creating DB puntamento creazionefile:///var/mobile/Containers/Data/Application/3D858B04-DA3A-488E-804A-DFD13DD882E3/Documents.playToysDb.sqlite/ 2021-04-14 18:00:59.298066+0200 playToys[285:7409] [logging] API call with NULL database connection pointer 2021-04-14 18:00:59.298104+0200 playToys[285:7409] [logging] misuse at line 131400 of [378230ae7f] Prepration table cliente fail 2021-04-14 18:00:59.298127+0200 playToys[285:7409] [logging] API call with NULL database connection pointer 2021-04-14 18:00:59.298142+0200 playToys[285:7409] [logging] misuse at line 131400 of [378230ae7f] Prepration createing Currencies table fail 2021-04-14 18:00:59.298161+0200 playToys[285:7409] [logging] API call with NULL database connection pointer 2021-04-14 18:00:59.298174+0200 playToys[285:7409] [logging] misuse at line 131400 of [378230ae7f] Prepration createing Categorie table fail This is a part of code to create and open db: class DBHelper { static let dbHelperInstance = DBHelper() var db : OpaquePointer? var path : String = "playToysDb.sqlite" private init() {} func initDatabase(){ self.db = createDB() self.createTableCurrency() self.initDefaultItems() } func createDB() -> OpaquePointer? { let filePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathExtension(path) var dbPointer : OpaquePointer? = nil if sqlite3_open(filePath.path, &dbPointer) != SQLITE_OK { print("There is error in creating DB") return nil }else { print("Database has been created with path \(path)") return dbPointer } } func openDatabase() { let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) .appendingPathExtension(path) if sqlite3_open(fileURL.path, &db) == SQLITE_OK { print("Database Created !!") } } } Any idea? Thank you
You are using appendingPathExtension. That is for adding an extension to a filename. You meant appendingPathComponent, which is for adding an entire filename (or other path component) to a URL. That is why you are seeing a . between Documents and your filename, rather than a /, in the path shown in your error message. The simulator will blithely create a file called Documents.playToysDb.sqlite in your app’s root directory, but a device cannot. It expects it to be within the Documents directory, resulting in the failure when trying to create that file. You asked about the correct way to get the database URL. A few observations: You asked about urls(for:in:)[0] vs url(for:in:appropriateFor:create:). I would use the latter. I would advise using .applicationSupportDirectory rather than .documentDirectory. The former is for the app’s support files. The latter is intended for user-facing files, which is not appropriate here. You can use .documentDirectory, but I would not advise it. I would also avoid repeating this open in createDB and openDatabase. In fact, I would have a single method that would try to open the existing database, and if it fails, only then create the database and create/populate the tables. Implicit in the above point, I would advise sqlite3_open_v2, in which you can chose to create a blank database or not. So I call it first without SQLITE_OPEN_CREATE (which, if successful, I know the database has already been created). Only if that fails do I try it again with that option (and then proceed to create the necessary tables). Thus: class DBHelper { static let shared = DBHelper() private var db: OpaquePointer? private let filename = "playToysDb.sqlite" private init() { openDatabase() } } private extension DBHelper { var errorMessage: String { sqlite3_errmsg(db).flatMap { String(cString: $0) } ?? "Unknown Error" } func openDatabase() { let fileURL = try! FileManager.default .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent(filename) // try to open existing database (but don't create); if successful, return immediately if sqlite3_open_v2(fileURL.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK { print("Database has been opened with path \(fileURL.path)") return } // if you got here, clean up and then try creating database sqlite3_close(db) // clean up after the above `sqlite3_open` call guard sqlite3_open_v2(fileURL.path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nil) == SQLITE_OK else { print("There is error in creating DB: ", errorMessage) sqlite3_close(db) // if creation failed, make sure to again clean up db = nil return } // if you got here, we created blank database, so populate however you want createTableCurrency() populateDefaultItems() print("Database has been created with path \(fileURL.path)") } func createTableCurrency() { ... } func populateDefaultItems() { ... } } Note, in addition to the above, I have tackled a few unrelated observations: As a matter of convention, I used shared for the name of the shared instance. I would mark the private properties and methods as such, to avoid external manipulation of them. If a query ever fails, I would call sqlite3_errmsg to get the error reported by SQLite. If open fails, always call sqlite3_close. As the docs say: Whether or not an error occurs when it is opened, resources associated with the database connection handle should be released by passing it to sqlite3_close() when it is no longer required.
thank you for reply. I solved my issue using appendingPathComponent and using the first directory available for the user. Is that a correct way? class DBHelper { static let dbHelperInstance = DBHelper() var db : OpaquePointer? var path : String = "playToysDb.sqlite" private init() {} func initDatabase(){ self.db = createDB() self.createTableCurrency() self.initDefaultItems() } func createDB() -> OpaquePointer? { let filePath = getDocumentsDirectory().appendingPathComponent(path); var dbPointer : OpaquePointer? = nil if sqlite3_open(filePath.path, &dbPointer) != SQLITE_OK { print("There is error in creating DB") return nil }else { print("Database has been created with path \(path)") return dbPointer } } func openDatabase() { let filePath = getDocumentsDirectory().appendingPathComponent(path); if sqlite3_open(fileURL.path, &db) == SQLITE_OK { print("Database Created !!") } } func getDocumentsDirectory() -> URL { // find all possible documents directories for this user let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) // just send back the first one, which ought to be the only one return paths[0] } } This approach works too, which is the differences? class DBHelper { static let dbHelperInstance = DBHelper() var db : OpaquePointer? var path : String = "playToysDb.sqlite" private init() {} func initDatabase(){ self.db = createDB() self.createTableCurrency() self.initDefaultItems() } func createDB() -> OpaquePointer? { let fileURL = try! FileManager.default .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent(path) var dbPointer : OpaquePointer? = nil if sqlite3_open(fileURL.path, &dbPointer) != SQLITE_OK { print("There is error in creating DB") return nil }else { print("Database has been created with path \(path)") return dbPointer } } func openDatabase() { let fileURL = try! FileManager.default .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent(path) if sqlite3_open(fileURL.path, &db) == SQLITE_OK { print("Database Created !!") } } }
Download and view document in ios from firebase storage
I have a firebase storage link of a document which I want to download in my ios device's Document directory and then view it from there using QuickLook Module. I am able to download document in iOS device but it is not saving with the name i am providing. I want to create following directory structure and then need to save document in "doc" folder. Documents/chat/doc following code is to create "/chat/doc" inside Iphone's Document directory. Where folderName will contain "/chat/doc". func createDocument(folderName: String ,completion: #escaping(Bool,String)->()){ let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first if let documentsDirectory = path{ // let docDirectoryPath = documentsDirectory.appending(folderName) let fileManager = FileManager.default if !fileManager.fileExists(atPath: docDirectoryPath) { do { try fileManager.createDirectory(atPath: docDirectoryPath, withIntermediateDirectories: true, attributes: nil) //return path on success completion(true,docDirectoryPath) return } catch { print("Error creating folder in documents dir: \(error.localizedDescription)") completion(false,error.localizedDescription) return } } completion(true,docDirectoryPath) } } after creating folder i want to download document and need to store in newly created folder in Document Directory. Following code will download document. func downloadDocument(){ createDocument(folderName: "/chat/doc") { (status, path) in if let docURL = self.documentURL{ if status{ //folder is created now download document let docDownloadRef = Storage.storage().reference(forURL: docURL) //get documents metadata from firebase to get document name docDownloadRef.getMetadata { metadata, error in if let error = error { // Uh-oh, an error occurred! } else { //get document name from metadata if let fileName = metadata?.name{ //create file system url to store document let docsurl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) let myurl = docsurl.appendingPathComponent("chat/\(fileName)") _ = docDownloadRef.write(toFile: myurl) { url, error in if let error = error { print(error.localizedDescription) } else { // Local file URL for document is returned print("doc url \(url?.absoluteString)") } } } } } }else{ //folder is not created show document using online location } } } } Ultimate goal is to achieve something like this "Documents/chat/doc/abc.doc" but my code is not storing document using file name it stores like this "Documents/chat/doc" where doc is considered as a document not a folder. Please help to let me know what I am doing wrong. Thank you.
When you are creating url at below line, here you are missing the path for doc folder. let myurl = docsurl.appendingPathComponent("chat/\(fileName)") This will save to chat folder. You need to pass the same folder while creating i.e folderName: "/chat/doc" so it should be let myurl = docsurl.appendingPathComponent("chat/doc/\(fileName)")
Move database file from bundle to documents folder - FMDB
I'm using FMdatabase. I want to use a prepared database. I think I should move database file from bundle to documents folder. my code: import FMDB class DatabaseManager { private let dbFileName = "kashanmapDB_upgrade_3-4.db" private var database:FMDatabase! let TABLE_LOCATION_FA = "LocationInfoFa"; let TABLE_LOCATION_EN = "LocationInfoEn"; let TABLE_GREAT_PEOPLE_FA = "GreatPeopleInfoFa"; let TABLE_GREAT_PEOPLE_EN = "GreatPeopleInfoEn"; let TABLE_TAGS = "Tags"; let TABLE_RELATION_TAG_LOCATION = "RelationTagLocation"; let TABLE_NECESSARY_INFORMATION = "NecessaryInformation"; let TABLE_SLIDER_FA = "SliderFa"; let TABLE_SLIDER_EN = "SliderEn"; let DATABASE_VERSION = 4; static var LANGUAGE = 1 ; //1:Fa , 2:En var utilities = Utilities() init() { openDatabase() if(utilities.getData(key: "lang") == "2") { DatabaseManager.LANGUAGE = 2 } } func copyDatabaseIfNeeded() { // Move database file from bundle to documents folder let fileManager = FileManager.default let documentsUrl = fileManager.urls(for: .documentDirectory, in: .userDomainMask) guard documentsUrl.count != 0 else { return // Could not find documents URL } //let finalDatabaseURL = documentsUrl.first!.appendingPathComponent("kashanmapDB_upgrade_3-4.db") let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let finalDatabaseURL = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName) if !( (try? finalDatabaseURL.checkResourceIsReachable()) ?? false) { print("DB does not exist in documents folder") let documentsURL = Bundle.main.resourceURL?.appendingPathComponent("kashanmapDB_upgrade_3-4.db") do { try fileManager.copyItem(atPath: (documentsURL?.path)!, toPath: finalDatabaseURL.path) } catch let error as NSError { print("Couldn't copy file to final location! Error:\(error.description)") } } else { print("Database file found at path: \(finalDatabaseURL.path)") } } func openDatabase() { self.copyDatabaseIfNeeded() let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let dbPath = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName) let str_path = Bundle.main.resourceURL!.appendingPathComponent(dbFileName).path let database = FMDatabase(path: str_path) /* Open database read-only. */ if (!(database.open(withFlags: 2))) { print("Could not open database at \(dbPath).") } else { print("opened database") self.database = database; } } at the first time (when application installed ) I got this error message: DB does not exist in documents folder and I always got this message: Error Domain=FMDatabase Code=8 "attempt to write a readonly database" UserInfo={NSLocalizedDescription=attempt to write a readonly database}
Hmmm... Looking at your code: func openDatabase() { self.copyDatabaseIfNeeded() let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let dbPath = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName) let str_path = Bundle.main.resourceURL!.appendingPathComponent(dbFileName).path let database = FMDatabase(path: str_path) /* Open database read-only. */ if (!(database.open(withFlags: 2))) { print("Could not open database at \(dbPath).") } else { print("opened database") self.database = database; } } It appears you are setting dbPath equal to the path to the file in documents folder, but then you're trying to open database which is at str_path which is equal to the Bundle path. Maybe just change: let database = FMDatabase(path: str_path) to: let database = FMDatabase(path: dbPath)
Having copied the database, you are trying to open the database from the bundle. Open the one in the Documents folder. If you define the bundle URL inside the if statement that handles the missing database (like shown below), there's no possibility of accidentally grabbing the wrong database. As an aside, Apple is getting more particular about what gets stored in Documents folder (see iOS Storage Best Practices). You might want to use Application Support folder instead. let fileURL = try FileManager.default .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent("test.sqlite") let fileExists = (try? fileURL.checkResourceIsReachable()) ?? false if !fileExists { let bundleURL = Bundle.main.url(forResource: "test", withExtension: "sqlite")! try FileManager.default.copyItem(at: bundleURL, to: fileURL) } let db = FMDatabase(url: fileURL) guard db.open() else { print("unable to open") return } Alternatively, it’s often preferred to adopt the “ask for forgiveness rather than permission” strategy. I.e., rather than checking for existence before you open the database every time you open it, just try to open it and handle the file-not-found error scenario (which will happen just once, the first time you try opening it). Bottom line, just try opening the database, and if it fails, copy it from the bundle and try again. The trick is to supply the SQLITE_OPEN_READWRITE parameter (made available if you import SQLite3) but not the SQLITE_OPEN_CREATE so that it won’t create a blank database if it’s not found that first time you try opening it: let fileURL = try FileManager.default .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent("test.sqlite") let db = FMDatabase(url: fileURL) if !db.open(withFlags: SQLITE_OPEN_READWRITE) { let bundleURL = Bundle.main.url(forResource: "test", withExtension: "sqlite")! try FileManager.default.copyItem(at: bundleURL, to: fileURL) guard db.open(withFlags: SQLITE_OPEN_READWRITE) else { print("unable to open") return } }
Why are deleted files coming back after a new write to file? Swift 2.0
I am writing an app in swift that logs sensor data to a txt file. When I have an event occur that needs to be logged I create the filename func createNewLogFile (){ // Create a new file name currentFileName = "log\(NSDate()).txt" //get the path let paths = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) //create the file _ = paths[0].URLByAppendingPathComponent(currentFileName) } After the file is created I write data to the new file like this: func writeData (data: String){ // get the path to document directory let paths = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) let filePath = paths[0].URLByAppendingPathComponent(currentFileName) //get the data to be logged let stringLocation = data let stringData = stringLocation.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! //look to see if the file exist if NSFileManager.defaultManager().fileExistsAtPath(filePath.path!) { do { //seek to the end of the file to append data let fileHandle = try NSFileHandle(forWritingToURL: filePath) fileHandle.seekToEndOfFile() fileHandle.writeData(stringData) fileHandle.closeFile() } catch { print("Can't open fileHandle \(error)") } } else { do { // write to new file try stringData.writeToURL(filePath, options: .DataWritingAtomic) } catch { print("Can't write to new file \(error)") } } } When I delete the files (from a different ViewController or the same, I tried both) I am calling this DeleteAllFiles func deleteAllFiles (Extension: String){ let dirs = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) let dir = dirs[0] do { let fileList = try NSFileManager.defaultManager().contentsOfDirectoryAtURL(dir, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions()) //return fileList as [String] for elements in fileList{ do{ try NSFileManager.defaultManager().removeItemAtURL(elements) print("old Files has been removed") } catch let error as NSError { print(error.localizedDescription) } } }catch let error as NSError { print(error.localizedDescription) } } I then refresh the list and the files seem to be gone.(even when I go back and forth between views) However, when I write a new file and refresh the list the files are back with the new file. This even happens when I delete them from iTunes using the shared files feature. Any ideas on why this is happening? I am not getting any helpful error messages.
I found the fix for the problem. When I was creating the file I actually only meant to create the file name. There was no reason to actually create the file at this time. I am creating the actual file when I write to it. func createNewLogFile (){ // Create a new file name currentFileName = "log\(NSDate()).txt" //Removed creating actual file code }
How to properly move In App Purchase download files from cache in iOS?
I am trying to set up an app with an In App Purchase that downloads content for 12 levels of a game when that respective pack is purchased. I am stuck on how to properly move the downloaded images from the cache folder to the Documents folder. Here is my code so far: func processDownload(sender: NSURL) { //Convert URL to String, suitable for NSFileManager var path:String = sender.path! path = path.stringByAppendingPathComponent("Contents") //Makes an NSArray with all of the downloaded files let fileManager = NSFileManager.defaultManager() var files: NSArray! do { files = try fileManager.contentsOfDirectoryAtPath(path) } catch let err as NSError { print("Error finding zip URL", err.localizedDescription) } //For each file, move it to Library for file in files { let pathSource: String = path.stringByAppendingPathComponent(file as! String) let pathDestination: String = NSSearchPathForDirectoriesInDomains(.LibraryDirectory, .UserDomainMask, true)[0] //Remove destination files b/c not allowed to overwrite do { try fileManager.removeItemAtPath(pathDestination) }catch let err as NSError { print("Could not remove file", err.localizedDescription) } //Move file do { try fileManager.moveItemAtPath(pathSource, toPath: pathDestination) print("File", file, "Moved") }catch let err as NSError { print("Couldn't move file", err.localizedDescription) } } } Everything actually works just fine except for the errors that are printing from the two do statements. When trying to remove any existing files of the same name in the first do block, I get the following error: Could not remove file “Library” couldn’t be removed because you don’t have permission to access it. This subsequently causes the next error from the next do statement to print because the original could not be removed. Any ideas of why this is happening and how I can properly save the downloaded files elsewhere? Thanks.
I've found a proper working solution. This code will move all of the items in the downloaded zip folder to the Library directory. func processDownload(sender: NSURL) { //Convert URL to String, suitable for NSFileManager var path: String = sender.path! path = path.stringByAppendingPathComponent("Contents") //Makes an NSArray with all of the downloaded files let fileManager = NSFileManager.defaultManager() var files: NSArray! do { files = try fileManager.contentsOfDirectoryAtPath(path) } catch let err as NSError { print("Error finding zip URL", err.localizedDescription) } //For each file, move it to Library for file in files { let currentPath: String = path.stringByAppendingPathComponent(file as! String) var pathDestination: String = NSSearchPathForDirectoriesInDomains(.LibraryDirectory, .UserDomainMask, true)[0] pathDestination = pathDestination.stringByAppendingPathComponent(file as! String) //Move file do { try fileManager.moveItemAtPath(currentPath, toPath: pathDestination) print("File", file, "Moved") }catch let err as NSError { print("Couldn't move file", err.localizedDescription) } } } I can now make SKTextures in SpriteKit with these files like so: var rippleTex = SKTexture(image: UIImage(contentsOfFile: NSSearchPathForDirectoriesInDomains(.LibraryDirectory, .UserDomainMask, true)[0].stringByAppendingPathComponent("P06_ripple.png"))!)