I have created a function for mapping json data to a sqlite store using sqlite.swift. I am receiving strange behavior though since the first time I try to run an insert I get a "No such table" error". But the inserts work perfectly after that initial throw. My code works by making a connection to the database onload using SQLData.instance.connect(). Then every time a call to insert is placed it initializes the table to make sure it is set up with all the columns within the data.
//attached to button
let userJSON = JSON(["user_id" : id, "username" : email])
SQLData.instance.insert(table: "users", data: userJSON) { result in
if let result = result{
print(result)
}
}
//sql class
class SQLData {
static let instance = SQLData()
var db: Connection? = nil
func connect() {
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
do {
self.db = try Connection("\(path)/contacts.sqlite3")
if let db = self.db {
try db.run(Table("users").drop(ifExists: true))
}
print("SQLite connection established.")
} catch {
print("Couldn't create SQLite connection.")
}
}
func initTable(table: String, data: JSON, completionHandler: (Table?) -> Void){
if let db = db {
let t = Table(table)
do {
try db.run(t.create(temporary: false, ifNotExists: true) { r in
r.column(Expression<Int>("id"), primaryKey: true)
r.column(Expression<Bool>("synced"), defaultValue: false)
for (key,val) in data {
if val.bool != nil{
r.column(Expression<Bool>(key), defaultValue: false)
} else if val.int != nil {
r.column(Expression<Int>(key), defaultValue: 0)
} else if val.double != nil {
r.column(Expression<Double>(key), defaultValue: 0.0)
} else {
r.column(Expression<String?>(key), defaultValue: "")
}
}
completionHandler(t)
})
} catch let err {
print(err)
completionHandler(nil)
}
} else {
completionHandler(nil)
}
}
func insert(table: String, data: JSON, completionHandler: #escaping (Int64?) -> Void){
self.initTable(table: table, data: data) { t in
if let db = db, let t = t {
var query: [Setter] = []
for (key, val) in data {
if val.bool != nil{
query.append(Expression<Bool>(key) <- val.boolValue)
} else if val.int != nil {
query.append(Expression<Int>(key) <- val.intValue)
} else if val.double != nil {
query.append(Expression<Double>(key) <- val.doubleValue)
} else {
query.append(Expression<String?>(key) <- val.stringValue)
}
}
do {
try db.transaction {
let insert = t.insert(query)
let rowid = try db.run(insert)
completionHandler(rowid)
}
} catch let err {
print(err)
completionHandler(nil)
}
} else {
completionHandler(nil)
}
}
}
}
Change your connection function as below
func connect(){
do {
let databaseFilePath = Bundle.main.path(forResource: "contacts", ofType: "sqlite3")!
db = try Connection(databaseFilePath)
} catch {
print(error)
}
}
and bu sure your .sqlite3 (or .db) file in to your project hierarchy and copy bundle resources like images
Related
For a school project, I have to create a swiftUi chat app.
I'm fairly new to swift but managed to use the Network framework to connect to my chat server using the following code :
import Foundation
import Network
class Client {
let connection: Connection
let host: NWEndpoint.Host
let port: NWEndpoint.Port
var queue: DispatchQueue = DispatchQueue.init(label: "keepAlive")
init(host: String, port: UInt16) {
self.host = NWEndpoint.Host(host)
self.port = NWEndpoint.Port(rawValue: port)!
let nwConnection = NWConnection(host: self.host, port: self.port, using: .tcp)
connection = BaburuConnection(nwConnection: nwConnection)
}
func start() {
print("Client started \(host) \(port)")
connection.didStopCallback = didStopCallback(error:)
connection.start()
}
func stop() {
connection.stop()
}
func sendMessage(content: String, channel: UUID) {
let msg = Data(Frame.Message(value: content, channel: channel).serialize())
connection.send(data: msg)
}
func sendCommand(command: CommandKind, args: [String]? = nil) {
let cmd = Data(Frame.Cmd(cmd: Command(kind: command, args: args)).serialize())
connection.send(data: cmd)
}
func isConnected() -> Bool {
return connection.running
}
func didStopCallback(error: Error?) {
if error == nil {
exit(EXIT_SUCCESS)
} else {
exit(EXIT_FAILURE)
}
}
}
import Foundation
import Network
var KeepAlive = 30
var maxReadLength = 4096
class Connection {
let nwConnection: NWConnection
let queue = DispatchQueue(label: "Client connection Q")
var running = false
private var serializedKA = Frame.KeepAlive().serialize()
private lazy var timer = Timer(timeInterval: TimeInterval(KeepAlive), repeats: true) { _ in
self.send(data: Data(Frame.KeepAlive().serialize()))
}
init(nwConnection: NWConnection) {
self.nwConnection = nwConnection
}
var didStopCallback: ((Error?) -> Void)? = nil
func start() {
print("connection will start")
nwConnection.stateUpdateHandler = stateDidChange(to:)
setupReceive()
nwConnection.start(queue: queue)
running = true
DispatchQueue.global(qos: .background).async {
let runLoop = RunLoop.current
runLoop.add(self.timer, forMode: .default)
runLoop.run()
}
}
private func stateDidChange(to state: NWConnection.State) {
switch state {
case .waiting(let error):
connectionDidFail(error: error)
case .ready:
print("Client connection ready")
case .failed(let error):
connectionDidFail(error: error)
default:
break
}
}
private func setupReceive() {
nwConnection.receive(minimumIncompleteLength: 1, maximumLength: maxReadLength) { (data, _, isComplete, error) in
if let data = data, !data.isEmpty {
let message = String(data: data, encoding: .utf8)
print("connection did receive, data: \(data as NSData) string: \(message ?? "-" )")
switch(Frame.parse(reader: BufferedString(str: message!))) {
case .success(let res):
if res is Frame.MessageWrapper {
print("message : ", res)
} else if res is Frame.Cmd {
print("command : ", res)
} else {
print("TODO")
}
case .failure(let err):
print(err)
}
}
if isComplete {
self.connectionDidEnd()
} else if let error = error {
self.connectionDidFail(error: error)
} else {
self.setupReceive()
}
}
}
func send(data: Data) {
nwConnection.send(content: data, completion: .contentProcessed( { error in
if let error = error {
self.connectionDidFail(error: error)
return
}
print("connection did send, data: \(data as NSData)")
}))
}
func stop() {
print("connection will stop")
self.timer.invalidate()
running = false
stop(error: nil)
}
private func connectionDidFail(error: Error) {
print("connection did fail, error: \(error)")
self.stop(error: error)
}
private func connectionDidEnd() {
print("connection did end")
self.stop(error: nil)
}
private func stop(error: Error?) {
self.nwConnection.stateUpdateHandler = nil
self.nwConnection.cancel()
if let didStopCallback = self.didStopCallback {
self.didStopCallback = nil
didStopCallback(error)
}
}
}
The code works fine when I use it with a blocking operation beneath it but I can't think of any good solution for implementing it in mw swiftUi app since I need to keep some sort of reference on the Client object in order to call it's sendMessage and sendCommand methods with a button and notify my ui when I receive a message.
Does anyone has any advises ?
Also, if there is a cleaner way than recursion for async reading, I would gladly change it.
I want to search first in the favorite collection for a specific artwork ID and get all the documents where field of artwork id is equal to it. Then check in all these documents whether the user id field is equal to the current user if yes it exists and need to delete the document if not exists, I want to create a new document with current id user and artwork. this mean added to favorite list.
But I’m getting error: if one of the users click on the heart button it will be added to favorite. If other user sign in and click on the heart button for the same artwork it will not be added. only for one user.
//MARK: to get the fav
func checkFavorite(artwork_id: String, completion: #escaping (Bool, String) -> Void) {
let collectionRef = FirebaseReference(.fav)
let user_id = Auth.auth().currentUser?.uid
collectionRef.whereField("artwork_id", isEqualTo: artwork_id).getDocuments { (snapshot, err) in
if let err = err {
print("Error getting document: \(err)")
} else if (snapshot?.isEmpty)! {
completion(false, "")
} else {
for document in (snapshot?.documents)! {
let uid = document.data()["uid"] as? String ?? ""
if uid == user_id {
let documentId = document.documentID
//let isLiked = document.data()["isLiked"]
completion(true, documentId)
}
/** else {
completion(false, "")
}**/
}
}
}
}
//MARK: Add to favorite
func addToFavorite( artwork_id: String, uid: String) {
var ref: DocumentReference? = nil
ref = FirebaseReference(.fav).addDocument(data: [
"uid":uid,
"artwork_id":artwork_id,
"timestamp": Date(),
"artwork": FirebaseReference(.artworks).document(artwork_id),
"isLiked": true
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Artwork fav added with ID: \(ref!.documentID)")
}
}
}
//MARK: delete Favorite
func deleteFavorite(documentID: String) {
FirebaseReference(.fav).document(documentID).delete { (err) in
if let err = err {
print("Error deleting favorite: \(err)")
} else {
print("favorite have been deleted")
}
}
}
Here the call of the function:
Button(action: { self.liked.toggle()
//if self.liked {
let like = Int.init(self.data.num_likes)!
checkFavorite(artwork_id: self.data.id) { (success,docId) in
if success == false {
addToFavorite(artwork_id: self.data.id, uid: self.uid)
FirebaseReference(.artworks).document(self.data.id).updateData(["num_likes": "\(like + 1)"
])
}
else {
FirebaseReference(.artworks).document(self.data.id).updateData(["num_likes": "\(like - 1)"
])
deleteFavorite(documentID: docId)
}
}
//}
}) {
Image(systemName: self.liked ? "heart.fill" : "heart" )
.foregroundColor(Color("Color"))
}
I am trying to transition an app from UIKit to SwiftUI which depends on basic DynamoDB resources but I have hit a snag in forcing the view to refresh as data is added to the list. I have been at this set of code for hours trying different things and I thought I might see if anyone might know why the 'SessionsData' seems to be thrown away and will not accumulate the 'Sessions' objects.
Does anyone have any quick thoughts???
class SessionsData: ObservableObject {
let didChange = PassthroughSubject<SessionsData, Never>()
#Published var data: [Sessions] = [] {
didSet {
didChange.send(self)
}
}
init() {
load()
}
func load() {
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
var temp : [Sessions] = []
dynamoDBObjectMapper.scan(Sessions.self, expression: scanExpression).continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else if let paginatedOutput = task.result {
for session in paginatedOutput.items as! [Sessions] {
print("Item Found")
temp.append(session)
}
DispatchQueue.main.async {
self.data = temp
self.didChange.send(self)
}
}
print(self.data.count)
return true
})
}
}
struct Events: View {
#ObservedObject var sessionsData = SessionsData()
var body: some View {...}
}
Looks like you've over-complicated the code. The PassthroughSubject is unnecessary. Whenever you change a #Published property, it should trigger an update.
class SessionsData: ObservableObject {
#Published var data: [Sessions] = []
init() {
load()
}
func load() {
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
var temp : [Sessions] = []
dynamoDBObjectMapper.scan(Sessions.self, expression: scanExpression).continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else if let paginatedOutput = task.result {
for session in paginatedOutput.items as! [Sessions] {
print("Item Found")
temp.append(session)
}
DispatchQueue.main.async {
self.data = temp
}
}
print(self.data.count)
return true
})
}
}
I don't have experience with DynamoDB, but here are a few things from SwiftUI / Combine perspective. In ObseravbleObjects have change a significant bit and and are now declared with objectWillChange and then sending newValue in willSet:
class SessionsData: ObservableObject {
public let objectWillChange = PassthroughSubject<[Sessions], Never>()
public private(set) var items: [Sessions] = [] {
willSet {
objectWillChange.send(newValue)
}
}
init() {
self.items = []
}
public func load() {
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
var temp: [Sessions] = []
dynamoDBObjectMapper
.scan(Sessions.self,
expression: scanExpression)
.continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else if let paginatedOutput = task.result,
let sessions = paginatedOutput.items as? [Sessions] {
temp.append(contentsOf: sessions)
}
DispatchQueue.main.async {
self.items = temp
}
}
return true
})
}
}
For the UI part you have to just call your load() method defined above in .onApear() and everything else should happen magically:
struct Events: View {
#ObservedObject var sessionsData: SessionsData
var body: some View {
List {
ForEach(self.sessionsData.items) { session in
Text(session.name) // or something of that kind
}
} .onAppear(perform: { self.sessionsData.load() })
}
}
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'Unable to parse the format
string "12+6+ == 1"'
I want to validate expression is valid or not. And I am trying this using following code :
let equationString = "12+6+"
do {
let expr = try NSExpression(format: equationString)
if let result = expr.expressionValue(with: nil, context: nil) as? NSNumber {
let x = result.doubleValue
print(x)
} else {
print("failed")
}
}
catch {
print("failed")
}
I have used try-catch statement, but still I am getting crash here. Is there any solution for this?
Any help would be appreciated.
And you can use it with try:
let equationString = "12+6+"//"12*/6+10-5/2"
do {
try TryCatch.try({
let expr = NSExpression(format: equationString)
if let result = expr.expressionValue(with: nil, context: nil) as? NSNumber {
let x = result.doubleValue
print(x)
} else {
print("failed")
}
})
} catch {
print("Into the catch.....")
// Handle error here
}
TryCatch.h:
+ (BOOL)tryBlock:(void(^)(void))tryBlock
error:(NSError **)error;
TryCatch.m:
#implementation TryCatch
+ (BOOL)tryBlock:(void(^)(void))tryBlock
error:(NSError **)error
{
#try {
tryBlock ? tryBlock() : nil;
}
#catch (NSException *exception) {
if (error) {
*error = [NSError errorWithDomain:#"com.something"
code:42
userInfo:#{NSLocalizedDescriptionKey: exception.name}];
}
return NO;
}
return YES;
}
#end
what I have to write here?
db = Graph(cloud: "iCloud.com.devname.appname", completion: { (done, error) in
if let errore = error {
debugPrint("Error iCloud: \(errore.localizedDescription)")
return
}
})
or
db = Graph(cloud: "fantasyString", completion: { (done, error) in
if let errore = error {
debugPrint("Errore iCloud: \(errore.localizedDescription)")
return
}
})
I tried everything but I'm unable to make iCloud works
Thank you for your help, Daniel
EDIT:
the way I read data form db:
var customers : [Entity] {
let search = Search<Entity>(graph: db).for(types: "Customers")
return search.sync(completion: nil).sorted { ($0["name"] as! String) < ($1["name"] as! String)}
}
the way I save the record:
func newCustomer(name:String, phone:String, mail:String, closure: #escaping ()->()) {
let cliente = Entity(type: "Customers")
cliente["name"] = name
cliente["phone"] = phone
cliente["mail"] = mail
db.sync { (done, error) in
if let errore = error {
debugPrint("Errore addCustomer: \(errore.localizedDescription)")
return
}
if done { closure() }
}
}
EDIT 2: the GraphDelegate implementation:
extension DataManager: GraphDelegate {
func graphWillPrepareCloudStorage(graph: Graph, transition: GraphCloudStorageTransition) {
debugPrint("graphWillPrepareCloudStorage")
if transition == .initialImportCompleted {
debugPrint("iCloud initialImportCompleted ok")
self.clientiCont?.tableView.reloadData()
}
}
func graphDidPrepareCloudStorage(graph: Graph) {
debugPrint("graphDidPrepareCloudStorage")
self.clientiCont?.tableView.reloadData()
}
func graphWillUpdateFromCloudStorage(graph: Graph) {
debugPrint("graphWillUpdateFromCloudStorage")
self.clientiCont?.tableView.reloadData()
}
func graphDidUpdateFromCloudStorage(graph: Graph) {
debugPrint("graphDidUpdateFromCloudStorage")
// refresh clienti
self.clientiCont?.tableView.reloadData()
// refresh lista ordini
self.gestCliCont?.tableOrder.reloadData()
// refresh oridine
self.gestOrdCont?.showOrder()
self.gestOrdCont?.tableProdotti.reloadData()
}
}
EDIT: the iCloud config
Thanks to one of my students I found the bug:
if you make a record this way everything works fine:
let record = Entity(type: "Names", graph: self.db)
but if you use this init it doesn't: let record = Entity(type: "Names")
so the solution is: make a record this way
let record = Entity(type: "Names", graph: self.db)