I am having troubles with implementing key-value storage into my app. All my iCloud associated code is in my ViewController and it is as follows:
import UIKit
import CloudKit
import NotificationCenter
var purchased = [[Bool]]() {
didSet {
instance.saveToiCloud()
print("SAVED")
}
}
var rockAmount = 0 {
didSet {
instance.saveToiCloud()
print("SAVED")
}
}
var goldAmount = 0 {
didSet {
instance.saveToiCloud()
print("SAVED")
}
}
var adBlock = false {
didSet {
instance.saveToiCloud()
print("SAVED")
}
}
var instance = GameViewController()
class GameViewController: UIViewController {
var iCloudKeyStore: NSUbiquitousKeyValueStore?
var interstitial: GADInterstitial!
var rewardAd = GADRewardBasedVideoAd()
var instRequest = receivingInst.GameScene //Placeholder
var instCatch = false
var rewardCatch = false
override func viewDidLoad() {
super.viewDidLoad()
//Set up iCloud Key-Value Storage
iCloudKeyStore = NSUbiquitousKeyValueStore()
iCloudKeyStore?.synchronize()
//Retrieve Assets from database
retrieveAssets()
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.ubiquitousKeyValueStoreDidChange), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: iCloudKeyStore)
instance = self
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
//if let scene = SKScene(fileNamed: "GameScene") {
if let scene = SKScene(fileNamed: "LaunchScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill //.fill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
//view.showsFPS = true
//view.showsPhysics = true
}
}
func retrieveAssets() {
//notFirst is to determine if it is the first ti
let notFirst = UserDefaults.standard.bool(forKey: "notFirst")
if !notFirst {
//Create iCloud References
var c = 0
while c < 4 {
var keyMake = ""
switch c {
case 0: keyMake = "purchasedCloud"
saveToiCloudArray(purchased, keyMake)
case 1: keyMake = "goldCloud"
saveToiCloudInt(0, keyMake)
case 2: keyMake = "rocksCloud"
saveToiCloudInt(0, keyMake)
case 3: keyMake = "adBlockCloud"
saveToiCloudBool(false, keyMake)
default: break
}
c += 1
}
print("SET")
}
//Retrieve iCloud Data
getValuesFromiCloud()
}
func ubiquitousKeyValueStoreDidChange(notification: NSNotification) {
let alert = UIAlertController(title: "Change detected",
message: "iCloud key-value-store change detected",
preferredStyle: UIAlertControllerStyle.alert)
let cancelAction = UIAlertAction(title: "OK",
style: .cancel, handler: nil)
alert.addAction(cancelAction)
self.present(alert, animated: true,
completion: nil)
getValuesFromiCloud()
}
func iCloudGetInt(_ key: String) -> Int {
var str = 0
if let saved = iCloudKeyStore?.object(forKey: key) {
print("B-INT\(saved)")
str = saved as! Int
print("A-INT\(saved)")
} else {
str = -10
print("IntFailed")
}
return str
}
func iCloudGetArray(_ key: String) -> [[Bool]] {
var str = [[Bool]]()
if let saved = iCloudKeyStore?.array(forKey: key){
print("B-AR\(saved)")
str = saved as! [[Bool]]
print("A-AR\(saved)")
} else {
str = [[]]
print("ArFailed")
}
return str
}
func iCloudGetBool(_ key: String) -> Bool {
var str = false
if let saved = iCloudKeyStore?.bool(forKey: key){
print("B-BOOL\(saved)")
str = saved as! Bool
print("A-BOOL\(saved)")
} else {
str = false
print("BoolFailed")
}
return str
}
//Set if stored icloud value is greater than current (on button activation)
func saveToiCloudArray(_ txt: [[Bool]],_ key: String) {
iCloudKeyStore?.set(txt, forKey: key)
//iCloudKeyStore?.synchronize()
}
func saveToiCloudInt(_ txt: Int,_ key: String) {
iCloudKeyStore?.set(txt, forKey: key)
//iCloudKeyStore?.synchronize()
}
func saveToiCloudBool(_ txt: Bool,_ key: String) {
iCloudKeyStore?.set(txt, forKey: key)
//iCloudKeyStore?.synchronize()
}
func getValuesFromiCloud() {
var c = 0
while c < 4 {
var keyMake = ""
switch c {
case 0: keyMake = "purchasedCloud"
let ar = iCloudGetArray(keyMake)
if !ar.isEmpty {
purchased = ar
UserDefaults.standard.set(purchased, forKey: "purchased")
} else {
print("Error\(keyMake)")
}
case 1: keyMake = "goldCloud"
let g = iCloudGetInt(keyMake)
if g != -10 {
goldAmount = g
UserDefaults.standard.set(goldAmount, forKey: "goldAmount")
} else {
print("Error\(keyMake)")
}
case 2: keyMake = "rocksCloud"
let r = iCloudGetInt(keyMake)
if r != -10 {
rockAmount = r
UserDefaults.standard.set(rockAmount, forKey: "rockAmount")
} else {
print("Error\(keyMake)")
}
case 3: keyMake = "adBlockCloud"
let r = iCloudGetBool(keyMake)
adBlock = r
UserDefaults.standard.set(adBlock, forKey: "adBlock")
default: break
}
c += 1
}
print(purchased, rockAmount, goldAmount)
}
func saveToiCloud() {
var c = 0
while c < 4 {
var keyMake = ""
switch c {
case 0: keyMake = "purchasedCloud"
saveToiCloudArray(purchased, keyMake)
case 1: keyMake = "goldCloud"
saveToiCloudInt(goldAmount, keyMake)
case 2: keyMake = "rocksCloud"
saveToiCloudInt(rockAmount, keyMake)
case 3: keyMake = "adBlockCloud"
saveToiCloudBool(adBlock, keyMake)
default: break
}
c += 1
}
}
[I realise that the code is not optimised and is not very clean but it's because I have been trying out many different things]
I have enabled iCloud in my Capabilities and done everything for the Provisioning Profile like this (the container exists and is valid):
I was wondering that maybe my issue is one of 2 things:
My code is meant to be in the AppDelegate and not in the ViewController.
OR
I need to write an entitlement here (which is accessible by the .entitlements file):
The highlighted red section is the one I am thinking to change (the iCloud Key-Value Store). Am I meant to write my own thing instead of $(TeamIdentifierPrefix)$(CFBundleIdentifier)? It says that is com.apple.developer.ubiquity-kvstore-identifier but what does this mean and how should I fill it out if I need to (e.g. what is the ubiquity-kvstore-identifier)?
Any sort of help and guidance would be greatly appreciated. Thank you.
The solution I found was to use the default container and not the specified container for iCloud. I'm not sure what the real difference between the 2 is but changing to this worked for me.
Also, I'm not sure if this helped solve getting iCloud key-value data to be stored but I put the code into the AppDelegate instead of the ViewController.
Related
In my project concept i need a insert 10k data when user open the application. I integrate core data for storing data but its take 1 to 5 minutes.
Here is my code ?
func inserChatMessage(_ message: String, chatId: String, onCompletion completionHandler:((_ message: ChatMessage) -> Void)?) {
var objMessage: ChatMessage? = nil
if let obj = ChatMessage.createEntity() {
objMessage = obj
}
objMessage?.messageId = ""
objMessage?.message = message
objMessage?.chatId = chatId
objMessage?.senderId = AIUser.current.userId
objMessage?.createAt = Date()
objMessage?.updateAt = Date()
let cManager = CoreDataManager.sharedManager
cManager.saveContext()
if let completionHandler = completionHandler, let objMessage = objMessage {
completionHandler(objMessage)
}
}
Coredata is not a threadsafe. And as per your requirement you need to save large amount of data on app launch. So If you will save those data using main thread, your app will get hanged. So Instead on saving large amount of data on main thread you can save those data on background thread. Coredata is supporting multi threading concept by providing parent child context concept.
I have done same in one of my project and its working fine. Here i have attached code.
func savePersonalMessagesOnBackGroundThread(arrMessages:NSArray,responseData:#escaping () -> Void)
{
print(arrMessages)
let temporaryChatContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
temporaryChatContext.parent = self.managedObjectContext
temporaryChatContext.perform({() -> Void in
for i in 0..<arrMessages.count
{
let msgDic = arrMessages[i] as! NSDictionary
_ = self.saveMessageInLocalDB(dictMessage: msgDic, managedObjectContext: temporaryChatContext, onBackground: true)
if i == arrMessages.count - 1 {
do {
try temporaryChatContext.save()
runOnMainThreadWithoutDeadlock {
DLog(message: "Thred \(Thread.isMainThread)")
if(self.managedObjectContext.hasChanges)
{
self.saveContext()
responseData()
}
}
}
catch {
print(error)
}
}
}
})
}
func saveMessageInLocalDB(dictMessage:NSDictionary, managedObjectContext:NSManagedObjectContext,onBackground:Bool) -> Chat
{
var chatObj : Chat! = Chat()
var receiveId: Int32!
var flag:Bool = false
print(dictMessage)
// let predicate = NSPredicate(format:"uniqueId == %# and senderId = %d and receiverId = %d","\(dictMessage.value(forKey:keyuniqueId)!)",Int32(dictMessage.value(forKey:keysenderId) as! Int64),Int32(dictMessage.value(forKey:keyreceiverId) as! Int64))
let predicate = NSPredicate(format:"uniqueId == %#","\(dictMessage.value(forKey:keyuniqueId)!)")
let objContext = managedObjectContext
let fetchRequest = NSFetchRequest<Chat>(entityName: ENTITY_CHAT)
let disentity: NSEntityDescription = NSEntityDescription.entity(forEntityName: ENTITY_CHAT, in: objContext)!
fetchRequest.predicate = predicate
fetchRequest.entity = disentity
do{
let results = try managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as! [Chat]
if(results.count > 0)
{
chatObj = results[0]
chatObj.messageId = Int32(dictMessage.value(forKey:keymessageId) as! Int64)
chatObj.dateOnly = dictMessage.value(forKey:keydateOnly) as! String?
}
else{
//receiveId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
//self.createNewChatObject(dictMessage: dictMessage, receiverId: receiveId, managedObjectContext: managedObjectContext)
chatObj = NSEntityDescription.insertNewObject(forEntityName:ENTITY_CHAT,into: managedObjectContext) as? Chat
if dictMessage[keymessageId] != nil {
chatObj.messageId = dictMessage.value(forKey:keymessageId) as! Int32
}
if(chatObj.message?.length != 0)
{
chatObj.message = dictMessage.value(forKey:keychatMessage) as? String
}
chatObj.messageType = Int32(dictMessage.value(forKey:keymessageType) as! Int64)
chatObj.senderId = Int32(dictMessage.value(forKey:keysenderId) as! Int64)
if(chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!))
{
let contactObj = self.getContactByContactId(contactId: Int32(dictMessage.value(forKey:keysenderId) as! Int64))
if(contactObj == nil)
{
_ = self.saveUnknownUserASContact(msgDict: dictMessage as! Dictionary<String, Any>)
}
}
chatObj.receiverId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
chatObj.uniqueId = dictMessage.value(forKey:keyuniqueId) as? String
chatObj.mediaName = dictMessage.value(forKey:keymediaName) as? String
print(NSDate())
if dictMessage[keycreatedDate] != nil {
let utcDate : NSDate = DateFormater.getUTCDateFromUTCString(givenDate: dictMessage.value(forKey:keycreatedDate) as! String)
chatObj.createdDate = utcDate
chatObj.updatedDate = utcDate
}
else
{
chatObj.createdDate = NSDate()
chatObj.updatedDate = NSDate()
}
if(chatObj.senderId == Int32((APP_DELEGATE.loggedInUser?.id)!))
{
chatObj.chatUser = chatObj.receiverId
}
else
{
chatObj.chatUser = chatObj.senderId
}
if dictMessage[keystatus] != nil {
chatObj.status = Bool((dictMessage.value(forKey:keystatus) as! Int64) as NSNumber)
}
switch Int(chatObj.messageType)
{
case MSG_TYPE.MSG_Text.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_TEXT_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_TEXT_SENDER.rawValue))
case MSG_TYPE.MSG_Image.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_IMAGE_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_IMAGE_SENDER.rawValue))
self.saveMedia(chatObj: chatObj)
default :
// chatObj.cellType = Int32(CELL_TYPE.CELL_LOAD_MORE.rawValue)
break
}
}
// deviceMake = 1;
if(!onBackground)
{
self.saveContext()
}
}
catch
{
}
return chatObj
}
Using the basic example below I can insert 10k records very quickly. The main thing that has changed here compared to your code is that I loop through and create the entities and then call save() at the very end. So you are performing one write call to the db instead of 10k. You are writing more information in that one call but it is still much quicker.
import UIKit
import CoreData
class ViewController: UIViewController {
lazy var sharedContext: NSManagedObjectContext? = {
return (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
}()
override func viewDidLoad() {
super.viewDidLoad()
if let messages = getMessages(), messages.count > 0 {
printMessages(messages: messages)
} else {
loadChatMessages()
printMessages(messages: getMessages())
}
}
private func printMessages(messages: [Message]?) {
guard let messages = messages else { return }
for message in messages {
print(message.message)
}
}
private func getMessages() -> [Message]? {
let request = NSFetchRequest<Message>(entityName: "Message")
let messages = try? self.sharedContext?.fetch(request)
return messages ?? nil
}
private func loadChatMessages() {
var counter = 1
while counter <= 10000 {
let message = Message(entity: Message.entity(), insertInto: self.sharedContext)
message.message = "This is message number \(counter)"
message.read = false
message.timestamp = Date()
counter = counter + 1
}
do {
try self.sharedContext?.save()
} catch {
print(error)
}
}
}
As mentioned in my comment above, you can improve this further by doing it in the background (see Twinkle's answer for an example of how to switch to a background thread), you can also provide a pre-filled (pre-seeded) core data database that already contains the 10k records with your app. so it doesn't need to load this on initial load.
To do this you would fill the db locally on your dev machine and then copy it to the project bundle. On initial load you can check to see if your db filename exists in the documents folder or not. If it doesn't copy it over from the bundle and then use that DB for core data.
I'm having this code:
//
// Measurement.swift
// BicycleSpeed
import Foundation
import CoreBluetooth
// CSC Measurement
// https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.csc_measurement.xml
//
// Flags : 1 byte. Bit 0: Wheel. Bit 1: Crank
// Cumulative Wheel revolutions: 4 bytes uint32
// Last wheel event time: 2 bytes. uint16 (1/1024s)
// Cumulative Crank revolutions: 2 bytes uint16
// Last cranck event time: 2 bytes. uint16 (1/1024s)
struct Measurement : CustomDebugStringConvertible {
let hasWheel:Bool
let hasCrank:Bool
let cumulativeWheel:UInt32
let lastWheelEventTime:TimeInterval
let cumulativeCrank:UInt16
let lastCrankEventTime:TimeInterval
let wheelSize:UInt32
init(data:Data, wheelSize:UInt32) {
self.wheelSize = wheelSize
// Flags
var flags:UInt8=0
(data as NSData).getBytes(&flags, range: NSRange(location: 0, length: 1))
hasWheel = ((flags & BTConstants.WheelFlagMask) > 0)
hasCrank = ((flags & BTConstants.CrankFlagMask) > 0)
var wheel:UInt32=0
var wheelTime:UInt16=0
var crank:UInt16=0
var crankTime:UInt16=0
var currentOffset = 1
var length = 0
if ( hasWheel ) {
length = MemoryLayout<UInt32>.size
(data as NSData).getBytes(&wheel, range: NSRange(location: currentOffset, length: length))
currentOffset += length
length = MemoryLayout<UInt16>.size
(data as NSData).getBytes(&wheelTime, range: NSRange(location: currentOffset, length: length))
currentOffset += length
}
if ( hasCrank ) {
length = MemoryLayout<UInt16>.size
(data as NSData).getBytes(&crank, range: NSRange(location: currentOffset, length: length))
currentOffset += length
length = MemoryLayout<UInt16>.size
(data as NSData).getBytes(&crankTime, range: NSRange(location: currentOffset, length: length))
currentOffset += length
}
cumulativeWheel = CFSwapInt32LittleToHost(wheel)
lastWheelEventTime = TimeInterval( Double(CFSwapInt16LittleToHost(wheelTime))/BTConstants.TimeScale)
cumulativeCrank = CFSwapInt16LittleToHost(crank)
lastCrankEventTime = TimeInterval( Double(CFSwapInt16LittleToHost(crankTime))/BTConstants.TimeScale)
}
func timeIntervalForCurrentSample( _ current:TimeInterval, previous:TimeInterval ) -> TimeInterval {
var timeDiff:TimeInterval = 0
if( current >= previous ) {
timeDiff = current - previous
}
else {
// passed the maximum value
timeDiff = ( TimeInterval((Double( UINT16_MAX) / BTConstants.TimeScale)) - previous) + current
}
return timeDiff
}
func valueDiffForCurrentSample<T:UnsignedInteger>( _ current:T, previous:T , max:T) -> T {
var diff:T = 0
if ( current >= previous ) {
diff = current - previous
}
else {
diff = ( max - previous ) + current
}
return diff
}
func valuesForPreviousMeasurement( _ previousSample:Measurement? ) -> ( cadenceinRPM:Double?, distanceinMeters:Double?, speedInMetersPerSecond:Double?)? {
var distance:Double?, cadence:Double?, speed:Double?
guard let previousSample = previousSample else {
return nil
}
if ( hasWheel && previousSample.hasWheel ) {
let wheelTimeDiff = timeIntervalForCurrentSample(lastWheelEventTime, previous: previousSample.lastWheelEventTime)
let valueDiff = valueDiffForCurrentSample(cumulativeWheel, previous: previousSample.cumulativeWheel, max: UInt32.max)
distance = Double( valueDiff * wheelSize) / 1000.0 // distance in meters
if distance != nil && wheelTimeDiff > 0 {
speed = (wheelTimeDiff == 0 ) ? 0 : distance! / wheelTimeDiff // m/s
}
}
if( hasCrank && previousSample.hasCrank ) {
let crankDiffTime = timeIntervalForCurrentSample(lastCrankEventTime, previous: previousSample.lastCrankEventTime)
let valueDiff = Double(valueDiffForCurrentSample(cumulativeCrank, previous: previousSample.cumulativeCrank, max: UInt16.max))
cadence = (crankDiffTime == 0) ? 0 : Double(60.0 * valueDiff / crankDiffTime) // RPM
}
print( "Cadence: \(String(describing: cadence)) RPM. Distance: \(String(describing: distance)) meters. Speed: \(String(describing: speed)) Km/h" )
return ( cadenceinRPM:cadence, distanceinMeters:distance, speedInMetersPerSecond:speed)
}
var debugDescription:String {
get {
return "Wheel Revs: \(cumulativeWheel). Last wheel event time: \(lastWheelEventTime). Crank Revs: \(cumulativeCrank). Last Crank event time: \(lastCrankEventTime)"
}
}
var myMeasurement = ((Measurement?) -> (cadenceinRPM: Double?, DistanceinMeters: Double?, speedinMetersPerSecond: Double?)).self
struct dataVariables {
static var mySpeed = myMeasurement.speedInMetersPerSecond
static var myCadence : Double?
static var miDistance : Double?
static var myLastWheelEventTime : Double? = Measurement.valuesForPreviousMeasurement(lastWheelEventTime)
static var myLastCrankEventTime : Double? = Measurement.valuesForPreviousMeasurement(lastCrankEventTime)
}
}
}
and I'm trying to assign to static variables inside the struct dataVariables the returned values cadenceinRPM,distanceinMeters,speedinMetersPerSecond
as well as lastWheelEventTime and lastCrankEventTime, so I can access them in another class, but I'm having the following errors:
on var mySpeed : Instance member'myMeasurement' cannot be used on type 'Measurement'
on var myLastWheelEventTime and var myLastCrankEventTime : Instance member 'valuesForPreviousMeasurement' cannot be used on type 'Measurement' ; did you mean to use a value of this type instead?
how can a reference to those returned values than?
Can somebody explain the error? I searched for other similar questions but I'm not getting to a solution yet
I have tried to change var myVariable to
var myMeasurement = Measurement.self
but the errors stayed the same.
and this other code
//
// CadenceSensor.swift
import Foundation
import CoreBluetooth
/*
// Bluetooth "Cycling Speed and Cadence"
https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.cycling_speed_and_cadence.xml
Service Cycling Speed and Cadence. Characteristic [2A5B] // Measurement
Service Cycling Speed and Cadence. Characteristic [2A5C] // Supported Features
Service Cycling Speed and Cadence. Characteristic [2A5D] // Sensor location
Service Cycling Speed and Cadence. Characteristic [2A55] // Control Point
*/
public struct BTConstants {
static let CadenceService = "1816"
static let CSCMeasurementUUID = "2a5b"
static let CSCFeatureUUID = "2a5c"
static let SensorLocationUUID = "2a5d"
static let ControlPointUUID = "2a55"
static let WheelFlagMask:UInt8 = 0b01
static let CrankFlagMask:UInt8 = 0b10
static let DefaultWheelSize:UInt32 = UInt32(myVariables.circonferenzaRuota!) // In millimiters. 700x30 (by default my bike's wheels) :)
static let TimeScale = 1024.0
}
protocol CadenceSensorDelegate {
func errorDiscoveringSensorInformation(_ error:NSError)
func sensorReady()
func sensorUpdatedValues( speedInMetersPerSecond speed:Double?, cadenceInRpm cadence:Double?, distanceInMeters distance:Double? )
}
class CadenceSensor: NSObject {
let peripheral:CBPeripheral
var sensorDelegate:CadenceSensorDelegate?
var measurementCharasteristic:CBCharacteristic?
var lastMeasurement:Measurement?
let wheelCircunference:UInt32
init(peripheral:CBPeripheral , wheel:UInt32=BTConstants.DefaultWheelSize) {
self.peripheral = peripheral
wheelCircunference = wheel
}
func start() {
self.peripheral.discoverServices(nil)
self.peripheral.delegate = self
}
func stop() {
if let measurementCharasteristic = measurementCharasteristic {
peripheral.setNotifyValue(false, for: measurementCharasteristic)
}
}
func handleValueData( _ data:Data ) {
let measurement = Measurement(data: data, wheelSize: wheelCircunference)
print("\(measurement)")
let values = measurement.valuesForPreviousMeasurement(lastMeasurement)
lastMeasurement = measurement
sensorDelegate?.sensorUpdatedValues(speedInMetersPerSecond: values?.speedInMetersPerSecond, cadenceInRpm: values?.cadenceinRPM, distanceInMeters: values?.distanceinMeters)
}
}
extension CadenceSensor : CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
guard error == nil else {
sensorDelegate?.errorDiscoveringSensorInformation(NSError(domain: CBErrorDomain, code: 0, userInfo: [NSLocalizedDescriptionKey:NSLocalizedString("Error receiving measurements updates", comment:"")]))
return
}
print("notification status changed for [\(characteristic.uuid)]...")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("Updated [\(characteristic.uuid)]...")
guard error == nil , let data = characteristic.value else {
return
}
handleValueData(data)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard error == nil else {
sensorDelegate?.errorDiscoveringSensorInformation(error! as NSError)
return
}
// Find the cadence service
guard let cadenceService = peripheral.services?.filter({ (service) -> Bool in
return service.uuid == CBUUID(string: BTConstants.CadenceService)
}).first else {
sensorDelegate?.errorDiscoveringSensorInformation(NSError(domain: CBErrorDomain, code: NSNotFound, userInfo: [NSLocalizedDescriptionKey:NSLocalizedString("Cadence service not found for this peripheral", comment:"")]))
return
}
// Discover the cadence service characteristics
peripheral.discoverCharacteristics(nil, for:cadenceService )
print("Cadence service discovered")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else {
sensorDelegate?.errorDiscoveringSensorInformation(NSError(domain: CBErrorDomain, code: NSNotFound, userInfo: [NSLocalizedDescriptionKey:NSLocalizedString("No characteristics found for the cadence service", comment:"")]))
return
}
print("Received characteristics");
// Enable notifications for the measurement characteristic
for characteristic in characteristics {
print("Service \(service.uuid). Characteristic [\(characteristic.uuid)]")
if characteristic.uuid == CBUUID(string: BTConstants.CSCMeasurementUUID) {
print("Found measurement characteristic. Subscribing...")
peripheral.setNotifyValue(true, for: characteristic)
measurementCharasteristic = characteristic
}
}
sensorDelegate?.sensorReady()
}
}
and this other code
//
// MainViewController.swift
// BicycleSpeed
import UIKit
import CoreBluetooth
class MainViewController: UIViewController {
struct Constants {
static let ScanSegue = "ScanSegue"
static let SensorUserDefaultsKey = "lastsensorused"
}
var bluetoothManager:BluetoothManager!
var sensor:CadenceSensor?
weak var scanViewController:ScanViewController?
var infoViewController:InfoTableViewController?
var accumulatedDistance:Double?
lazy var distanceFormatter:LengthFormatter = {
let formatter = LengthFormatter()
formatter.numberFormatter.maximumFractionDigits = 1
return formatter
}()
//#IBOutlet var labelBTStatus:UILabel!
#IBOutlet var scanItem:UIBarButtonItem!
#IBOutlet weak var idLabel: UILabel!
override func viewDidLoad() {
bluetoothManager = BluetoothManager()
bluetoothManager.bluetoothDelegate = self
scanItem.isEnabled = false
}
deinit {
disconnectSensor()
}
#IBAction func unwindSegue( _ segue:UIStoryboardSegue ) {
bluetoothManager.stopScan()
guard let sensor = (segue as? ScanUnwindSegue)?.sensor else {
return
}
print("Need to connect to sensor \(sensor.peripheral.identifier)")
connectToSensor(sensor)
}
func disconnectSensor( ) {
if sensor != nil {
bluetoothManager.disconnectSensor(sensor!)
sensor = nil
}
accumulatedDistance = nil
}
func connectToSensor(_ sensor:CadenceSensor) {
self.sensor = sensor
bluetoothManager.connectToSensor(sensor)
// Save the sensor ID
UserDefaults.standard.set(sensor.peripheral.identifier.uuidString, forKey: Constants.SensorUserDefaultsKey)
UserDefaults.standard.synchronize()
}
// TODO: REconnect. Try this every X seconds
func checkPreviousSensor() {
guard let sensorID = UserDefaults.standard.object(forKey: Constants.SensorUserDefaultsKey) as? String else {
return
}
guard let sensor = bluetoothManager.retrieveSensorWithIdentifier(sensorID) else {
return
}
self.sensor = sensor
connectToSensor(sensor)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let infoVC = segue.destination as? InfoTableViewController {
infoViewController = infoVC
}
if segue.identifier == Constants.ScanSegue {
// Scan segue
bluetoothManager.startScan()
scanViewController = (segue.destination as? UINavigationController)?.viewControllers.first as? ScanViewController
}
}
}
extension MainViewController : CadenceSensorDelegate {
func errorDiscoveringSensorInformation(_ error: NSError) {
print("An error ocurred disconvering the sensor services/characteristics: \(error)")
}
func sensorReady() {
print("Sensor ready to go...")
accumulatedDistance = 0.0
}
func updateSensorInfo() {
let name = sensor?.peripheral.name ?? ""
let uuid = sensor?.peripheral.identifier.uuidString ?? ""
OperationQueue.main.addOperation { () -> Void in
self.infoViewController?.showDeviceName(name , uuid:uuid )
}
}
func sensorUpdatedValues( speedInMetersPerSecond speed:Double?, cadenceInRpm cadence:Double?, distanceInMeters distance:Double? ) {
accumulatedDistance? += distance ?? 0
let distanceText = (accumulatedDistance != nil && accumulatedDistance! >= 1.0) ? distanceFormatter.string(fromMeters: accumulatedDistance!) : "N/A"
let speedText = (speed != nil) ? distanceFormatter.string(fromValue: speed!*3.6, unit: .kilometer) + NSLocalizedString("/h", comment:"(km) Per hour") : "N/A"
let cadenceText = (cadence != nil) ? String(format: "%.2f %#", cadence!, NSLocalizedString("RPM", comment:"Revs per minute") ) : "N/A"
OperationQueue.main.addOperation { () -> Void in
self.infoViewController?.showMeasurementWithSpeed(speedText , cadence: cadenceText, distance: distanceText )
}
}
}
extension MainViewController : BluetoothManagerDelegate {
func stateChanged(_ state: CBCentralManagerState) {
print("State Changed: \(state)")
var enabled = false
var title = ""
switch state {
case .poweredOn:
title = "Bluetooth ON"
enabled = true
// When the bluetooth changes to ON, try to reconnect to the previous sensor
checkPreviousSensor()
case .resetting:
title = "Reseeting"
case .poweredOff:
title = "Bluetooth Off"
case .unauthorized:
title = "Bluetooth not authorized"
case .unknown:
title = "Unknown"
case .unsupported:
title = "Bluetooth not supported"
}
infoViewController?.showBluetoothStatusText( title )
scanItem.isEnabled = enabled
}
func sensorConnection( _ sensor:CadenceSensor, error:NSError?) {
print("")
guard error == nil else {
self.sensor = nil
print("Error connecting to sensor: \(sensor.peripheral.identifier)")
updateSensorInfo()
accumulatedDistance = nil
return
}
self.sensor = sensor
self.sensor?.sensorDelegate = self
print("Sensor connected. \(String(describing: sensor.peripheral.name)). [\(sensor.peripheral.identifier)]")
updateSensorInfo()
sensor.start()
}
func sensorDisconnected( _ sensor:CadenceSensor, error:NSError?) {
print("Sensor disconnected")
self.sensor = nil
}
func sensorDiscovered( _ sensor:CadenceSensor ) {
scanViewController?.addSensor(sensor)
}
}
in MainViewController.swift, there is this func sensorUpdatedValues that converts the three values that I want, to strings and initialize the the func showMeasurementWithSpeed ins the InfoTableViewController.swift .
Could I just return the three values inside function sensorUpdateValues instead to be able to store them into new variables?
The better way to solve this is by passing on the sensor object to the InfoTableViewController in the prepare(forSegue:) and then inside InfoTableViewControlleryou can call sensor.lastMeasurement.speedInMetersPerSecond or any other var that is inside there. Since the class is passed on by reference it will retain the data even when you transition to a new ViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let infoVC = segue.destination as? InfoTableViewController {
infoVC.sensor = self.sensor
}
if segue.identifier == Constants.ScanSegue {
// Scan segue
bluetoothManager.startScan()
scanViewController = (segue.destination as? UINavigationController)?.viewControllers.first as? ScanViewController
}
}
Then of course you can do whatever you want with this data in the new VC( assign the values to labels or whatnot)
I believe it's because you have that func declared as an instance level function rather than a class/struct level function. You should be able to simply add the "static" keyword to make it accessible for the way you are using it in your sample code. i.e. "static func valuesForPreviousMeasurement ..."
** UPDATE - Added a simple example to show the difference between class and instance functions.
// This is an instance function being used. It's called such because
// you need an actual object instance in order to call the func.
var myCar: Car = Car()
myCar.startEngine()
// This is a class level function being used. It's called such because
// you don't actually need an object instance: It's simply part of the class.
Car.PriceForModel("HondaCivic")
Finally Solved .. it was easier than thought.. as I was understanding the three values I'm interested in were passed some how to the infoTableViewController I wanted to get rid of. And they were passed, already converted to String with the function sensorUpdatedValues inside MainView controller
func sensorUpdatedValues( speedInMetersPerSecond speed:Double?, cadenceInRpm cadence:Double?, distanceInMeters distance:Double? ) {
accumulatedDistance? += distance ?? 0
let distanceText = (accumulatedDistance != nil && accumulatedDistance! >= 1.0) ? distanceFormatter.string(fromMeters: accumulatedDistance!) : "N/A"
let speedText = (speed != nil) ? distanceFormatter.string(fromValue: speed!*3.6, unit: .kilometer) + NSLocalizedString("/h", comment:"(km) Per hour") : "N/A"
let cadenceText = (cadence != nil) ? String(format: "%.2f %#", cadence!, NSLocalizedString("RPM", comment:"Revs per minute") ) : "N/A"
OperationQueue.main.addOperation { () -> Void in
self.infoViewController?.showMeasurementWithSpeed(speedText , cadence: cadenceText, distance: distanceText )
}
}
so I traced down the function inside my InfospeedoViewController ( as xCode was asking for it because it was present in InfoTableViewController and rerouting to InfoSpeedoViewController made it necessary) and inside that function's body I made the connection of the values to the labels.
func showMeasurementWithSpeed( _ speed:String, cadence:String, distance:String ) {
speedDisplayLabel.text = speed
cadenceDisplayLabel.text = cadence
distanceDisplayLabel.text = distance
// showDetailText(speed, atSection: Constants.MeasurementsSection, row:Constants.SpeedRow)
// showDetailText(cadence, atSection: Constants.MeasurementsSection, row:Constants.CadenceRow)
// showDetailText(distance, atSection: Constants.MeasurementsSection, row:Constants.DistanceRow)
}
the commented out parts were the old InfoTableViewController indication to fill the cells..
Many thanks for your help. I learned a few things and consolidated others. I guess we went the hard way trying to catch this values in the wrong place, but I guess that just because I had all the project's file printed that I could trace the data flow easier. Not fully understanding the code because of my low knowledge of swift made it a bit more difficoult, but I had this feeling the this solution was logic as often the simple way is the best way, an often one just can't see it.
thanks again
I have two more values I want to get. A new adventure begins ..should I post another question or continue this one?
I'm using Google Places API for iOS and can successfully retrieve nearby places and present the address as a string. What I'm trying to do is extract address components such as city to store in a database.
The documentation indicates a GMSPlace has an addressComponents property (an array), but I can't seem to figure how to use this property.
The code below provides sets the entire address to the text of a label, but I can't get beyond that:
Edit ---- added code that shows how I'm trying to access Address Components
venueLabel.isHidden = false
venueLabel.text = selectedPlace?.name
addressLabel.isHidden = false
addressLabel.text = selectedPlace?.formattedAddress
let addressComponents = selectedPlace?.addressComponents
for component in addressComponents! {
let city = component["city"] //Error Type GMSPaceAddressComponent has no subscript members
print(city)
}
A safe Swift 4.2 solution:
let name = place.addressComponents?.first(where: { $0.type == "city" })?.name
selectedPlace.addressComponents is a array of GMSAddressComponent which have 2 properties type and name.
In you case it will be like
for component in addressComponents! {
if component.type == "city" {
print(component.name)
}
}
GMSAddressComponent is a class not a dictionary or array that's you are getting this error.
Additional component types can be referred from the link.
Swift 4
var keys = [String]()
place.addressComponents?.forEach{keys.append($0.type)}
var values = [String]()
place.addressComponents?.forEach({ (component) in
keys.forEach{ component.type == $0 ? values.append(component.name): nil}
})
print(values)
For the latest Google Places API (July 2019) this is what could help you.
Actually, Google now putting a couple of types for each element. So the "type" is now deprecated and "types" is new filed.
You can do something like this:
for addressComponent in (self.mySelectedPlace?.addressComponents)! {
for type in (addressComponent.types){
switch(type){
case "country":
var country = addressComponent.name
case "postal_code":
var postCode = addressComponent.name
default:
break
}
}
}
It's working in Swift 5 and iOS 13.3
1. Create a function for showing GMSAutocompleteViewController
func googlePlacesSearchVC() {
let acController = GMSAutocompleteViewController()
acController.delegate = self
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.formattedAddress.rawValue) |
UInt(GMSPlaceField.addressComponents.rawValue))!
acController.placeFields = fields
// Specify a filter.
let filter = GMSAutocompleteFilter()
filter.country = "IN"
acController.autocompleteFilter = filter
acController.secondaryTextColor = .darkGray
acController.primaryTextColor = .lightGray
acController.primaryTextHighlightColor = .black
acController.tableCellBackgroundColor = .whiteThree
acController.tableCellSeparatorColor = .lightGray
// Display the autocomplete view controller.
present(acController, animated: true) {
let views = acController.view.subviews
let subviewsOfSubview = views.first!.subviews
let subOfNavTransitionView = subviewsOfSubview[1].subviews
let subOfContentView = subOfNavTransitionView[1].subviews
let searchBar = subOfContentView[0] as! UISearchBar
searchBar.searchTextField.placeholder = "places_picker_hint_add_address".localized
searchBar.searchBarStyle = .minimal
searchBar.searchTextField.font = UIFont.screenTitle16Pt
searchBar.searchTextField.backgroundColor = UIColor.white
searchBar.searchTextField.leftView?.tintColor = .darkGray
searchBar.delegate?.searchBar?(searchBar, textDidChange: "")
}
}
2. Call that function where ever you need, for ex:-
override func viewDidLoad() {
super.viewDidLoad()
googlePlacesSearchVC()
}
3. Call GMSAutoCompleteViewControllerDelegates Methods.
Here will get all details like Street, City, etc. from GMSPlace Address Components
extension ViewController {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
// Show HouseAndFlat
if place.name?.description != nil {
yourTxtField.text = place.name?.description ?? ""
}
// Show latitude
if place.coordinate.latitude.description.count != 0 {
var latitude = place.coordinate.latitude
}
// Show longitude
if place.coordinate.longitude.description.count != 0 {
selectedLongitude = place.coordinate.longitude
}
// Show AddressComponents
if place.addressComponents != nil {
for addressComponent in (place.addressComponents)! {
for type in (addressComponent.types){
switch(type){
case "sublocality_level_2":
yourTxtField.text = addressComponent.name
case "sublocality_level_1":
yourTxtField.text = addressComponent.name
case "administrative_area_level_2":
yourTxtField.text = addressComponent.name
case "administrative_area_level_1":
yourTxtField.text = addressComponent.name
case "country":
yourTxtField.text = addressComponent.name
case "postal_code":
yourTxtField.text = addressComponent.name
default:
break
}
}
}
}
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
}
I have found an answer from this link
I am using this extension:
extension Array where Element == GMSAddressComponent {
func valueFor(placeType: String, shortName: Bool = false) -> String? {
// bug in the google or apple framework. This cast must stay.
// Withou it crashing.
guard let array = self as? NSArray else { return nil }
let result = array
.compactMap { $0 as? GMSAddressComponent }
.first(where: {
$0.types
.first(where: { $0 == placeType }) == placeType
})
return shortName ? result?.shortName : result?.name
}
}
Usage:
let place: GMSPlace
// ...
place.addressComponents?.valueFor(placeType: kGMSPlaceTypeRoute, shortName: true)
I will extend #Ramis answer, in any case you want to check for multiple types if one of them exist, e.x
addressComponents?.valueFor(placeTypes: kGMSPlaceTypePostalTown, kGMSPlaceTypeLocality)
implementation
extension Array where Element == GMSAddressComponent {
func valueFor(placeTypes: String..., shortName: Bool = false) -> String? {
let array = self as NSArray
let result = array
.compactMap { $0 as? GMSAddressComponent }
.first(where: {
placeTypes.contains($0.types.first(where: { placeTypes.contains($0) }) ?? "")
})
return shortName ? result?.shortName : result?.name
}
}
I could not find this anywhere for Objective-C, for those who follow behind me:
for (int i = 0; i < [place.addressComponents count]; i++)
{
NSLog(#"name %# = type %#", place.addressComponents[i].name, place.addressComponents[i].type);
}
for ( GMSAddressComponent * component in place.addressComponents) {
if ( [component.type isEqual: #"locality"] )
{
NSLog(#"Current city %# ", component.name);
}
}
var keys = [String]()
place.addressComponents?.forEach{keys.append($0.type)}
var values = [String]()
place.addressComponents?.forEach({ (component) in
keys.forEach{ component.type == $0 ? values.append(component.name): nil}
print("All component type \(component.type)")
if component.type == "locality"{
print("This is city name \(component.name)")
}
})
print(values)
I found this, I hope I help you
let arrays : NSArray = place.addressComponents! as NSArray
for i in 0..<arrays.count {
let dics : GMSAddressComponent = arrays[i] as! GMSAddressComponent
let str : NSString = dics.type as NSString
if (str == "country") {
print("Country: \(dics.name)")
self.pais = dics.name
}
else if (str == "administrative_area_level_2") {
print("City: \(dics.name)")
self.ciudad = dics.name
}
print("City: (String(describing: place.addressComponents?.first(where: {$0.type == "locality"})?.name))")
print("Postal Code: (String(describing: place.addressComponents?.first(where: {$0.type == "postal_code"})?.name))")
print("State: (String(describing: place.addressComponents?.first(where: {$0.type == "administrative_area_level_1"})?.name))"
Use this description for work like this.
https://developers.google.com/maps/documentation/places/ios-sdk/place-data-fields
I have an issue with my code that I hope maybe som of you can help me out with.
I have a "kindoff" dictionary app where the user can search for abbreviations.
The datamodel is build using an SQLite database. In my first version the data.db file was only stored locally, but since I have to make a lot of data updates I don't wanna update en whole app every time. So I have made a version that gets the data.db file from a server instead.
My problem is that the user have to quit og restart the app to get the updated version. Somehow, even if the new file is copied to the directory the sqldb class does'nt read the new file. Any idea how to make som code the gets the dbclass to read the new file, while the app is running?
Here is the code the copies the new file to the directory
func executeDownloadTask(){
if Reachability.isConnectedToNetwork() == true{
print("just Checking")
let backgroundSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("backgroundSession")
backgroundSession = NSURLSession(configuration: backgroundSessionConfiguration, delegate: self,delegateQueue: NSOperationQueue.mainQueue())
let the_path:String = UpdateDbFile.findPathInDocDir("data.db");// get the path for the data.db file
UpdateDbFile.deleteFilewithPath(the_path) // delete the existing file
startDownload() // download the updated dbfile
}
}
Heres the SQLiteDb class that I use.
//
// SQLiteDB.swift
// TasksGalore
//
// Created by Fahim Farook on 12/6/14.
// Copyright (c) 2014 RookSoft Pte. Ltd. All rights reserved.
//
import Foundation
#if os(iOS)
import UIKit
#else
import AppKit
#endif
let SQLITE_DATE = SQLITE_NULL + 1
internal let SQLITE_STATIC = unsafeBitCast(0, sqlite3_destructor_type.self)
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
// MARK:- SQLColumn Class - Column Definition
class SQLColumn {
var value:AnyObject? = nil
var type:CInt = -1
init(value:AnyObject, type:CInt) {
// println("SQLiteDB - Initialize column with type: \(type), value: \(value)")
self.value = value
self.type = type
}
// New conversion functions
func asString()->String {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
return "\(value!)"
case SQLITE_TEXT:
return value as! String
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
return str as String
} else {
return ""
}
case SQLITE_NULL:
return ""
case SQLITE_DATE:
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
return fmt.stringFromDate(value as! NSDate)
default:
return ""
}
}
func asInt()->Int {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
return value as! Int
case SQLITE_TEXT:
let str = value as! NSString
return str.integerValue
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
return str.integerValue
} else {
return 0
}
case SQLITE_NULL:
return 0
case SQLITE_DATE:
return Int((value as! NSDate).timeIntervalSince1970)
default:
return 0
}
}
func asDouble()->Double {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
return value as! Double
case SQLITE_TEXT:
let str = value as! NSString
return str.doubleValue
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
return str.doubleValue
} else {
return 0.0
}
case SQLITE_NULL:
return 0.0
case SQLITE_DATE:
return (value as! NSDate).timeIntervalSince1970
default:
return 0.0
}
}
func asData()->NSData? {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
let str = "\(value)" as NSString
return str.dataUsingEncoding(NSUTF8StringEncoding)
case SQLITE_TEXT:
let str = value as! NSString
return str.dataUsingEncoding(NSUTF8StringEncoding)
case SQLITE_BLOB:
return value as? NSData
case SQLITE_NULL:
return nil
case SQLITE_DATE:
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
let str = fmt.stringFromDate(value as! NSDate)
return str.dataUsingEncoding(NSUTF8StringEncoding)
default:
return nil
}
}
func asDate()->NSDate? {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
let tm = value as! Double
return NSDate(timeIntervalSince1970:tm)
case SQLITE_TEXT:
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
return fmt.dateFromString(value as! String)
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
return fmt.dateFromString(str as String)
} else {
return nil
}
case SQLITE_NULL:
return nil
case SQLITE_DATE:
return value as? NSDate
default:
return nil
}
}
func description()->String {
return "Type: \(type), Value: \(value)"
}
}
// MARK:- SQLRow Class - Row Definition
class SQLRow {
var data = Dictionary<String, SQLColumn>()
subscript(key: String) -> SQLColumn? {
get {
return data[key]
}
set(newVal) {
data[key] = newVal
}
}
func description()->String {
return data.description
}
}
// MARK:- SQLiteDB Class - Does all the work
class SQLiteDB{
let DB_NAME = "data.db"
let QUEUE_LABLE = "SQLiteDB"
private var db:COpaquePointer = nil
private var queue:dispatch_queue_t
private var fmt = NSDateFormatter()
private var GROUP = ""
var newFileName = " "
var recentFileName = "Bøsse "
struct Static {
static var instance:SQLiteDB? = nil
static var token:dispatch_once_t = 0
}
class func sharedInstance() -> SQLiteDB! {
dispatch_once(&Static.token) {
Static.instance = self.init(gid:"")
}
return Static.instance!
}
class func sharedInstance(gid:String) -> SQLiteDB! {
dispatch_once(&Static.token) {
Static.instance = self.init(gid:gid)
}
return Static.instance!
}
required init(gid:String) {
assert(Static.instance == nil, "Singleton already initialized!")
GROUP = gid
// Set queue
queue = dispatch_queue_create(QUEUE_LABLE, nil)
// Set up for file operations
let fm = NSFileManager.defaultManager()
let dbName:String = String.fromCString(DB_NAME)!
var docDir = ""
// Is this for an app group?
if GROUP.isEmpty {
// Get path to DB in Documents directory
docDir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)[0] as String
} else {
// Get path to shared group folder
if let url = fm.containerURLForSecurityApplicationGroupIdentifier(GROUP) {
docDir = url.path!
} else {
assert(false, "Error getting container URL for group: \(GROUP)")
}
}
let path = docDir.NS.stringByAppendingPathComponent(dbName)
print(path)
// Check if copy of DB is there in Documents directory
if !(fm.fileExistsAtPath(path)) {
// The database does not exist, so copy to Documents directory
if let from = NSBundle.mainBundle().resourcePath?.NS.stringByAppendingPathComponent(dbName) {
var error:NSError?
do {
try fm.copyItemAtPath(from, toPath: path)
print("restart the app to get updated data")
} catch let error1 as NSError {
error = error1
print("SQLiteDB - failed to copy writable version of DB!")
print("Error - \(error!.localizedDescription)")
return
}
}
}
// Open the DB
let cpath = path.cStringUsingEncoding(NSUTF8StringEncoding)
let error = sqlite3_open(cpath!, &db)
if error != SQLITE_OK {
// Open failed, close DB and fail
print("SQLiteDB - failed to open DB!")
sqlite3_close(db)
}
fmt.dateFormat = "YYYY-MM-dd HH:mm:ss"
}
deinit {
closeDatabase()
}
private func closeDatabase() {
if db != nil {
// Get launch count value
let ud = NSUserDefaults.standardUserDefaults()
var launchCount = ud.integerForKey("LaunchCount")
launchCount--
print("SQLiteDB - Launch count \(launchCount)")
var clean = false
if launchCount < 0 {
clean = true
launchCount = 500
}
ud.setInteger(launchCount, forKey: "LaunchCount")
ud.synchronize()
// Do we clean DB?
if !clean {
sqlite3_close(db)
return
}
// Clean DB
print("SQLiteDB - Optimize DB")
let sql = "VACUUM; ANALYZE"
if execute(sql) != SQLITE_OK {
print("SQLiteDB - Error cleaning DB")
}
sqlite3_close(db)
}
}
// Execute SQL with parameters and return result code
func execute(sql:String, parameters:[AnyObject]?=nil)->CInt {
var result:CInt = 0
dispatch_sync(queue) {
let stmt = self.prepare(sql, params:parameters)
if stmt != nil {
result = self.execute(stmt, sql:sql)
}
}
return result
}
// Run SQL query with parameters
func query(sql:String, parameters:[AnyObject]?=nil)->[SQLRow] {
var rows = [SQLRow]()
dispatch_sync(queue) {
let stmt = self.prepare(sql, params:parameters)
if stmt != nil {
rows = self.query(stmt, sql:sql)
}
}
return rows
}
// Show alert with either supplied message or last error
func alert(msg:String) {
dispatch_async(dispatch_get_main_queue()) {
#if os(iOS)
let alert = UIAlertView(title: "SQLiteDB", message:msg, delegate: nil, cancelButtonTitle: "OK")
alert.show()
#else
let alert = NSAlert()
alert.addButtonWithTitle("OK")
alert.messageText = "SQLiteDB"
alert.informativeText = msg
alert.alertStyle = NSAlertStyle.WarningAlertStyle
alert.runModal()
#endif
}
}
// Private method which prepares the SQL
private func prepare(sql:String, params:[AnyObject]?)->COpaquePointer {
var stmt:COpaquePointer = nil
let cSql = sql.cStringUsingEncoding(NSUTF8StringEncoding)
// Prepare
let result = sqlite3_prepare_v2(self.db, cSql!, -1, &stmt, nil)
if result != SQLITE_OK {
sqlite3_finalize(stmt)
if let error = String.fromCString(sqlite3_errmsg(self.db)) {
let msg = "SQLiteDB - failed to prepare SQL: \(sql), Error: \(error)"
print(msg)
}
return nil
}
// Bind parameters, if any
if params != nil {
// Validate parameters
let cntParams = sqlite3_bind_parameter_count(stmt)
let cnt = CInt(params!.count)
if cntParams != cnt {
let msg = "SQLiteDB - failed to bind parameters, counts did not match. SQL: \(sql), Parameters: \(params)"
print(msg)
return nil
}
var flag:CInt = 0
// Text & BLOB values passed to a C-API do not work correctly if they are not marked as transient.
for ndx in 1...cnt {
// println("Binding: \(params![ndx-1]) at Index: \(ndx)")
// Check for data types
if let txt = params![ndx-1] as? String {
flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
} else if let data = params![ndx-1] as? NSData {
flag = sqlite3_bind_blob(stmt, CInt(ndx), data.bytes, CInt(data.length), SQLITE_TRANSIENT)
} else if let date = params![ndx-1] as? NSDate {
let txt = fmt.stringFromDate(date)
flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
} else if let val = params![ndx-1] as? Double {
flag = sqlite3_bind_double(stmt, CInt(ndx), CDouble(val))
} else if let val = params![ndx-1] as? Int {
flag = sqlite3_bind_int(stmt, CInt(ndx), CInt(val))
} else {
flag = sqlite3_bind_null(stmt, CInt(ndx))
}
// Check for errors
if flag != SQLITE_OK {
sqlite3_finalize(stmt)
if let error = String.fromCString(sqlite3_errmsg(self.db)) {
let msg = "SQLiteDB - failed to bind for SQL: \(sql), Parameters: \(params), Index: \(ndx) Error: \(error)"
print(msg)
}
return nil
}
}
}
return stmt
}
// Private method which handles the actual execution of an SQL statement
private func execute(stmt:COpaquePointer, sql:String)->CInt {
// Step
var result = sqlite3_step(stmt)
if result != SQLITE_OK && result != SQLITE_DONE {
sqlite3_finalize(stmt)
if let err = String.fromCString(sqlite3_errmsg(self.db)) {
let msg = "SQLiteDB - failed to execute SQL: \(sql), Error: \(err)"
print(msg)
}
return 0
}
// Is this an insert
let upp = sql.uppercaseString
if upp.hasPrefix("INSERT ") {
// Known limitations: http://www.sqlite.org/c3ref/last_insert_rowid.html
let rid = sqlite3_last_insert_rowid(self.db)
result = CInt(rid)
} else if upp.hasPrefix("DELETE") || upp.hasPrefix("UPDATE") {
var cnt = sqlite3_changes(self.db)
if cnt == 0 {
cnt++
}
result = CInt(cnt)
} else {
result = 1
}
// Finalize
sqlite3_finalize(stmt)
return result
}
// Private method which handles the actual execution of an SQL query
private func query(stmt:COpaquePointer, sql:String)->[SQLRow] {
var rows = [SQLRow]()
var fetchColumnInfo = true
var columnCount:CInt = 0
var columnNames = [String]()
var columnTypes = [CInt]()
var result = sqlite3_step(stmt)
while result == SQLITE_ROW {
// Should we get column info?
if fetchColumnInfo {
columnCount = sqlite3_column_count(stmt)
for index in 0..<columnCount {
// Get column name
let name = sqlite3_column_name(stmt, index)
columnNames.append(String.fromCString(name)!)
// Get column type
columnTypes.append(self.getColumnType(index, stmt:stmt))
}
fetchColumnInfo = false
}
// Get row data for each column
let row = SQLRow()
for index in 0..<columnCount {
let key = columnNames[Int(index)]
let type = columnTypes[Int(index)]
if let val:AnyObject = self.getColumnValue(index, type:type, stmt:stmt) {
// println("Column type:\(type) with value:\(val)")
let col = SQLColumn(value: val, type: type)
row[key] = col
}
}
rows.append(row)
// Next row
result = sqlite3_step(stmt)
}
sqlite3_finalize(stmt)
return rows
}
// Get column type
private func getColumnType(index:CInt, stmt:COpaquePointer)->CInt {
var type:CInt = 0
// Column types - http://www.sqlite.org/datatype3.html (section 2.2 table column 1)
let blobTypes = ["BINARY", "BLOB", "VARBINARY"]
let charTypes = ["CHAR", "CHARACTER", "CLOB", "NATIONAL VARYING CHARACTER", "NATIVE CHARACTER", "NCHAR", "NVARCHAR", "TEXT", "VARCHAR", "VARIANT", "VARYING CHARACTER"]
let dateTypes = ["DATE", "DATETIME", "TIME", "TIMESTAMP"]
let intTypes = ["BIGINT", "BIT", "BOOL", "BOOLEAN", "INT", "INT2", "INT8", "INTEGER", "MEDIUMINT", "SMALLINT", "TINYINT"]
let nullTypes = ["NULL"]
let realTypes = ["DECIMAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT", "NUMERIC", "REAL"]
// Determine type of column - http://www.sqlite.org/c3ref/c_blob.html
let buf = sqlite3_column_decltype(stmt, index)
// println("SQLiteDB - Got column type: \(buf)")
if buf != nil {
var tmp = String.fromCString(buf)!.uppercaseString
// Remove brackets
let pos = tmp.positionOf("(")
if pos > 0 {
tmp = tmp.subStringTo(pos)
}
// Remove unsigned?
// Remove spaces
// Is the data type in any of the pre-set values?
// println("SQLiteDB - Cleaned up column type: \(tmp)")
if intTypes.contains(tmp) {
return SQLITE_INTEGER
}
if realTypes.contains(tmp) {
return SQLITE_FLOAT
}
if charTypes.contains(tmp) {
return SQLITE_TEXT
}
if blobTypes.contains(tmp) {
return SQLITE_BLOB
}
if nullTypes.contains(tmp) {
return SQLITE_NULL
}
if dateTypes.contains(tmp) {
return SQLITE_DATE
}
return SQLITE_TEXT
} else {
// For expressions and sub-queries
type = sqlite3_column_type(stmt, index)
}
return type
}
// Get column value
private func getColumnValue(index:CInt, type:CInt, stmt:COpaquePointer)->AnyObject? {
// Integer
if type == SQLITE_INTEGER {
let val = sqlite3_column_int(stmt, index)
return Int(val)
}
// Float
if type == SQLITE_FLOAT {
let val = sqlite3_column_double(stmt, index)
return Double(val)
}
// Text - handled by default handler at end
// Blob
if type == SQLITE_BLOB {
let data = sqlite3_column_blob(stmt, index)
let size = sqlite3_column_bytes(stmt, index)
let val = NSData(bytes:data, length: Int(size))
return val
}
// Null
if type == SQLITE_NULL {
return nil
}
// Date
if type == SQLITE_DATE {
// Is this a text date
let txt = UnsafePointer<Int8>(sqlite3_column_text(stmt, index))
if txt != nil {
if let buf = NSString(CString:txt, encoding:NSUTF8StringEncoding) {
let set = NSCharacterSet(charactersInString: "-:")
let range = buf.rangeOfCharacterFromSet(set)
if range.location != NSNotFound {
// Convert to time
var time:tm = tm(tm_sec: 0, tm_min: 0, tm_hour: 0, tm_mday: 0, tm_mon: 0, tm_year: 0, tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_gmtoff: 0, tm_zone:nil)
strptime(txt, "%Y-%m-%d %H:%M:%S", &time)
time.tm_isdst = -1
let diff = NSTimeZone.localTimeZone().secondsFromGMT
let t = mktime(&time) + diff
let ti = NSTimeInterval(t)
let val = NSDate(timeIntervalSince1970:ti)
return val
}
}
}
// If not a text date, then it's a time interval
let val = sqlite3_column_double(stmt, index)
let dt = NSDate(timeIntervalSince1970: val)
return dt
}
// If nothing works, return a string representation
let buf = UnsafePointer<Int8>(sqlite3_column_text(stmt, index))
let val = String.fromCString(buf)
// println("SQLiteDB - Got value: \(val)")
return val
}
}
public extension String {
var NS: NSString { return (self as NSString) }
}
Hope you can give me some advice. Thanks
I am trying to duplicate Need to write calculator in Objective-C in Swift but my code is not working.
import Foundation
var equation:NSString = "5*(2.56-1.79)-4.1"
var result = NSExpression(format: equation, argumentArray: nil)
println(result)
As already said in a comment, you have to call expressionValueWithObject()
on the expression:
let expr = NSExpression(format: equation)
if let result = expr.expressionValueWithObject(nil, context: nil) as? NSNumber {
let x = result.doubleValue
println(x)
} else {
println("failed")
}
Update for Swift 3:
let expr = NSExpression(format: equation)
if let result = expr.expressionValue(with: nil, context: nil) as? Double {
print(result) // -0.25
} else {
print("failed")
}
Details
Xcode 9.4.1, Swift 4.1
Xcode 10.2.1 (10E1001), Swift 5
Solution
import Foundation
extension String {
private func allNumsToDouble() -> String {
let symbolsCharSet = ".,"
let fullCharSet = "0123456789" + symbolsCharSet
var i = 0
var result = ""
var chars = Array(self)
while i < chars.count {
if fullCharSet.contains(chars[i]) {
var numString = String(chars[i])
i += 1
loop: while i < chars.count {
if fullCharSet.contains(chars[i]) {
numString += String(chars[i])
i += 1
} else {
break loop
}
}
if let num = Double(numString) {
result += "\(num)"
} else {
result += numString
}
} else {
result += String(chars[i])
i += 1
}
}
return result
}
func calculate() -> Double? {
let transformedString = allNumsToDouble()
let expr = NSExpression(format: transformedString)
return expr.expressionValue(with: nil, context: nil) as? Double
}
}
Usage
"3*(3-1)-5".calculate()
Full sample
func test(_ expressrion: String) {
if let num = expressrion.calculate() {
print("\(expressrion) = \(num)")
} else {
print("\(expressrion) = nil")
}
}
test("3*(3-1)-5")
test("5.2*(2-1.79)-5.1")
test("11/5")
Results