Swift 1D Dictionary to 2D - ios

I've got a little problem in understanding two-dimensional dictionaries. My function has to return dictionary for UITableView with sections. 1 template type can have multiple template strings. So when in fetchedData there are 2 or more texts with similar types, they have to be in array [String] with 1 key - String.
The code below is absolutely correct from the complier point of view. As for me smth is wrong, but nice auto-completions make me think that everything is OK.
Obviously it returns an empty dictionary [:]
func fetchTemplates() -> Dictionary<String, [String]> {
var templates: Dictionary<String, [String]> = [:]
let fetchRequest: NSFetchRequest<Template> = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor.init(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
if (!fetchedData.isEmpty) {
for templateItem in fetchedData {
templates[templateItem.templateType!]?.append(templateItem.templateText!)
}
return templates
}
else {
return templates
}
}
P.S. fetchedData returns:
<Template: 0x003281h4> (entity: Template; id: 0x003281h4 <x-coredata:///Template/> ; data: {
templateText = "Example";
templateType = "First";
})

The issue lies on this line:
templates[templateItem.templateType!]?.append(templateItem.templateText!)
templates was initialized with this line: var templates: Dictionary<String, [String]> = [:]. At this point, templates is an empty dictionary.
Let's break that line down into the steps that happen, in chronological order:
templateItem.templateType is accessed, and force unwrapped. A crash will occur if it's nil.
templateItem.templateType! is used as a key into the templates dictionary. This will always return nil. The dictionary is empty, thus is has no values for any keys, including this one.
?.append() is called, on the condition that it's not being called on nil. If it's called on nil, nothing will happen.
3 is the cause of your issue. You need to initialize a new array if one doesn't exist for the key yet:
func fetchTemplates() -> Dictionary<String, [String]> {
var templates: Dictionary<String, [String]> = [:]
let fetchRequest: NSFetchRequest<Template> = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor.init(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
if (!fetchedData.isEmpty) { //see note 2
for templateItem in fetchedData {
let type = templateItem.templateType!
var array = templates[type] ?? [] //see note 1
array!.append(templateItem.templateText!)
templates[type] = array
}
return templates
}
else {
return templates
}
}
This function can be simplified:
func fetchTemplates() -> [String : [String]] {
let fetchRequest = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
var templates = [String, [String]]()
for templateItem in fetchedData {
let type = templateItem.templateType!
templates[type] = (templates[text] ?? []) + [templateItem.templateText!]
}
return templates
}
and reduce can be used instead:
func fetchTemplates() -> [String : [String]] { //see note 3
let fetchRequest = Template.fetchRequest() //see note 4
fetchRequest.sortDescriptors = [SortDescriptor(key: "templateType", ascending: true)] //see note 5
let fetchedData = try! context.fetch(fetchRequest)
return fetchedData.reduce([String, [String]]()){templates, templateItem in
(templates[templateItem.tempalteText!] ?? []) + [templateItem.templateText!]
} //see note 6
}
Notes
if template[text] is not nil, it's assigned to array. Otherwise, a new array ([]) is assigned to `array.
this check is unnecessary
Dictionary<String, [String]> can be written as just [String : [String]]
No need for an explicit type signiture
X.init() can be written as just X()
The emptiness check is unnecessary, and the whole for loop can be changed into a reduce call.

The issue is:
templates[templateItem.templateType!] is always nil because the dictionary is empty.
Therefore nothing can be appended.

Related

Sorting fetched results

I'm fetching some objects from core data. One of the properties is a name identifier.
The names can be either text or a number, so the property is a String type.
What I'd like to be able to do is sort it so that the text objects are first, then the numbers in numerical order.
Currently its putting the numbers first, and the numbers are in the wrong order, ie. 300, 301, 3011, 304, 3041, Blanc, White
let sortDescriptor = NSSortDescriptor(key: "number", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
Naive version:
let fetchedResults = ["300", "301", "3011", "304", "3041", "Blanc", "White"]
var words = [String]()
var numbers = [String]()
for value in fetchedResults {
if let number = Int(value) {
numbers.append(value)
} else {
words.append(value)
}
}
let result = words + numbers
print(result)
Prints:
["Blanc", "White", "300", "301", "3011", "304", "3041"]
Try this maybe:
var a: [Int] = []
var b: [String] = []
if let value = self[key] as? String {
if let valueAsInt = Int(value) {
a.append(valueAsInt)
} else {
b.append(value)
}
}

Gives all of them optional value

Hello i have variables but gives all of them Optional(). How can i resolve them my codes under below.
Json append codes for koltuklar koltuklaridler array under below you can see
for name in json as! [AnyObject] {
let SeatName = name["SeatName"]
let SeatDesignId = name["SeatDesignId"]
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
let TicketTypeId = blog["TicketTypeId"]
let TicketTypeName = blog["TicketTypeName"]
let Amount = blog["Amount"]
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
Under below you can see tableview inside codes ( That codes doing open koltuklar index path item after search id inside koltuklaridler and when found take some varibles from it )
var koltuklar = [""]
var koltuklaridler = [""]
if let myStrings:String! = koltuklar[indexPath.row]{
print("\(myStrings!)")
let myStringArrf = myStrings.componentsSeparatedByString("*")
print("\(myStringArrf)")
if let koltukisims:String! = String(myStringArrf[0]) {
cell.koltukName.text = koltukisims
}
print(" STR - \(myStringArrf[1] as String!)")
if let index = koltuklaridler.indexOf(myStringArrf[1] as String!) {
let myStringdetaysecilen = koltuklaridler[index]
print("myStringdetaysecilen \(myStringdetaysecilen)")
}
Also my json file
[
{
"SeatDesignId": 16484,
"SeatName": "A6",
"SaloonId": 148,
"SeatDetail": [
{
"TicketTypeId": 1,
"TicketTypeName": "Okay",
"Amount": 13
}
]
},
Output
Optional("A17")*Optional(16254)*
["Optional(\"A17\")", "Optional(16254)", ""]
STR - Optional(16254)
All variables output Optional i try everything but doesn't fix.
As mentioned in my comments, whenever you use String Interpolation "\(...)" make sure that all optional strings are unwrapped. Values read from dictionaries are always optional.
This code unwraps all optional strings
for name in json as! [[String:AnyObject]] {
guard let SeatName = name["SeatName"] as? String,
SeatDesignId = name["SeatDesignId"] as? Int else {
continue
}
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
if let TicketTypeId = blog["TicketTypeId"] as? Int,
TicketTypeName = blog["TicketTypeName"] as? String,
Amount = blog["Amount"] as? Int {
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
}
Edit: I updated the casting to the actual types according to the JSON
Now declare both arrays as empty string arrays.
var koltuklar = [String]()
var koltuklaridler = [String]()
and remove the optional binding in the first line
let myStrings = koltuklar[indexPath.row]
print("\(myStrings)")
...
By the way: Your way to "serialize" the strings with asterisks and deserialize it in the table view is very, very clumsy and inefficient. Use a custom class or struct for the data records.
Your problem is that you are creating a string from values from dict without a if let statement so it returns an optional value:
for name in json as! [AnyObject] {
if let SeatName = name["SeatName"],
let SeatDesignId = name["SeatDesignId"] {
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
}
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
if let TicketTypeId = blog["TicketTypeId"],
let TicketTypeName = blog["TicketTypeName"],
let Amount = blog["Amount"] {
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
}
There is a two way of operate optional.
unwrapped using "!" but in this chances of crash if value is nil.
unwrapped using term call "optional binding" using "if let" condition.
if let var = "assigned your optional variable"{
print(var)
}
You will get your variable without optional.

How to use filteredArrayUsingPredicate with NSDictionary?

Now I'm using
let array = (displayNames as NSArray).filteredArrayUsingPredicate(searchPredicate)
where displayNames is
var displayNames[String]()
But I want to use it with:
var displayNames[String: UIImage]()
How can I use .filteredArrayUsingPredicate with displayNames string part in NSDictionary?
Get the keys from the dictionary using the allKeys property and perform the filter on that. Try that.
RE-EDIT: Try something like this
let theOriginalDictionary = [String : UIImage]()
let searchPredicate = NSPredicate(format: "", argumentArray: nil)
let otherDictionary = NSDictionary(dictionary: theOriginalDictionary)
let arrayOfKeys = NSArray(array: otherDictionary.allKeys)
let filteredArray = arrayOfKeys.filteredArrayUsingPredicate(searchPredicate)
If you're using Swift, why don't you use built-in functions like filter?
var displayNames = [String: UIImage]()
let result = displayNames.filter {key,image in
return true // here add your predicate to filter, true means returning all
}
It sounds like you're trying to filter the dictionary, while keeping the dictionary type after the filter. There's a few ways to do this, but maybe the easiest is to extend dictionary:
extension Dictionary {
func filter(#noescape includeElement: (Key, Value) throws -> Bool) rethrows -> [Key:Value] {
var result: [Key:Value] = [:]
for (k,v) in self where try includeElement(k,v) {
result[k] = v
}
return result
}
}
Then, if you wanted to filter a dictionary based on the keys, you can do something like this:
let dict = ["a": 1, "b": 2, "c": 3]
let filtered = dict.filter { (k,_) in k != "a" }
// ["b": 2, "c": 3]

Swift array of dictionaries with multiple types without using NSDictionary in iOS

I have been working with an array of dictionaries in Swift (version 7 beta 4) using NSDictionary. The array is a collection of fmdb results which contain differing data types. I would like to end up with an array of native swift dictionaries in order to use the filter functionality of swift collection types. The following is a snippet of the query function I'm using the create the array:
class func query(sql:String) -> [NSDictionary] {
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)
let docsDir = dirPaths[0] as String
let databasePath:String = docsDir.stringByAppendingPathComponent("myDB.db")
let db = FMDatabase(path: databasePath as String)
var resultsArray:[NSDictionary] = []
if db.open() {
let results:FMResultSet? = db.executeQuery(sql, withArgumentsInArray: nil)
while (results?.next() == true) {
resultsArray.append(results!.resultDictionary()) //appending query result
}
db.close()
} else {
print("Error: \(db.lastErrorMessage())")
}
return resultsArray
}
I tried using AnyObject such as:
class func query(sql:String) -> [[String:AnyObject]] {
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)
let docsDir = dirPaths[0] as String
let databasePath:String = docsDir.stringByAppendingPathComponent("Kerbal.db")
let db = FMDatabase(path: databasePath as String)
var resultsArray:[[String:AnyObject]] = []
if db.open() {
let results:FMResultSet? = db.executeQuery(sql, withArgumentsInArray: nil)
while (results?.next() == true) {
resultsArray.append(results!.resultDictionary() as! Dictionary<String,AnyObject>)
}
db.close()
} else {
print("Error: \(db.lastErrorMessage())")
}
return resultsArray
}
however I can't use the filter such as resultsArray.filter({$0["fieldName"] == "part"}) with AnyObject.
My question: Is it possible to create this array with native Swift dictionaries even though the dictionaries have different types? Could the new protocol extension be used on collection type to solve this problem?
Any suggestions are appreciated.
If you don't know which objects you are dealing with you have to use filter like so:
resultsArray.filter{ ($0["fieldName"] as? String) == "part"}
So you optionally cast the value to the desired type and compare it.
Note: I'm using the trailing closure syntax.
As suggestion I would use a tuple of different arrays which hold dictionaries:
// some dummy values
let resultsArray: [[String : AnyObject]] = [["Hi" : 3], ["Oh" : "String"]]
var result = (doubles: [[String : Double]](), strings: [[String : String]]())
// going through all dictionaries of type [String : AnyObject]
for dict in resultsArray {
// using switch and pattern matching to cast them (extensible for more types)
// you have to up cast the dictioanary in order to use patten matching
switch dict as AnyObject {
case let d as [String : Double]: result.doubles.append(d)
case let s as [String : String]: result.strings.append(s)
default: fatalError("unexpected type")
}
}
return result
You should probably adopt the Equatable protocol, not all AnyObjects are such in fact, and so no comparison and thereafter filtering may be done.

Syntax for loop ManagedObject insertion in swift

On the process of exploring Core Data with Swift I have the following function working, as a test:
func insertObject (entityName:String) {
var newItem = NSEntityDescription.insertNewObjectForEntityForName(entityName, inManagedObjectContext:managedObjectContext!) as! EventList
let now = NSDate()
newItem.date = now
newItem.eventDescription = “Whatever Anniversary"
}
This seems to work, but to make my function more useful, I want to pass it a dictionnary describing the object I intend to insert.
Something like the following:
func insertObject (entityName:String,dico:NSDictionary) {
var newItem = NSEntityDescription.insertNewObjectForEntityForName(entityName, inManagedObjectContext:managedObjectContext!) as! EventList
for (key, value) in dico {
println("\(key) : \(value)")
newItem.key = value
}
}
Here comes the problem, this line is wrong:
newItem.key = value
What is the proper syntax to use?
This line shows me that the looping part works fine:
println("\(key) : \(value)")
You can use Key-Value coding for managed objects:
func insertObject (entityName:String, dico: [String : NSObject]) {
let newItem = NSEntityDescription.insertNewObjectForEntityForName(entityName, inManagedObjectContext:managedObjectContext!) as! EventList
for (key, value) in dico {
newItem.setValue(value, forKey: key)
}
}
which can be shortened to
func insertObject (entityName:String, dico: [String : NSObject]) {
let newItem = NSEntityDescription.insertNewObjectForEntityForName(entityName, inManagedObjectContext:managedObjectContext!) as! EventList
newItem.setValuesForKeysWithDictionary(dico)
}
The problem with this general approach is that it will crash at
runtime if the dictionary contains keys which are not properties
of the entity, or if the data type does not match.

Resources