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:
Related
How to access my Model from ViewController and use the Model data to load in table view????
Source Code Link
My ViewController looks like this
import UIKit
class ViewController: UIViewController {
var cclm: CountryCodeListModel?
override func viewDidLoad() {
super.viewDidLoad()
Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(hello), userInfo: nil, repeats: true)
readLocalJSONFile(forName: "countryList")
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
}
#objc func hello()
{
print(cclm?.data?[0].flag)
}
}
and my model class look like this
struct CountryCodeList : Decodable {
var alpha2Code: String?
var alpha3Code: String?
var flag : String?
var name : String?
var code : String?
}
public struct CountryCodeListModel : Decodable {
var data : [CountryCodeList]?
}
var cclm: CountryCodeListModel?
//Method to load json
func readLocalJSONFile(forName name: String) {
do {
if let filePath = Bundle.main.path(forResource: name, ofType: "json") {
let fileUrl = URL(fileURLWithPath: filePath)
let data = try Data(contentsOf: fileUrl)
if let countryCodeObject = parse(jsonData: data) {
cclm = countryCodeObject
print(cclm?.data?[1].alpha2Code ?? "") //Printing Correct Value
}
}
} catch {
print("error: \(error)")
}
}
func parse(jsonData: Data) -> CountryCodeListModel?{
var dataArray : [Dictionary<String,Any>] = [[:]]
var country = Dictionary<String,Any>()
var modelData = Dictionary<String,Any>()
do {
// make sure this JSON is in the format we expect
if let json = try JSONSerialization.jsonObject(with: jsonData, options: []) as? Dictionary<String,Any> {
dataArray.removeAll()
for item in json["data"] as! [Dictionary<String, Any>] {
country = item
let url = URL(string: country["flag"] as? String ?? "")
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
let image = UIImage(data: data!)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileName = url?.lastPathComponent // name of the image to be saved
let fileURL = documentsDirectory.appendingPathComponent(fileName ?? "")
if let data = image?.jpegData(compressionQuality: 1.0){
do {
try data.write(to: fileURL)
country["flag"] = fileURL.absoluteString
//print("file saved")
//urlAsString = fileURL.absoluteString
} catch {
print("error saving file:", error)
}
}
dataArray.append(country)
country.removeAll()
}
modelData["data"] = dataArray
//print(modelData)
let jsonData1 = try JSONSerialization.data(withJSONObject: modelData, options: [])
do {
let decodedData = try JSONDecoder().decode(CountryCodeListModel.self, from: jsonData1)
return decodedData
} catch {
print("error: \(error)")
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
return nil
}
Problem statement:
Iam reading local json and take the url value of flag key and download corresponding images to local. Once i download then am taking the localpath and update in the dictionary and then create JSON object and update my model class.
Now, am trying to access my model class from ViewController like below
print(CountryCodeListModel?.data?[0].name) //check screenshot for error
print(cclm?.data?[0].flag) // this prints nil always
Please check the error screenshots attached2
My JSON look like this
{
"meta":{
"success":true,
"message":"Successfully retrieved country details",
"code":"200"
},
"data":[
{
"alpha2Code":"AF",
"alpha3Code":"AFG",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/afg.png",
"name":"Afghanistan",
"code":"+93"
},
{
"alpha2Code":"AX",
"alpha3Code":"ALA",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/ala.png",
"name":"Aland Islands",
"code":"+358"
},
{
"alpha2Code":"AL",
"alpha3Code":"ALB",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/alb.png",
"name":"Albania",
"code":"+355"
},
{
"alpha2Code":"DZ",
"alpha3Code":"DZA",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/dza.png",
"name":"Algeria",
"code":"+213"
},
{
"alpha2Code":"AS",
"alpha3Code":"ASM",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/asm.png",
"name":"American Samoa",
"code":"+1684"
}
]
}
You are trying to decode something that doesn't exist.
print(CountryCodeListModel?.data?[0].name) //check screenshot for error
print(cclm?.data?[0].flag) // this prints nil always
The above code states that you want:
the name of
the variable data at position 0 of
the struct CountryCodeListModel.
What you want to do is:
the name of
the variable at position 0 of
an INSTANCE of the struct CountryCodeListModel.
For example...
func readLocalJSONFile(forName name: String) {
do {
if let filePath = Bundle.main.path(forResource: name, ofType: "json") {
let fileUrl = URL(fileURLWithPath: filePath)
let data = try Data(contentsOf: fileUrl)
if let countryCodeObject = parse(jsonData: data) {
cclm = countryCodeObject
print(cclm?.data?[1].alpha2Code ?? "") //Printing Correct Value
print(cclm?.data?[0].flag ?? "")
print(countryCodeObject?.data[0].flag ?? "") // Same as the line above
}
}
} catch {
print("error: \(error)")
}
}
Unless you are trying to use a static variable (at which you would use CountryCodeListModel.data), you need to make sure you are actually using an instance of the structure or an object of a class to reference your properties.
CAVEAT
CountryCodeListModel is a structure. CountryCodeListModel() is an instance of the structure CountryCodeListModel. Since you can have multiple instances of a structure, you need to reference a specific structure when accessing data. Thus, CountryCodeListModel.data will not work and it needs to be CountryCodeListModel().data. In this case, you have cclm.data.
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'm trying to save my json file and show it to offline. I'm trying this code but it is not working for me ..
let myData = NSKeyedArchiver.archivedData(withRootObject: self.data)
UserDefaults.standard.set(myData, forKey: "userJson")
UserDefaults.standard.synchronize()
Can any one suggest me better way to save data and show off line ?
You should not save JSON in the UserDefault, Instead save it in file in document directory
I have created generic class which allows to do it easily
//
// OfflineManager.swift
//
//
// Created by Prashant on 01/05/18.
// Copyright © 2018 Prashant. All rights reserved.
//
import UIKit
class OfflineManager: NSObject {
static let sharedManager = OfflineManager()
let LocalServiceCacheDownloadDir = "LocalData"
enum WSCacheKeys {
case CampignList
case CampignDetail(id:String)
case ScreenShotList
var value:String {
switch self {
case .CampignList:
return "CampignList"
case .CampignDetail(let id):
return id
case .ScreenShotList :
return "ScreenShotList"
}
}
}
func getBaseForCacheLocal(with fileName:String) -> String? {
let filePath = FileManager.default.getDocumentPath(forItemName: self.LocalServiceCacheDownloadDir)
if FileManager.default.directoryExists(atPath: filePath) {
return filePath.stringByAppendingPathComponent(fileName)
} else {
if FileManager.default.createDirectory(withFolderName: self.LocalServiceCacheDownloadDir) {
return filePath.stringByAppendingPathComponent(fileName)
}
}
return nil
}
//------------------------------------------------------------
#discardableResult
func cacheDataToLocal<T>(with Object:T,to key:WSCacheKeys) -> Bool {
let success = NSKeyedArchiver.archiveRootObject(Object, toFile: getBaseForCacheLocal(with: key.value)!)
if success {
print( "Local Data Cached\(String(describing: getBaseForCacheLocal(with: key.value)))")
} else {
print("Error")
}
return success
}
//------------------------------------------------------------
func loadCachedDataFromLocal<T>(with key:WSCacheKeys ) -> T? {
return NSKeyedUnarchiver.unarchiveObject(withFile: getBaseForCacheLocal(with: key.value)!) as? T
}
//------------------------------------------------------------
func removeAllCacheDirs () {
do {
try FileManager.default.removeItem(atPath: self.getBaseForCacheLocal(with: "")!)
} catch {
print("error in remove dir \(error.localizedDescription)")
}
}
//--------------------------------------------------------------------------------
}
Here is some helper methods of extension FileManager
public var getDocumentDirectoryPath: String {
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
return documentDirectory
}
public func getDocumentPath(forItemName name: String)-> String {
return getDocumentDirectoryPath.stringByAppendingPathComponent(name)
}
public func directoryExists(atPath filePath: String)-> Bool {
var isDir = ObjCBool(true)
return FileManager.default.fileExists(atPath: filePath, isDirectory: &isDir )
}
public func createDirectory(withFolderName name: String)-> Bool {
let finalPath = getDocumentDirectoryPath.stringByAppendingPathComponent(name)
return createDirectory(atPath: finalPath)
}
Here Is String extension's method
public func stringByAppendingPathComponent(_ path: String) -> String {
let fileUrl = URL.init(fileURLWithPath: self)
let filePath = fileUrl.appendingPathComponent(path).path
return filePath
}
How to use it ?
To save
OfflineManager.sharedManager.cacheDataToLocal(with: object as! [String:Any], to: .CampignList)
To read data
DispatchQueue.global().async {
// GET OFFLINE DATA
if let object:[String:Any] = OfflineManager.sharedManager.loadCachedDataFromLocal(with: .CampignList) {
do {
let data = try JSONSerialization.data(withJSONObject: object, options: [])
let object = try CampaignListResponse.init(data: data)
self.arrCampignList = object.data ?? []
DispatchQueue.main.async {
self.tableVIew.reloadData()
}
} catch {
}
}
}
Note: You can define your own WSCacheKeys for type of your json like i am fetching some campaign list
You can use Realm or CoraData for saving data and showing it when you are offline.
Here is the official link for Realm.You can learn from here.
https://realm.io/docs/swift/latest
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)
At the moment, my data does not get retrieved in another new session and it uses the default values. Do I have to have an existing plist file for this to work? I tried using an existing file with no luck.
I followed the guide here http://battleofbrothers.com/sirryan/saving-game-data-in-spritekit
class GameData : NSObject, NSCoding {
/// Data to save
var variableC : Int = 3
/// Create of shared instance
class var sharedInstance: GameData {
struct Static {
static var instance: GameData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
var gamedata = GameData()
if let savedData = GameData.loadGame() {
gamedata = savedData
}
Static.instance = gamedata
}
return Static.instance!
}
override init() {
super.init()
}
required init(coder: NSCoder) {
super.init()
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(GameData.sharedInstance, forKey: "GameData")
}
class func loadGame() -> GameData? {
// load existing high scores or set up an empty array
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0] as! String
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
let fileManager = NSFileManager.defaultManager()
// check if file exists
if !fileManager.fileExistsAtPath(path) {
// create an empty file if it doesn't exist
println("File doesn't exist")
if let bundle = NSBundle.mainBundle().pathForResource("DefaultFile", ofType: "plist") {
fileManager.copyItemAtPath(bundle, toPath: path, error:nil)
}
}
if let rawData = NSData(contentsOfFile: path) {
// do we get serialized data back from the attempted path?
// if so, unarchive it into an AnyObject, and then convert to an array of HighScores, if possible
if let data = NSKeyedUnarchiver.unarchiveObjectWithData(rawData) as? GameData {
println("We loaded the data!")
return data
}
}
println("we returned Nil")
return nil
}
func save() {
// find the save directory our app has permission to use, and save the serialized version of self.scores - the HighScores array.
let saveData = NSKeyedArchiver.archivedDataWithRootObject(GameData.sharedInstance);
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray;
let documentsDirectory = paths.objectAtIndex(0)as! NSString;
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist");
saveData.writeToFile(path, atomically: true);
}
}
I load the gamedata in the init function with this
var gameData: GameData = GameData.sharedInstance
Updating the data
gameData.variableC = gameData.variableC + 1
gameData.save()
println(gameData.variableC)
You save the data but don't use it in your init. In initWithCoder:
required init(coder: NSCoder) {
super.init()
GameData.shaderInstance = coder.decodeObjectForKey("GameData")
}
Like Jozsef said, I forgot to decode the data. However I need to decode each of the individual variables and then copy it over to the GameData for it to work.
Here's
class GameData : NSObject, NSCoding {
/// Data to save
var variableC : Int! = 3
/// Create of shared instance
class var sharedInstance: GameData {
struct Static {
static var instance: GameData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
var gamedata = GameData()
if let savedData = GameData.loadGame() {
gamedata.variableC = savedData.variableC
}
Static.instance = gamedata
}
return Static.instance!
}
override init() {
super.init()
}
required init(coder: NSCoder) {
super.init()
self.variableC = coder.decodeObjectForKey("variableC") as? Int
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(GameData.sharedInstance.variableC, forKey: "variableC")
}
class func loadGame() -> GameData? {
// load existing high scores or set up an empty array
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0] as! String
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
let fileManager = NSFileManager.defaultManager()
// check if file exists
if !fileManager.fileExistsAtPath(path) {
// create an empty file if it doesn't exist
println("File doesn't exist")
if let bundle = NSBundle.mainBundle().pathForResource("DefaultFile", ofType: "plist") {
fileManager.copyItemAtPath(bundle, toPath: path, error:nil)
}
}
if let rawData = NSData(contentsOfFile: path) {
// do we get serialized data back from the attempted path?
// if so, unarchive it into an AnyObject, and then convert to an array of HighScores, if possible
if let data = NSKeyedUnarchiver.unarchiveObjectWithData(rawData) as? GameData {
println("We loaded the data!")
return data
}
}
return nil
}
func save() {
// find the save directory our app has permission to use, and save the serialized version of self.scores - the HighScores array.
let saveData = NSKeyedArchiver.archivedDataWithRootObject(GameData.sharedInstance);
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray;
let documentsDirectory = paths.objectAtIndex(0)as! NSString;
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist");
saveData.writeToFile(path, atomically: true);
}
}