CloudKit Fetch record with CKRecordID - cloudkit

I have a problem with fetching a record from iCloud. I want to access a fetched record and assign one value from the key-value pair.
I already tried:
currentRecord.object(forKey:)
currentRecord.value(forKey:)
currentRecord.value(forKeyPath:
Here is my fetchRecord method:
public func fetchRecord(_ recordID: CKRecordID) -> Void {
let privateDatabase = CKContainer.default().privateCloudDatabase
var recordIDArray: [CKRecordID] = []
recordIDArray.append(recordID)
let fetchRecordsWithID = CKFetchRecordsOperation(recordIDs: recordIDArray)
fetchRecordsWithID.fetchRecordsCompletionBlock = { (records, error) in
if error != nil {
print("Error")
} else {
DispatchQueue.main.async() {
self.currentRecord = records?[recordID]
print("\(self.currentRecord)")
print("ALL KEYS: \(self.currentRecord.allKeys())")
self.companyNameLabel.text = self.currentRecord.value(forKeyPath: "companyName") as? String// as? String //object(forKey: "companyName") as? String
// self.companyStreetLabel.text = record!.object(forKey: "companyStreet") as? String
// self.companyZipCodeLabel.text = record!.object(forKey: "companyZipCode") as? String
// self.companyCityLabel.text = record!.object(forKey: "companyCity") as? String
// self.companyPhoneLabel.text = record!.object(forKey: "companyPhone") as? String
// self.companyEmailLabel.text = record!.object(forKey: "companyEmail") as? String
}
}
}
Result of:
print("\(self.currentRecord)")
is:
{
creatorUserRecordID -> <CKRecordID: 0x170021800; recordName=__defaultOwner__, zoneID=_defaultZone:__defaultOwner__>
lastModifiedUserRecordID -> <CKRecordID: 0x17002d420; recordName=__defaultOwner__, zoneID=_defaultZone:__defaultOwner__>
creationDate -> 2017-02-24 16:39:21 +0000
modificationDate -> 2017-02-24 17:18:54 +0000
modifiedByDevice -> iCloud
companyStreet -> "Street"
companyCity -> "City"
companyPhone -> "+49 123456136"
companyEmail -> "info#abc.de"
companyName -> "PKP"
companyZipCode -> "12356"
})
Everything seems fine with the record
print("ALL KEYS: \(self.currentRecord.allKeys())")
Result:
ALL KEYS: ["companyStreet", "companyCity", "companyPhone", "companyEmail", "companyName", "companyZipCode"]
If I want to assign the value of the key "companyStreet" to my label, it returns nil:
fatal error: unexpectedly found nil while unwrapping an Optional value
2017-02-24 18:19:55.239907 XYZ[4868:976786] fatal error: unexpectedly found nil while unwrapping an Optional value
EDIT -> SOLVED
I finally solved my problem. The problem was my appDelegate, trying to handle my CKNotification. Instead of referencing to my existing TableViewController (embedded in a Navigation Controller - TabBarController) I created a new instance of my TableViewController.

Related

How to convert json into String array

Hi I'm very new to Swift and I'm trying to make a simple application.
The app gets data from server as JSON format.
func addLangList(completion: #escaping ([String], [String]) -> Void) {
let request = NetworkRequest()
let reqUrl = NetworkInformation.serverAddr + "/word/purpose"
let parameters: Parameters = ["category": "lang"]
request.sendGetRequest(url: reqUrl, parameters: parameters, success: { (response) in
let json = JSON(response)
let isSuccess = json[ServerResponseKey.KEY_RESULT]
if isSuccess == true {
var resultMessage:JSON = json[ServerResponseKey.KEY_MESSAGE]
let lang = resultMessage["lang"].arrayValue
let purpose = resultMessage["purpose"].arrayValue
completion(lang, purpose)
}
}, fail: request.CommonNetworkFailureHandler)
}
By using Swiftyjson, the function converts the data received into JSON format. Inside the closure, 'completion' is called for further process in caller. An error occurs at 'completion(lang, purpose). Xcode says
" Cannot convert value of type '[JSON]' to expected argument type '[String]'".
The error, I guess, because .arrayValue doesn't change resultMessage["lang"] into [String] type....Can anyone give me some advice??
Those 2 arrays
let lang = resultMessage["lang"].array
let purpose = resultMessage["purpose"].array
are of type JSON which isn't String , you need to cast them
let langStr = lang.map { $0.string }
let purposeStr = purpose.map { $0.string }
let langStr = lang.map { $0.string }
let purposeStr = purpose.map { $0.string }

Parse String into an object in Swift

I have received this response from the server and I am sure there must be a more efficient way to convert it into an object.
I have the following response:
[
id=2997,rapidViewId=62,state=ACTIVE,name=Sprint7,startDate=2018-11-20T10:28:37.256Z,endDate=2018-11-30T10:28:00.000Z,completeDate=<null>,sequence=2992,goal=none
]
How do I convert it nicely into a well formed swift object in the simplest way?
Here is my attempt which gives me just the Sprint Value
if sprintJiraCustomField.count > 0 {
let stringOutput = sprintJiraCustomField.first?.stringValue // convert output to String
let name = stringOutput?.components(separatedBy: "name=") // get name section from string
let nameFieldRaw = name![1].components(separatedBy: ",") // split out to the comma
let nameValue = nameFieldRaw.first!
sprintDetail = nameValue// show name field
}
Not sure what format you want but the below code will produce an array of tuples (key, value) but all values are strings so I guess another conversion is needed afterwards
let items = stringOutput.components(separatedBy: ",").compactMap( {pair -> (String, String) in
let keyValue = pair.components(separatedBy: "=")
return (keyValue[0], keyValue[1])
})
This is a work for reduce:
let keyValueStrings = yourString.components(separatedBy: ",")
let dictionary = keyValueStrings.reduce([String: String]()) {
(var aggregate: [String: String], element: String) -> [String: String] in
let elements = element.componentsSeparatedByString("=")
let key = elements[0]
// replace nil with the value you want to use if there is no value
let value = (elements.count > 1) ? elements[1] : nil
aggregate[key] = value
return aggregate
}
This is a functional approach, but you can achieve the same using a for iteration.
So then you can use Swift’s basic way of mapping. for example you will have your custom object struct. First, you will add an init method to it. Then map your object like this:
init(with dictionary: [String: Any]?) {
guard let dictionary = dictionary else { return }
attribute = dictionary["attrName"] as? String
}
let customObjec = CustomStruct(dictionary: dictionary)
We already have some suggestion to first split the string at each comma and then split each part at the equals sign. This is rather easy to code and works well, but it is not very efficient as every character has to be checked multiple times. Writing a proper parser using Scanner is just as easy, but will run faster.
Basically the scanner can check if a given string is at the current position or give you the substring up to the next occurrence of a separator.
With that the algorithm would have the following steps:
Create scanner with the input string
Check for the opening bracket, otherwise fail
Scan up to the first =. This is the key
Consume the =
Scan up to the first , or ]. This is the value
Store the key/value pair
If there is a , consume it and continue with step 3
Consume the final ].
Sadly the Scanner API is not very Swift-friendly. With a small extension it is much easier to use:
extension Scanner {
func scanString(_ string: String) -> Bool {
return scanString(string, into: nil)
}
func scanUpTo(_ delimiter: String) -> String? {
var result: NSString? = nil
guard scanUpTo(delimiter, into: &result) else { return nil }
return result as String?
}
func scanUpTo(_ characters: CharacterSet) -> String? {
var result: NSString? = nil
guard scanUpToCharacters(from: characters, into: &result) else { return nil }
return result as String?
}
}
With this we can write the parse function like this:
func parse(_ list: String) -> [String: String]? {
let scanner = Scanner(string: list)
guard scanner.scanString("[") else { return nil }
var result: [String: String] = [:]
let endOfPair: CharacterSet = [",", "]"]
repeat {
guard
let key = scanner.scanUpTo("="),
scanner.scanString("="),
let value = scanner.scanUpTo(endOfPair)
else {
return nil
}
result[key] = value
} while scanner.scanString(",")
guard scanner.scanString("]") else { return nil }
return result
}

