Connecting SQLite 3 Database to Swift - ios

I'm attempting to open an SQLite3 database in my SwiftUI code, to access the file path within the bundle, I'm using the following function;
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
.first!
However I keep getting the error unable to open database file (code: 14), but after trying to run it a few times I realized that the second last folder from path is different every time I run the code. How can I find a path to the bundle that will not change so the code works?
These are some examples of how the path changes:
Second last folder is named: CAC91DB3-E264-436F-89A8-1E3D76C4DC56
/Library/Developer/CoreSimulator/Devices/EFD14A1B-7207-4840-9ACE-8E44A269CC70/data/Containers/Data/Application/CAC91DB3-E264-436F-89A8-1E3D76C4DC56/Documents
Second last folder is named: 0A23051F-9362-4B4D-B49B-BE0F028384DC
/Library/Developer/CoreSimulator/Devices/EFD14A1B-7207-4840-9ACE-8E44A269CC70/data/Containers/Data/Application/0A23051F-9362-4B4D-B49B-BE0F028384DC/Documents
Edit: This is the code that I am using:
Import swiftUI
Import Foundation
Import SQLite
func openDatabase() {
let ProductID = Expression<Int64>("ProductID")
let Name = Expression<String>("Name")
let Calories = Expression<Double>("Calories")
let Protein = Expression<Double>("Protein")
let Carbs = Expression<Double>("Carbs")
let Fats = Expression<Double>("Fats")
let Weight = Expression<Double>("Weight")
let Category = Expression<String>("Category")
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentDirectory.appendingPathComponent("Database.db")
let db = try! Connection("\(fileURL)")
let Products = Table("Products")
//Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: no such table: Products (code: 1)
for prID in try! db.prepare(Products) {
print("productid: \(prID[ProductID]), name: \(prID[Name]), calories: \(prID[Calories]), protein: \(prID[Protein]), carbs: \(prID[Carbs]), fats: \(prID[Fats]), weight: \(prID[Weight]), category: \(prID[Category])")
DatabaseTest().dataTest.append(Product(name: prID[Name], calories: prID[Calories], protein: prID[Protein], carbs: prID[Carbs], fats: prID[Fats], weight: prID[Weight]))
}
}
For clarification on sqlite.swift reference:
(https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md)

This is because all applications in iOS are sandboxed. There is no way to avoid that. You can not save the full path. It will change every time you launch your App. What you need is to save only your file name and its parent directory. Use them to contruct a new fileURL and/or path every time you need to access them.
edit/update:
If your file is located inside your App Bundle:
let databaseURL = Bundle.main.url(forResource: "Database", withExtension: "db")!
Note: If you need the database path you have to get your fileURL path property not the absoluteString. Last but not least the Bundle is read only. You need to move/copy your database to another directory (application support) to be able to modify it.
let databasePath = databaseURL.path

Related

how to unarchive plists in bundle using a path in Swift 5

Short version:
How to access an archived file in the bundle for unarchiving with unarchivedObject(ofClass:from:). The core issue is going from a path (string or URL) to Data which is required by the method.
Long version:
I have many plists containing SCNNode hierarchies stored as plists in bundle resources.
I was able to access them in Swift 3 with:
let fileName = MoleName.DNA_ideal_comps // retrieves String
let filePath = getFilePath(fileName)
guard let components = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? [SCNNode] , components.count >= 4
else { // no value for this key, or not enough nodes
print("Couldn't find molecule (DNA_ideal_comps)")
return
} // [ atoms_A, bonds_A, atoms_B, bonds_B ]
scaleNode_2 = components[0] // DNA A
fixedNode_2 = components[1] // bonds
scaleNode_3 = components[2] // DNA B
fixedNode_3 = components[3] // bonds
// where filePath is:
func getFilePath(_ fileName: String) -> String {
if let path = Bundle.main.path(forResource: fileName, ofType: "plist") {
return path
}
return "path could not be found"
}
Here fileName is the name of the plist to retrieve, "DNA_ideal_comps" in this example. I created these independently of the active program due to the sheer volume of data; some of these plists contain over 30,000 items and there are about 90 total.
The above attempt to unarchive is deprecated and I've struggled to replace it. My best try so far:
guard let components = NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: moleData), components.count >= 4
But this fails with Cannot convert value of type 'String' to expected argument type 'Data'. Unlike the method in Swift 3, there appears no way here to use the path of the object to retrieve it.
Is there a way to access these plists using this method? Is there another method more appropriate?
The core issue is going from a path (string or URL) to Data which is required by the method
Well, that's trivial enough; just call the Data initializer that reads from a file on disk. You would then decode the Data by calling NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: theData).
https://developer.apple.com/documentation/foundation/data/3126626-init
https://developer.apple.com/documentation/foundation/nskeyedunarchiver/2983380-unarchivedobject
However, I'm confused as to why you're using NSKeyedUnarchiver to read a plist file. A plist file would be read using a PropertyListSerialization object.
https://developer.apple.com/documentation/foundation/propertylistserialization

FileManager.enumerator returns nil while testing on ios device

