Convert UUID to String representation eg: 1816 to Cycling Speed And Cadence - swift

I'm connecting to a Cycling Speed and Cadence service via bluetooth and am saving the peripheral into an array (for tableview).
I have implemented this struct
struct BTPeripheral: Identifiable {
let id: Int
let name: String
let uuid: UUID
let desc: String
}
and am calling is as
discoveredBTDevices.append(BTPeripheral.init(
id: discoveredBTDevices.count,
name: peripheral.name!,
uuid: peripheral.identifier,
desc: service.uuid.uuidstring) // trying service.uuid results in error
)
which results in this output
id: 0,
name: "Wahoo CADENCE 1DE8",
uuid: E98F3843-1C6A-E4DE-6EF3-76B013950007,
desc: "1816"
The description is the string 1816 (which is the UUID of the cycle Speed & Cad service)
let serviceCyclingSpeedAndCadenceUUID = CBUUID(string: "1816")
How can I convert the 1816 into the string "Cycling Speed and Cadence" which is what gets printed out
print(service.uuid) // this results in "cycling Speed and Cadence"
As noted above, putting service.uuid results in an error Cannot convert value of type 'CBUUID' to expected argument type 'String'

You can just get its description (which, IIRC, is what print will do eventually, as CBUUID conforms to CustomStringConvertible):
service.uuid.description
Or use a string interpolation:
"\(service.uuid)"
debugDescription produces the same result too, but I wouldn't use this unless this is actually for debugging.

Related

Get codable's property names as strings in swift