Why cant I unwrap this optional value using Swift?

I am trying to get the value for first record which is "2018-05-04 09:00:00 +0000". When I print out `firstRecord', I get an optional value in the terminal:
optional(2018-05-04 09:00:00 +0000).
Then I tried to unwrap it like so:
if dailyArray.count == 0 {
print("no record was found")
}else{
guard let firstRecord : String = dailyArray[0]["startTime"] as! String else {return}
let firstRecordArray = firstRecord.components(separatedBy: " ")
print("data exist \(firstRecordArray[0])")
}
And now Xcode is giving me an error saying that you are trying to unwrap a none optional value.
Any idea where am I making a mistake?
Do like this
guard !dailyArray.isEmpty, let firstRecord = dailyArray[0]["startTime"] as? String else { return }
let firstRecordArray = firstRecord.components(separatedBy: " ")
print("data exist \(firstRecordArray[0])")
In your code you are typecasting forcely thatswhy
["startTime"] as! String
this means that the value is Strictly string while ? says it can be string
If you're going to use optional chaining, then you may as well use .first (returns an optional, nil on empty array) rather than [0] (returns value, crashes on empty array):
guard !dailyArray.isEmpty else {
print("no record was found")
return
}
guard let firstRecord = dailyArray.first?["startTime"] as? String else {
return
}
let firstRecordArray = firstRecord.components(separatedBy: " ")
print("data exist \(firstRecordArray[0])")
You can use optional chaining and the if let syntax to unwrap the variable and make the code much more concise and clear:
// dailyArray should be declared as or typecast to [[String : String]]
guard !dailyArray.isEmpty else {
print("no record was found")
exit(1) // or return if in func
}
if let firstRecord = dailyArray.first?["startTime"]?.components(separatedBy: " ") {
print("data exists: \(firstRecord.first)")
}
else {
print("no start time date was found")
}

Check for nil in dictionary

I have a class in my app, where the user inputs values and i set them to an instance of the class, then i upload this data to Database, but i have to convert the class to something the database accepts and i'm converting to dictionary using Mirror Reflection. Some properties in my class can be nil, because by design not all properties are required. But i can't pass nil values to the database.
I have recreated my example is very simplified playground. i didn't set a value for the name property of the class
I tried to check for nil before adding the key, value pair to the dictionary
Below is my code
import UIKit
class Color: NSObject {
var name: String?
var code: Int?
var shade: String?
}
let cl = Color()
cl.code = 3456
cl.shade = "DARK"
var colorDict = [String: Any]()
for x in Mirror(reflecting: cl).children.makeIterator() {
if let val = x.value as Any? {
print(type(of: val))
colorDict[x.label!] = val
}
}
print (colorDict)
the output in console is as below
Optional<String>
Optional<Int>
Optional<String>
["name": nil, "code": Optional(3456), "shade": Optional("DARK")]
how can i check for nil values and skip adding that property to the Dictionary
i have tried to loop through the dictionary after i add all values including nils and check for values too but i get the below warning
Comparing non-optional value of type 'Any' to nil always returns false
declaring the dictionary as below
var colorDict = [String: Any?]()
for x in colorDict {
if x.value == nil {
colorDict.removeValue(forKey: x.key)
}
}
removes the warning but it doesn't remove anything.
I would really appreciate your help.
The way of unwrapping objects of type Any that contain optionals is kind of weird but you can check that the values aren't nil in your mirror like this:
for x in Mirror(reflecting: cl).children {
if case Optional<Any>.some(let val) = x.value {
print(type(of: val))
colorDict[x.label!] = val
}
}
You can do this really easily in a one-liner, using filter:
let dict: [String : Any?] = ["Foo" : 3, "Bar" : nil, "Baz" : "Qux"]
let noNils = dict.filter { $0.value != nil }
print(noNils) // prints ["Foo": Optional(3), "Baz": Optional("Qux")]
As i have suggested, initialise all values.
If you decide not to store the nil values you will end up with children that some of them will have 1, some 2 and some 3 nodes, nothing wrong with that, BUT what happens when you go to read them?
You havent shared any info as to how these values will be used by the app, but assuming you have one function to read the properties/nodes of stored colors, it will go to read all 3 :
ref.child("colors").child("someSpecificColor").observeSingleEvent(of: .value, with: { (snapshot) in
// Get color values
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let code = value?["code"] as? String ?? ""
let shade = value?["shade"] as? String ?? ""
// ...
}) { (error) in
print(error.localizedDescription)
}
See the issue?
Here is a simple solution :
var colorDict = [String: Any?]()
for x in Mirror(reflecting: cl).children.makeIterator() {
if let val = x.value, val != nil {
print(type(of: val))
colorDict[x.label!] = val
}
}
Here before to print and add you val, you check if the val is different than nil. As your output suggests in your console log you print :
Optional<String>
Optional<Int>
Optional<String>
val is an optional. So, if it's nil it won't be added. If not, you enter into the if statement and that's it.

Swift - matching class dynamically

I am trying to write an extension method on Dictionary with the following signature:
func firstNonNilObjectForKey(keys: [Key], ofClass aClass: Any.Type = String.self) -> Value?
This is meant to help me deal with JSON dictionaries, where I often need the first non-null value, but sometimes the JSON includes null itself as value, which is converted to NSNull in Cocoa.
The usage would be something like:
let dict: [String:AnyObject] = [...]
let myValue = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String
The issue is implementational - how to match the class:
if let value = self[key] {
if value is aClass { ... } <--- error: aClass is not a type
if let castedValue = value as? aClass { ... } <--- error: ditto
let type = value.dynamicType
if type == aClass { ... } <--- no error, but doesn't handle subclasses!
// Cannot use value.isKindOfClass() since value may be Any
}
I have thought of an alternative:
func firstNonNilObjectForKey<ReturnValueType>(keys: [Key]) -> ReturnValueType?
which allows to be implemented as
if let value = self[key] as? ReturnValueType { ... }
But:
- Here I cannot set the default type (I mostly need String.Type).
- I'm not really sure how to invoke this:
let value = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String <--- error: Cannot invoke 'firstNonNilObjectForKey' with an argument list of type '([String])'
let value = dict.firstNonNilObjectForKey<String>([ ... ]) <--- error: Cannot explicitly specialize a generic function
I hope this isn't a duplicate, but most similar questions here are simply handling a situation where you are matching against a known class.
I'm not sure I got the requirements exactly, but can something like this work for you?
extension Dictionary {
func firstNonNilObjectForKey(keys: [Key]) -> String? {
return self.firstNonNilObjectForKey(keys, ofClass: String.self)
}
func firstNonNilObjectForKey<T>(keys: [Key], ofClass aClass: T.Type) -> T? {
for k in keys {
if let v = self[k] as? T {
return v
}
}
return nil
}
}
let dict = ["key1": 2, "key2": "Foo"]
let aString = dict.firstNonNilObjectForKey(["key1", "key2"]) // "Foo"
let anInt = dict.firstNonNilObjectForKey(["key1", "key2"], ofClass: Int.self) // 2
The main gotcha here is that I'm using overloading as opposed to default parameters, which don't seem to get along well with the swift type checker.