let documentsPath = "Users/xxxx/Desktop/*******/iOSSimulator/"
let url = URL(fileURLWithPath: documentsPath)
let fileManager = FileManager.default
print(FileManager.default.enumerator(atPath: url.path))
I want to run my UI tests on actual iOS device.But while executing the test cases in real device i am getting nil (in the print statement).but the test cases are executing successfully on the simulator(here i am getting some value in print statement).
I am not able to figure out why it is returning the nil value in ios device.
You are giving the wrong path(let documentsPath = "Users/xxxx/Desktop/*******/iOSSimulator/").
Real iOS device doesn't exist this path so you are getting nil.
You are giving static document path , which may be exist in simulator but not in device . Use this for getting document path in swift
if let documentsPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
//This gives you the string formed path
}
if let documentsPathURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
//This gives you the URL of the path
}

How to read a multidimensional String Array from a plist in Swift?

I'm using the following code to save a 2D String array to a plist:
func saveFavourites(favouriteStops: [[String]]) {
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// Write favourites to disk
let favsArray = favouriteStops as NSArray
print(favsArray)
favsArray.write(toFile: favouritesUrl.path, atomically: true)
}
}
}
The above snippet properly creates the .plist file (confirmed by looking at the simulator's filesystem in ~/Library/Developer/CoreServices). However, when I try reading it back to a NSArray with the following snippet, it results in nil:
let directories = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
if let library = directories.first {
if let libraryUrl = URL(string: library) {
let favouritesUrl = libraryUrl.appendingPathComponent("favourites.plist")
// favsToLoad is nil
let favsToLoad = NSArray(contentsOf: favouritesUrl)
// Do stuff with favsToLoad, if it would load properly
}
}
You're doing two very basic things wrong.
First, never make a URL from a file path by saying URL(string); this is a file on disk, so you must use URL.fileURL.
Second, don't start with a file path at all! Obtain the directory as a URL right from the start.
(Also, though I do not know whether this is the source of the issue, do not read and write directly in the Library directory. Use the Documents directory, the Application Support directory, or similar.)
So, for example, I would write:
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let favouritesurl = docsurl.appendingPathComponent("favourites.plist")
I see your problem. You misspelled "favorites". :)
But seriously...
Plists can only contain a very small set of "property list objects": (dictionaries, arrays, strings, numbers (integer and float), dates, binary data, and Boolean values).
If your array's "object graph" (the objects the array contains and any container objects inside the array recursively contain) contain anything other than the above types, the save will fail.
I don't honestly know what gets saved when it fails. Have you tried opening the plist file in a text editor and looking at it?
My guess is that something other than a string has snuck into your array, it's not one of the above types, and THAT'S why it's failing.

NSlogging/println SQLite db file location in Swift

So I want to figure out where my swift IOS app is storing its SQLite db file. I know that the applicationsDocumentsDirectory stores the directory. I need to find a way to print it in the console or NSLog it. I am new to swift and IOS development so I'm having trouble here. I tried just calling the variable in another class and also just NSLogging it within the clojure with no success. Any ideas?
Here is the variable.
lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "arux-software.onsite_childcare" in the application's documents Application Support directory.
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1] as NSURL
}()
There is an easier way to get the sqlite location:
println(NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString)
gives output with no time stamp like this:
/Users/tooti/Library/Developer/CoreSimulator/Devices/AB5B3350-F891-420B-88D5-E8F8E578D39B/data/Containers/Data/Application/38FBDC42-0D09-4A10-A767-17B576882117/Documents
And
NSLog(NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString )
gives output with a time stamp, like this:
2015-01-23 23:31:57.373 RAACTutor[6551:140531] /Users/tooti/Library/Developer/CoreSimulator/Devices/AB5B3350-F891-420B-88D5-E8F8E578D39B/data/Containers/Data/Application/38FBDC42-0D09-4A10-A767-17B576882117/Documents
You can do it in two ways.
First Way
1. we can get reference to the application document directory using NSSearchPathForDirectoriesInDomains() method.
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last! as String
The constant .documentDirectory says we are looking for Document directory.
The constant .userDomaininMask to restrict our search to our application's sandbox.
Prefered way to refer to a file or directory is with a URL.
let documentsURL = URL(string: documentsPath)!
print(documentsURL)
Second Way
2. using FileManager.default.url function.
let fileManager = FileManager.default
do {
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
print(documentsURL)
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}

`NSBundle.mainBundle().URLForResource` always returns `nil`

I am new to Swift and am using Xcode 6.
I am attempting to read data from the app's plist file, but it is not working.
The data.plist file is included in Xcode's Supporting Files group.
I am using the code below:
var dataList = NSDictionary(contentsOfURL:NSBundle.mainBundle().URLForResource("data", withExtension:"plist"))
however the NSURL:
NSBundle.mainBundle().URLForResource("data", withExtension:"plist")
always returns nil.
I don't know what is wrong.
Generally you would want to use this code to create your plist. This finds the the path to your plist and then moves it into the documents directory if it isn't already there. If you don't move it, you are not allowed to write to it, hence this chunk of code is vital. To fetch the information from the plist, use the second bit of code. Obviously if you have an array rather than a dictionary, you would have to alter it to deal with that.
var path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
path = path.stringByAppendingPathComponent("data.plist")
let fileManager = NSFileManager.defaultManager()
if !fileManager.fileExistsAtPath(path) {
let sourcePath = NSBundle.mainBundle().pathForResource("data", ofType: "plist")
fileManager.copyItemAtPath(sourcePath, toPath: path, error: nil)
}
.
let dict = NSMutableDictionary(contentsOfFile: path) as NSMutableDictionary

Resources