Edit
let disucssionMessageTimestampKey = DiscussionMessage.CodingKeys.messageTimestamp.stringValue gives an error:
'CodingKeys' is inaccessible due to 'private' protection level
I have a message structure defined like this:
struct DiscussionMessage: Codable {
let message, userCountryCode, userCountryEmoji, userName, userEmailAddress: String
let messageTimestamp: Double
let fcmToken, question, recordingUrl, profilePictureUrl: String?
}
I want to define a variable disucssionMessageTimestampKey whose value will be messageTimestamp. I want to use disucssionMessageTimestampKey variable in the following query:
messagesReference.queryOrdered(byChild: "messageTimestamp").queryStarting(atValue: NSDate().timeIntervalSince1970).observe(.childAdded)
So that I don't have to hardcode the string value ("messageTimestamp") of the variable name.
Now I know I could just do let disucssionMessageTimestampKey: String = "messageTimestamp". But this is again prone to errors. So I was wondering if there was a way that I could get the string value messageTimestamp without having to define it anywhere.
By something like this (I know this won't work but just to give an idea of what I am looking for)
let disucssionMessageTimestampKey: String = String(describing: DiscussionMessage.messageTimestamp) // Will store disucssionMessageTimestampKey = "messageTimestamp"
Also, would it be possible to completely define the key values first as strings and then use those as variable names in the actual codable object? I.e. first define let disucssionMessageTimestampKey: String = "messageTimestamp", and then use the variable disucssionMessageTimestampKey to define what the property (messageTimestamp) of the codable object should be called. (This is low priority but curious and seems related to the question at hand)

Correct way to insert/update Codable struct document/array to Firestore

My Firestore document structure, i created it via Android:
--- Document
----- testA (object)
----- testB (array)
------- 0: Device item 1
------- 1: Device item 2
I have following struct:
import Foundation
public struct Device: Codable {
var manufacturer: String?
var model: String?
var osVersion: String?
enum CodingKeys: String, CodingKey {
case manufacturer
case model
case osVersion
}
}
My device object
let currentDevice = Device(manufacturer: "a", model: "b", osVersion: "c")
Test A: Update Device to Document.
I tried below code:
db.collection("testCollection").document("testDoc").updateData([testA: currentDevice])
...
Test B: Add Device to testB array.
db.collection("testCollection").document("testDoc")
.updateData([testA: FieldValue.arrayUnion([currentDevice])])
It all cause below error: Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'Unsupported type: __SwiftValue'
I also checked official document but it does not show anyway to update struct object to Firestore. What can i do? Thanks.
Hopefully this helps somebody out.
First, in the original example above, testA is not quoted. The field key is a string. It should be "testA"
In order to save an item to an array on a document ref, using updateData, swift structs/classes need to be encoded into a dictionary. FirebaseFirestoreSwift helps with this by providing a specialized encoder. Otherwise, you can manually convert from your struct into the expected [String: Any] dictionary.
This example uses FirebaseFirestoreSwift
let currentDevice = Device(
manufacturer: "a",
model: "b",
osVersion: "c"
)
// declare outside of the try/catch block
let encoded: [String: Any]
do {
// encode the swift struct instance into a dictionary
// using the Firestore encoder
encoded = try Firestore.Encoder().encode(currentDevice)
} catch {
// encoding error
handleError(error)
return
}
let fieldKey = "testA"
// add a new item to the "testA" array.
db.collection("testCollection").document("testDoc")
.updateData(
[
fieldKey: FieldValue.arrayUnion([encoded])
]
) { error in
guard let error = error else {
// no error, that's great
return
}
// uh oh, firestore error
handleError(error)
return
}
Usually, this error is related to the fact that Firestore is not able to serialise different types of variables and arrays at the same time - in your case the problem seems related to the currentDevice variable, that Firestore is not being able to identify.
It seems that you will need to modify the below part, for your application to be able to insert the data.
...
enum CodingKeys: String, CodingKey {
case manufacturer
case model
case osVersion
}
...
Since you are not using one "variable" to add the information for the manufacturer, model and osVersion. I would recommend you to check this question from the Community here, to get a possible solution - adapting to your case - of how to convert your data for Firestore to be able to understand.
Let me know if the information helped you!

Swift initialise property at struct initialisation without initialiser

In swift, structs have an automatically generated memberwise initializer.
This means the following struct can be initialised without me having to write an init.
struct Activity {
let name: String
let desc: String
let category: Category
let subcategory: Subcategory
let emoji: Character
let coordinate: CLLocationCoordinate2D
let creationTime: Date = Date()
let activityTime: Date
let id: UUID = UUID()
var comments: [Comment] = []
}
I have one single property called emojiwhich is computed by the subcategory. In other words, the value for emoji depends on the value of subcategory.
However this means that the value of emoji can only be assigned after the initialisation of subcategory.
How should I do this in code?
Approach 1:
Provide my own initialiser
init(name: String, desc: String, category: Category, subcategory: Subcategory,
coordinate: CLLocationCoordinate2D, activityTime: Date) {
self.name = name
self.desc = desc
self.category = category
self.subcategory = subcategory
self.coordinate = coordinate
self.activityTime = activityTime
self.emoji = AllCategories.categories[category]?[subcategory] ?? "❌"
}
I don't like this approach as it adds a lot of unecessary code that will only grow if I add more properties... I would like to use the generated initialiser of the struct. On the other hand, the code is still very simple.
Approach 2:
Use a lazy varthat is only computed when called.
lazy var emoji: Character = {
AllCategories.categories[category]?[subcategory] ?? "❌"
}()
I also don't really like this approach, I find it overly complex for what I am trying to do. Also this makes emojia varinstead of letwhich it is not, I want it to remain a constant. On the other hand, I can continue using the automatically generated initialiser.
Questions:
What other possibilities do I have?
If there are none, which of the 2 approches is the best?
This sounds like a great chance to use computed properties:
var emoji: Character {
AllCategories.categories[category]?[subcategory] ?? "❌"
}
Although it is declared a var, you can't actually set it. It's just how computed properties must be declared.
The expression AllCategories.categories[category]?[subcategory] ?? "❌" will be evaluated every time you use the property. It's not a too time-consuming expression, so IMO it's fine.

Difference between "\(string)" and string?

callfunc(string: "\(string)")
callfunc(string: string)
I am calling the same function with same string value but different approach....
let me know what is the difference in it? and also I want to know in terms of memory consumption.
there is no difference, "\()" is used if your string is something like
let someInt: Int = 20
print("my integer is \(someInt)") //"my integer is 20"
i.e. not String in first place.
there is no memory difference because String in Swift is not reference type, it is Struct, so you pass copy of string to your callfunc, not reference to it.
There's a difference when your string is implicitly unwrapped optional. Consider example:
func some(string: String)
{
print(string)
}
let string: String! = "s"
some(string: string)
some(string: "\(string)")
The output will be:
s
Optional("s")
callfunc(string: string)
In the above syntax its a normal function call with a string.
callfunc(string: "(string)")
But here when we will pass "(string)" as a parameter, internally "(string)" creates a new string and pass it as a parameter. So in that particular point of time the memory will go high because of memory allocation for the string, which will again deallocated immediately.
Normally you won't be able to observe it with a small string, but if you will convert an image into base64 and try to pass it as a string. you can able to see the difference.
Apart from that there is no difference in functionality.

NSNull into a Struct with a property of type NSDate

I have an object from the server that is recognized by Swift 2.1 as either NSDate or NSNull. I want to put it into a struct with a property of type NSDate.
Is that possible? If not, how should I handle this to be type safe later when I use it?
struct Data {
var completedAt: [NSDate]
var name: [String]
var gender: [Bool]
}
but sometimes completedAt comes from the server as NSNull:
completedAt = "<null>";
Any help is very much appreciated, thank you.
Based on my interpretation of the text in the question you didn't mean to declare the variables as arrays.
This is how I handle my parson and I think it works pretty neatly.
The date formatter should probable not be initiated in every iteration of the constructor. If you won't use the date regularly you might want to keep the detesting until you need to parse the date or you can have a static date formatter utility that you only instantiate once.
struct Data {
var completedAt: NSDate?
var name: String
var gender: Bool
init?(dictionary: [String:AnyObject]) {
//Guessing that you want some of the values non optional...
guard let name = dictionary["name"] as? String,
let gender = dictionary["gender"] as? String
else {
return nil
}
self.name = name
self.gender = gender
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
//safe handle of optional values
if let completedAtString = dictionary["completedAt"] as? String, completedAt = dateFormater.dateFromString(completedAtString) {
self.completedAt = completedAt
}
}
}
Take a step back. For each item that the server might provide, there is no guarantee whatsoever that you receive what you expect, since you cannot control the server. So you need to decide how to react to which input.
In the case of expecting a date for example (if your data comes in JSON, that means you likely expect a string formatted in a certain way), the actual data that you receive might be an array, dictionary, string, number, bool, null, or nothing. You might then for example decide that you want to interpret nothing or null or an empty string as nil, that you want to interpret a string containing a well-formatted date as an NSDate, and anything else a fatal error in a debug version, and as either nothing or a fatal error in a release version. On the other hand, if an NSDate is absolutely required then you might interpret anything that doesn't give an NSDate as an error.
Then you write a function that delivers exactly what you want and use it. That way you can parse complex data, with your code warning you when something isn't as it should be, and with your code either surviving any possible input, or deliberately crashing on wrong input, as you want it.