i would like to open an existing Database with FMDB. The DB should be a sqlite Database with the ending .db.
My Code is:
static let shared: DBManager = DBManager()
let databaseFileName = "/mydb.db"
var pathToDatabase: String!
var pathNSURL: NSURL!
var database: FMDatabase!
override init() {
super.init()
let documentsDirectory = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString) as String
let zwerg = documentsDirectory + databaseFileName
let pathNSURL = NSURL(fileURLWithPath: zwerg)
let pathString = documentsDirectory + databaseFileName
pathToDatabase = pathNSURL.path
print(pathToDatabase)
}
func firstQuestion() -> Bool {
if openDatabase(){
let query = "SELECT * FROM movie"
do {
let result = try database.executeQuery(query, values: nil)
print(result)
}
catch {
print(error.localizedDescription)
}
database.close()
return true
}
return false
}
func openDatabase() -> Bool {
if database == nil {
print(FileManager.default.fileExists(atPath: pathToDatabase))
if FileManager.default.fileExists(atPath: pathToDatabase) {
print("Database set new path -> File exists")
database = FMDatabase(path: pathToDatabase)
}
}
if database != nil {
print("Database != nil")
if database.open() {
print("Database is open in != nil")
return true
}
}
return false
}
Im calling the Method firstQuestion() from a VC:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print(DBManager.shared.firstQuestion())
}
My Problem is that the fileExists-Method always returns false. The Filename is correct. This is my first Project with a database in swift, so perhaps i made a stupid mistake... Any suggestions?
Thanks for your help!
let fileManager = FileManager.default
let docURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let databaseURL = docURL.appendingPathComponent("databaseNaeme.db")
and initialize database as:
database = FMDatabase(path: databaseURL.absoluteString)
Related
I'm working a project where I need to use a SQLite database to read, create , and edit different objects. I thought I had established the connection properly but, it turns out I had only established a read only connection. How do I modify this code to be a read-write connection using SQLite.swift
import Foundation
import SQLite
import UIKit
let path = Bundle.main.path(forResource: "Assignment2", ofType: "sqlite3")
//Array of customer structs to populate the table
var customerArray: [Customer] = []
class CustomerPageVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
//IBOutlets
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var addCustButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//Additional Setup
do {
//Search for DB in documents directory
let db = try Connection(path!)
let customers = Table("Customers")
//Define the columns of the table as expressions
let id = Expression<Int64>("CustomerID")
let name = Expression<String>("CustomerName")
let contactName = Expression<String>("ContactName")
let address = Expression<String>("Address")
let city = Expression<String>("City")
let postalCode = Expression<String>("PostalCode")
let country = Expression<String>("Country")
//Load the data from db file into customerArray
for customer in try db.prepare(customers) {
let cust = Customer(Int(customer[id]), customer[name], customer[contactName], customer[address], customer[city], customer[postalCode], customer[country])
customerArray.append(cust)
}
}
catch {
print(error)
}
tableView.delegate = self
tableView.dataSource = self
}
}
Edit there's a func copyDatabaseIfNeeded in the documentation so maybe my true question is in what context do I use this func to copy the database to the application support directory?
func copyDatabaseIfNeeded(sourcePath: String) -> Bool {
let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let destinationPath = documents + "/db.sqlite3"
let exists = FileManager.default.fileExists(atPath: destinationPath)
guard !exists else { return false }
do {
try FileManager.default.copyItem(atPath: sourcePath, toPath: destinationPath)
return true
} catch {
print("error during file copy: \(error)")
return false
}
}
You can find the documentation for SQLite.swift here https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#connecting-to-a-database
When database created in your document directory it will be there permanently till user delete the app or you delete that directory.
so read and write connection will occur when you save your sql file in this directory.
as you wanted I make a new example for you that I created recently.
import UIKit
import SQLite
class ViewController: UIViewController {
let customer = Table("Customer")
let id = Expression<Int64>("CustomerID")
let name = Expression<String>("CustomerName")
override func viewDidLoad() {
super.viewDidLoad()
let db = makeDBConnection()
createTable(db: db)
insertNewCustomer(db: db)
fetchDatabase(db: db)
}
private func makeDBConnection() -> Connection {
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
let sourcePath = "\(path)/db.sqlite3"
_ = copyDatabaseIfNeeded(sourcePath: sourcePath)
return try! Connection(sourcePath)
}
private func createTable(db: Connection) {
//Define the columns of the table as expressions
do {
try db.run(customer.create(block: { table in
table.column(id, primaryKey: true)
table.column(name)
}))
} catch {
// This tells you table already created for second time you running this code
}
}
private func insertNewCustomer(db: Connection) {
// This will insert a new customer into your table each time app runs
let insert = customer.insert(name <- "Reza")
try! db.run(insert)
}
private func fetchDatabase(db: Connection) {
for customer in try! db.prepare(customer) {
print("id: \(customer[id]), name: \(customer[name])")
}
}
func copyDatabaseIfNeeded(sourcePath: String) -> Bool {
let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let destinationPath = documents + "/db.sqlite3"
let exists = FileManager.default.fileExists(atPath: destinationPath)
guard !exists else { return false }
do {
try FileManager.default.copyItem(atPath: sourcePath, toPath: destinationPath)
return true
} catch {
print("error during file copy: \(error)")
return false
}
}
}
the result is when I run the app each time:
I am saving datas on on the json file on the first VC , load the data as well and display it when switching tab. When I kill the app or re run the app again, add new datas to the JSON file, only those new datas are on the JSON file, previous datas are gone(deleted without deleting them manually) and can not be load. How do I save the file so that next time I run the program it will just append to the previous data ?
class ViewController: UIViewController {
var game : Game?
var weekLeague : [[Game]]? = []
override func viewDidLoad() {
super.viewDidLoad()
creation()
}
#IBAction func endWLButton(_ sender: UIButton) {
if games != nil {
weekLeague?.append(games!)
}
save()
}
func save(){
guard let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileUrl = documentDirectoryUrl.appendingPathComponent("ArrayOfArray.json")
print(fileUrl)
let json = try? JSONEncoder().encode(weekLeague)
do {
try json?.write(to: fileUrl)
print(json!)
print(weekLeague)
print("JSON data was written to teh file successfully!")
}catch{
print(error)
}
}
func ShouldSendGame(game: Game) {
self.game = game
games?.append(game)
}
func creation(){
let documentsDirectoryPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let documentsDirectoryPath = NSURL(string: documentsDirectoryPathString)!
let jsonFilePath = documentsDirectoryPath.appendingPathComponent("ArrayOfArray.json")
let fileManager = FileManager.default
var isDirectory: ObjCBool = false
// creating a .json file in the Documents folder
if !fileManager.fileExists(atPath: jsonFilePath!.path, isDirectory: &isDirectory) {
let created = fileManager.createFile(atPath: jsonFilePath!.path, contents: nil, attributes: nil)
if created {
print("File created ")
} else {
print("Couldn't create file for some reason")
}
} else {
print("File already exists")
}
}
}
class AllLeagueController : UITableViewController {
var arrayOfArrayGamesCopy : [[Game]] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global().async {
self.loadData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
func loadData() {
guard let documentsDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileUrl = documentsDirectoryUrl.appendingPathComponent("ArrayOfArray.json")
do{
let data = try Data(contentsOf:fileUrl)
let decoder = JSONDecoder()
let jsonData = try decoder.decode([[Game]].self, from: data)
arrayOfArrayGamesCopy = jsonData
print("Succes")
print(jsonData.count)
} catch{
print(error)
}
}
}
You need to load data here before save ... Also you need to have separate class for saving and loading data .. dot do that in controller .. its against Single Responsibility Principle ...Your load function should return array of [Game] and save data should return success or failure
#IBAction func endWLButton(_ sender: UIButton) {
//load and set data in games from file ...then append current data and save to file
if games != nil {
weekLeague?.append(games!)
}
save()
}
I want to store an image into sqlite3. I am new to iOS please help me out how to save this image and retrieve in image view.
Appdelegate.swift
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
createDB()
return true
}
func createDB()
{
// create db
let dir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let dbpath = dir.appendingPathComponent("imgdatabase.sqlite")
print(dir)
// check if file exist
let m = FileManager()
if m.fileExists(atPath: dbpath.path)
{
print("file exist no need to create")
}
else
{
m.createFile(atPath: dbpath.path, contents: nil, attributes: nil)
}
// open db
var op : OpaquePointer? = nil
if sqlite3_open(dbpath.path, &op)==SQLITE_OK
{
print("db open successfuly")
let query = "create table img_save(img text)"
if sqlite3_exec(op, query.cString(using: .utf8), nil, nil, nil)==SQLITE_OK
{
print("table created ")
}
else
{
print("table not created")
}
}
else
{
print("db unable to open")
}
sqlite3_close(op)
}
Image_save_ViewController.swift
import UIKit
class Image_save_ViewController: UIViewController {
#IBAction func img_display_click(_ sender: AnyObject) {
}
#IBOutlet weak var imgview: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func img_save_click(_ sender: AnyObject) {
let image : UIImage = UIImage(named: "35-handgun-png-image.png")!
let imageData : NSData = UIImagePNGRepresentation(image)! as NSData
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
var decodeimg : NSData = NSData(base64Encoded: strBase64, options: NSData.Base64DecodingOptions(rawValue: 0))!
imgview.image = UIImage(data : decodeimg as Data)!
let dir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let dbpath = dir.appendingPathComponent("imgdatabase.sqlite")
print(dir)
// check if file exist
let m = FileManager()
if m.fileExists(atPath: dbpath.path)
{
print("file exist no need to create")
}
else
{
m.createFile(atPath: dbpath.path, contents: nil, attributes: nil)
print("db file created")
}
// open db
var op : OpaquePointer? = nil
if sqlite3_open(dbpath.path, &op)==SQLITE_OK
{
print("db open successfuly")
let image_d = strBase64
let query = String.init(format : "insert into img_save values('%#')", image_d)
if sqlite3_exec(op, query.cString(using: .utf8), nil, nil, nil)==SQLITE_OK
{
print("image saved successfuly")
}
else
{
print("unable to save")
}
}
else
{
print("unable to open db")
}
sqlite3_close(op)
}
I AM ADDING HERE READDB() FUNC THAT I AM FETCHING IMAGE FROM DB
#IBOutlet weak var imgview: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
readDB()
//imgview.image = UIImage(named: "35-handgun-png-image.png")
}
func readDB()
{
let image : UIImage = UIImage(named: "35-handgun-png-image.png")!
let imageData = UIImagePNGRepresentation(image)!
let docdir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
print(docdir)
let imgurl = docdir.appendingPathComponent("35-handgun-png-image.png")
try! imageData.write(to: imgurl)
let newImage = UIImage(contentsOfFile: imgurl.path)
let dir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let dbpath = dir.appendingPathComponent("imgdatabase.sqlite")
//check file if exist
let m = FileManager()
if m.fileExists(atPath: dbpath.path)
{
print("file exist no need to create")
}
else
{
m.createFile(atPath: dbpath.path, contents: nil, attributes: nil)
print("file created")
}
//open db
var opq : OpaquePointer? = nil
if sqlite3_open(dbpath.path, &opq)==SQLITE_OK
{
let query = "select * from img_save"
var st : OpaquePointer? = nil
if sqlite3_prepare(opq, query.cString(using: .utf8), -1, &st, nil)==SQLITE_OK
{
while sqlite3_step(st)==SQLITE_ROW
{
let imgd = String.init(format : "%s",sqlite3_column_text(st, 0))
//let imgd = String.init(format : "%s",sqlite3_value_text(st!))
imgview.image = UIImage(named : imgd)
print("nil value")
}
}
}
sqlite3_close(opq)
}
EARLIER IT WAS SHOWING FATAL ERROR FOUND NIL WHILL UNWRAPPING VALUES BUT NOW I HAVE TAKEN IMAGE VIEW TO STORE COLUM VALUE AND DISPLAY IT USING IMAGE VIEW..SO PLEASE TELL ME HOW TO ACHIEVE THIS I AM GETTING NIL VALUE OUT THERE
If possible you write your image in application document directory by specific name.
And save that name in sqlite table. Retrieve name from sqlite and image from document directory.
I've already created sqlite tables for my app, but now I want to add a new column in table to the database. ALTER TABLE will help me in this problem but first i want to check the database version.
i am using PRAGMA user_version to check the user version and update the user_version but it always returning user_version as 0.
var database: FMDatabase? = nil
class func getInstance() -> ModelManager{
if(sharedInstance.database == nil){
sharedInstance.database = FMDatabase(path: Util.getPath("XXXX.sqlite"))
}
return sharedInstance
}
func userVersion(){
sharedInstance.database!.open()
var userVer = Int()
let resultSet = sharedInstance.database?.executeQuery("pragma user_version", withArgumentsInArray: nil)
userVer = Int(resultSet!.intForColumn("user_version"))
print("user version : ",userVer)
sharedInstance.database!.close()
}
func updateUserVersion(){
sharedInstance.database!.open()
sharedInstance.database?.executeUpdate("PRAGMA user_version=1", withArgumentsInArray: nil)
sharedInstance.database!.close()
}
The code below works fine with Swift 4
import UIKit
import FMDB
class DataConnection: NSObject {
static let databaseVersion = 2
static var isDatabaseUpdated = false
static var database: FMDatabase? = nil
class func databaseSetup() {
if database == nil {
let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let dpPath = docsDir.appendingPathComponent("database.sqlite")
let file = FileManager.default
if(!file.fileExists(atPath: dpPath.path)) {
copyDatabase(file: file, dpPath: dpPath)
database = FMDatabase(path: dpPath.path)
do {
database!.open()
try database!.executeUpdate("PRAGMA user_version = \(databaseVersion)", values: nil)
database!.close()
isDatabaseUpdated = true
}catch {
print("Error on updating user_version")
}
}else {
database = FMDatabase(path: dpPath.path)
if !isDatabaseUpdated {
var currentVersion = 0
do {
database!.open()
let resultSet: FMResultSet! = try database!.executeQuery("pragma user_version", values: nil)
while resultSet.next() {
currentVersion = Int(resultSet.int(forColumn: "user_version"))
}
database!.close()
}catch {
print("Error on getting user_version")
}
if databaseVersion > currentVersion {
do {
try file.removeItem(at: dpPath)
}catch {
print("Error on getting user_version")
}
copyDatabase(file: file, dpPath: dpPath)
database = FMDatabase(path: dpPath.path)
do {
database!.open()
try database!.executeUpdate("PRAGMA user_version = \(databaseVersion)", values: nil)
database!.close()
isDatabaseUpdated = true
}catch {
print("Error on updating user_version")
}
}else {
isDatabaseUpdated = true
}
}
}
}
}
private class func copyDatabase(file: FileManager, dpPath: URL){
let dpPathApp = Bundle.main.path(forResource: "database", ofType: "sqlite")
print("resPath: "+String(describing: dpPathApp))
do {
try file.copyItem(atPath: dpPathApp!, toPath: dpPath.path)
print("copyItemAtPath success")
} catch {
print("copyItemAtPath fail")
}
}
}
You need to call the next() method on your resultSet so that it loads the first row, before you access intForColumn.
Besides, since you use FMDB, take a look at https://github.com/groue/GRDB.swift: it's a Swift wrapper for SQLite that will look familiar to FMDB users. But you'd simply write let userVer = Int.fetchOne(db, "pragma user_version") this time.
How do I upload and load back images from cloud kit with swift?
What attribute type do I use?
What code do I use? This is the code I use currently...
func SaveImageInCloud(ImageToSave: UIImage) {
let newRecord:CKRecord = CKRecord(recordType: "ImageRecord")
newRecord.setValue(ImageToSave, forKey: "Image")
if let database = self.privateDatabase {
database.saveRecord(newRecord, completionHandler: { (record:CKRecord!, error:NSError! ) in
if error != nil {
NSLog(error.localizedDescription)
}
else {
dispatch_async(dispatch_get_main_queue()) {
println("finished")
}
}
})
}
You need to create a CKAsset and add that to your record. You can do that with code like this:
func SaveImageInCloud(ImageToSave: UIImage) {
let newRecord:CKRecord = CKRecord(recordType: "ImageRecord")
let nsDocumentDirectory = NSSearchPathDirectory.DocumentDirectory
let nsUserDomainMask = NSSearchPathDomainMask.UserDomainMask
if let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) {
if paths.count > 0 {
if let dirPath = paths[0] as? String {
let writePath = dirPath.stringByAppendingPathComponent("Image2.png")
UIImagePNGRepresentation(ImageToSave).writeToFile(writePath, atomically: true)
var File : CKAsset? = CKAsset(fileURL: NSURL(fileURLWithPath: writePath))
newRecord.setValue(File, forKey: "Image")
}
}
}
if let database = self.privateDatabase {
database.saveRecord(newRecord, completionHandler: { (record:CKRecord!, error:NSError! ) in
if error != nil {
NSLog(error.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
println("finished")
}
}
})
}
Here's something similar to Edwin's answer but a little more compact. I've tested this and it works well.
This example is saving "myImage" UIImageView into "mySaveRecord" CKRecord, just replace those names with your respective ones.
let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imageFilePath = documentDirectory.stringByAppendingPathComponent("lastimage")
UIImagePNGRepresentation(myImage).writeToFile(imageFilePath, atomically: true)
let asset = CKAsset(fileURL: NSURL(fileURLWithPath: imageFilePath))
mySaveRecord.setObject(asset, forKey: "ProfilePicture")
CKContainer.defaultContainer().publicCloudDatabase.saveRecord(mySaveRecord, completionHandler: {
record, error in
if error != nil {
println("\(error)")
} else {
//record saved successfully!
}
})
I created this little extension in Swift 5 to convert from UIImage to CKAsset:
extension UIImage {
func toCKAsset(name: String? = nil) -> CKAsset? {
guard let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
return nil
}
guard let imageFilePath = NSURL(fileURLWithPath: documentDirectory)
.appendingPathComponent(name ?? "asset#\(UUID.init().uuidString)")
else {
return nil
}
do {
try self.pngData()?.write(to: imageFilePath)
return CKAsset(fileURL: imageFilePath)
} catch {
print("Error converting UIImage to CKAsset!")
}
return nil
}
}
You can then use it as you have it in your question:
if let asset = ImageToSave.toCKAsset() {
newRecord.setObject(asset, forKey: "Image")
CKContainer.defaultContainer().publicCloudDatabase.saveRecord(newRecord, completionHandler: {
record, error in
if error != nil {
println("\(error)")
} else {
// saved successfully!
}
})
}
This answer works with Swift 2.2 & iOS 9, and separates the file creation from the upload so that you can properly test against both, since they are distinct actions with their own potential issues.
For the uploadPhoto function, the recordType variable is the value you use in your CloudKit dashboard. The "photo" key in the photo["photo"] = asset line is the field name for your record type.
func uploadPhoto(image: UIImage, recordName: String) {
let privateDB = CKContainer.defaultContainer().privateCloudDatabase
let photoID = CKRecordID(recordName: recordName)
let photo = CKRecord(recordType: recordType, recordID: photoID)
let asset = CKAsset(fileURL: writeImage(image))
photo["photo"] = asset
privateDB.saveRecord(photo) { (record, error) in
guard error == nil else {
print(error?.localizedDescription)
return
}
print("Successful")
}
}
func writeImage(image: UIImage) -> NSURL {
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let fileURL = documentsURL.URLByAppendingPathComponent(NSUUID().UUIDString + ".png")
if let imageData = UIImagePNGRepresentation(image) {
imageData.writeToURL(fileURL, atomically: false)
}
return fileURL
}
You can call this with the following:
uploadPhoto(UIImage(named: "foo.png")!, recordName: "bar")
You'll want to pick the Asset value type in the dashboard for this value.
newRecord.setValue(ImageToSave, forKey: "Image")
UIImage is not an allowed type on CKRecord. Your best option is to write this image out to a file, then create a CKAsset and set that on the record.