Why can't I pass an array to a vararg in Swift? - arrays

I have this function:
func doStuff(stuff: Int...) {
print(stuff)
}
and I call it like this:
let array = [1, 2, 3]
doStuff(array)
And it does not compile!
I mean, this makes no sense, right? The function is supposed to accept a list of things, and I am giving it a list of things. How come this doesn't work?
Here's some background info (you can skip it)
I have this NSManagedObject subclass:
class Entry: NSManagedObject {
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
// irrelevent
}
convenience init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext, title: String, content: String, date: NSDate) {
// irrelevent
}
}
extension Entry {
#NSManaged var content: String?
#NSManaged var date: NSDate?
#NSManaged var title: String?
}
In one of my view controllers, I fetch all the Entrys in viewDidLoad and I stored the fetched stuff in a variable called anyObjs which is of type [AnyObject]
I want to turn this [AnyObject] to a [NSDate: Entry], where the keys are the values' date property. I want it this way in order to easily access an Entry using an NSDate.
So I tried the following:
let literal = anyObjs!.map { (($0 as! Entry).date!, $0 as! Entry) }
entries = [NSDate: Entry](dictionaryLiteral: literal)
And I found out that I can't pass a [(NSDate, Entry)] to (NSDate, Entry)...!
"That's easy" you might say, "just pass all the elements in the array as varargs using the subscript!"
doStuff(array[0], array[1], array[2])
But this doesn't work if I don't know how many items there are.
Another workaround that doesn't work is to create a function that accepts an array:
func doStuff(array: [Int]) {
print(array)
}
This doesn't work either because if I don't know the exact implementation of the function, I cannot rewrite it in the new function.
What can I do?

You are right! There ought to be a method for getting a dictionary from an array of tuples.
extension Dictionary {
init(tuples: [Element]) {
self.init()
for (key, value) in tuples {
self.updateValue(value, forKey: key)
}
}
}
OK, now that's done, let's see.
let tuples = anyObjs!.map { (($0 as! Entry).date!, $0 as! Entry) }
let entries = [NSDate: Entry](tuples: tuples)
Or combine the two lines
let entries = [NSDate: Entry](tuples: anyObjs!.map { (($0 as! Entry).date!, $0 as! Entry) })

Related

Swift - Tableview won't reload correctly after loading for the second time

I am a little confused about how my Tableview is reacting. My problem is that the Tableview shows the other order than Dictionary than when I print the Dictionary. When I print the Dictionary everything seems to be fine, but when I look at my phone the tableview is showing the cell at a random order again.
First I fetch the JSON.
var Aanbiedingen_bieren = [Aanbiedingen_bier]()
var Aanbiedingen_bieren_Filter = [Aanbiedingen_bier]()
Fetch data.
func fetchData() {
Aanbiedingen_bieren_Filter.removeAll()
Aanbiedingen_bieren.removeAll()
let url_Aanbiedingen_bier = URL(string: "\(Firebase_url)")
let downloadURL = url_Aanbiedingen_bier
URLSession.shared.dataTask(with: downloadURL!) { data, urlResponse, error in
let data = data
print("downloaded")
do
{
let decoder = JSONDecoder()
let downloadedBiers = try decoder.decode(SheetJS.self, from: data!)
self.Aanbiedingen_bieren = downloadedBiers.SheetJS
for jsonData in self.Aanbiedingen_bieren {
let Soort = jsonData.Title
if self.Soort_Bier == "Alles" {
self.Aanbiedingen_bieren_Filter.append(jsonData)
}else{
if self.Soort_Bier == "Krat" {
if Soort.contains(word: "Krat") {
self.Aanbiedingen_bieren_Filter.append(jsonData)
}
}
if self.Soort_Bier == "Fles" {
if Soort.contains(word: "Fles") {
self.Aanbiedingen_bieren_Filter.append(jsonData)
}
}
}
}
self.Aanbiedingen_bieren_Filter = self.Aanbiedingen_bieren_Filter.sorted(by: {$0.Voor_prijs.compare($1.Voor_prijs, options: .numeric) == .orderedAscending})
print(self.Aanbiedingen_bieren_Filter)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}.resume()
}
The first time that the code runs the Soort_Bier = "Alles". After a button tap the Soort_Bier will change in Soort_bier = "Krat". After the data was loaded for a second time I first wanted to filter the data. I have this done by implementing an if statement that checks if the Title of the JSON has a specific word in the String and if so append it to an other array. After that, I wanted to sort the price. When the sorting is finished I wanted to print the Dictionary to see if the sorting is correct. This still seems to be the case. Then I want to reload the tableView so all of the cells will show and here is something wrong. When I want to load the dictionary for the second time, it doesn't seem to reload the tableView correctly.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Aanbiedingen_bieren_Filter.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Aanbiedingen_bieren_Cell", for: indexPath) as! Aanbiedingen_bieren_Cell
let Aanbiedingen_bier = self.Aanbiedingen_bieren_Filter[indexPath.row]
cell.Aanbiedingen_bier = Aanbiedingen_bier
return cell
}
Struct:
struct SheetJS: Codable {
var SheetJS: [Aanbiedingen_bier]
init(SheetJS: [Aanbiedingen_bier]) {
self.SheetJS = SheetJS
}
}
struct Aanbiedingen_bier: Codable {
let Logo_Image: String
let Van_prijs: String
let Voor_prijs: String
let Beschrijving: String
let Item_Image: String
let Title: String
let Bestel_Online_link: String
let Footer_item: String
let Item_1: String
let Item_2: String
init(Logo_Image: String, Item_Image: String, Van_prijs: String, Voor_prijs: String, Beschrijving: String, Title: String, Item_1: String, Item_2: String, Bestel_Online_link: String, Footer_item: String) {
self.Logo_Image = Logo_Image
self.Title = Title
self.Item_Image = Item_Image
self.Beschrijving = Beschrijving
self.Van_prijs = Van_prijs
self.Voor_prijs = Voor_prijs
self.Item_1 = Item_1
self.Item_2 = Item_2
self.Bestel_Online_link = Bestel_Online_link
self.Footer_item = Footer_item
}
}
JSON:
{
"SheetJS": [
{
"Logo_Image": "https://www.biernet.nl/images/winkel/17335-agrimarkt.gif",
"Van_prijs": "€16,99",
"Voor_prijs": "€10,49",
"Beschrijving": "Krat 24x0,30",
"Item_Image": "https://www.biernet.nl/images/soort/23026-grolsch%20krat%20normale%20flesjes%2030%20cl.png",
"Title": "Grolsch Premium Pilsener",
"Bestel_Online_link": "",
"Footer_item": "t/m zaterdag 3 augustus",
"Item_1": "€6,50 korting (38%)",
"Item_2": "€1,46 per liter"
},//Some more data
]
}
Only price printed of dictionary:
€5,39
€5,94
€6,39
€6,39
€7,64
€16,19
Result
Please let me know if you want more information or code.
It's because items in Dictionary ordered randomly.
If you want to order it, you should use 'Set' another ordered collection type.
Or see the Apple Document below
Apple Docs : The order of key-value pairs in a dictionary is stable between mutations but is otherwise unpredictable. If you need an ordered collection of key-value pairs and don’t need the fast key lookup that Dictionary provides, see the KeyValuePairs type for an alternative.
I think you haven't been using wrongly data collection. You should use array to order. Because A dictionary stores associations between keys of the same type and values of the same type in a collection with no defined ordering. Unlike array, a dictionary do not have a specified order.
Click here! to read about data collection in swift

Swift 3 - Pass Dictionary inside Array from JSON to Another Method

I have the following JSON:
{
"stores":2,
"store_data":[
{
"store_id":1,
"store_name":"Target"
"store_color":"000000"
},
{
"store_id":2,
"store_name":"Walmart"
"store_color":"FFFFFF"
}
]
}
And I am collecting it (within a function) the following way (safeguards removed for simplicity):
let task = URLSession.shared.dataTask(with: baseURL) { (data, response, error) in
if let tmpRawData: NSDictionary = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary {
process(rawData: tmpRawData)
}
}
And sending it to the helper process function defined as:
func process(rawData: NSDictionary) -> Bool {
if let storeDataArray = rawData["store_data"] {
// Here I want to loop through the store_data array
}
}
And I am having some trouble looping through the array in the function above. Initially, I had tried:
for store: Dictionary<String, String> in storeDataArray {
// Do things with store["store_id"] here
}
But I am new to swift and am having trouble deciphering between NSArray, Array, Dictionary, NSDictionary, etc. I'm working in Swift 3. Any help is much appreciated!
First of all, don't annotate types that much. The compiler will tell you if it needs an explicit annotation
Second of all it's convenient to declare a type alias for a JSON dictionary
typealias JSONObject = [String:Any]
This is the task, tmpRawData is a dictionary – represented by {} in the JSON.
let task = URLSession.shared.dataTask(with: baseURL) { (data, response, error) in
if let tmpRawData = try JSONSerialization.jsonObject(with: data, options: []) as! JSONObject {
process(rawData: tmpRawData)
}
}
and the process function, the type alias makes everything more readable.
The value of rawData is an array of dictionaries – represented by [] in the JSON.
func process(rawData: JSONObject) -> Bool {
if let storeDataArray = rawData["store_data"] as? [JSONObject] {
for store in storeDataArray {
let storeID = store["store_id"] as! Int
let storeName = store["store_name"] as! String
let storeColor = store["store_color"] as! String
print(storeID, storeName, storeColor)
}
}
}
I have no idea why all tutorials suggests the mutableContainers option. You don't need it at all in Swift when using native collection types.

Segue textfields to a string array

I'm doing an app using swift that takes the entries in 6 text fields and passes them through a segue into an empty string array on a second view controller. How do I do that?
This is my code, is the basics, but I'm not sure how I can send the information through.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! secondView
if segue.identifier == segueID {
destination.stringArray = ?
}
}
The array on my second view controller is this:
var stringArray = [String]()
The shortest way:
destination.stringArray = view.subviews.flatMap { ($0 as? UITextField)?.text }
Are your 6 textfields in an IBOutletCollection (in other words, an array of textfields) or are they each their own IBOutlet (textfield)? Either way, your question comes down to putting all of their text values in an array, which can be done like this if they're in an IBOutletCollection:
destination.stringArray = myTextFieldCollection.flatMap { $0.text }
Or, if they're not in an array:
destination.stringArray = [myTextField1, myTextField2, ...,
myTextField6].flatMap { $0.text }
I'm using flatMap in these because UITextField.text is an optional String?, so they need to be unwrapped to String, which flatMap safely does.

Extend Swift Array to Filter Elements by Type

How can a swift array be extended to access members of a particular type?
This is relevant if an array contains instances of multiple classes which inherit from the same superclass. Ideally it would enforce type checking appropriately.
Some thoughts and things that don't quite work:
Using the filter(_:) method works fine, but does enforce type safety. For example:
protocol MyProtocol { }
struct TypeA: MyProtocol { }
struct TypeB: MyProtocol { }
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.filter({ $0 is TypeA })
the filteredArray contains the correct values, but the type remains [MyProtocol] not [TypeA]. I would expect replacing the last with let filteredArray = myStructs.filter({ $0 is TypeA }) as! [TypeA] would resolve that, but the project fails with EXEC_BAD_INSTRUCTION which I do not understand. Perhaps type casting arrays is not possible?
Ideally this behavior could be wrapped up in an array extension. The following doesn't compile:
extension Array {
func objectsOfType<T:Element>(type:T.Type) -> [T] {
return filter { $0 is T } as! [T]
}
}
Here there seem to be at least two problems: the type constraint T:Element doesn't seem to work. I'm not sure what the correct way to add a constraint based on a generic type. My intention here is to say T is a subtype of Element. Additionally there are compile time errors on line 3, but this could just be the same error propagating.
SequenceType has a flatMap() method which acts as an "optional filter":
extension SequenceType {
/// Return an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
#warn_unused_result
#rethrows public func flatMap<T>(#noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}
Combined with matt's suggestion to use as? instead of is you
can use it as
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.flatMap { $0 as? TypeA }
Now the type of filteredArray is inferred as [TypeA].
As an extension method it would be
extension Array {
func objectsOfType<T>(type:T.Type) -> [T] {
return flatMap { $0 as? T }
}
}
let filteredArray = myStructs.objectsOfType(TypeA.self)
Note: For Swift >= 4.1, replace flatMap by compactMap.
Instead of testing (with is) how about casting (with as)?
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
var filteredArray = [TypeA]()
for case let t as TypeA in myStructs {filteredArray.append(t)}
Casting arrays does not work in Swift. This is because arrays in Swift use generics, just like you can't cast a custom class, where only the type T changes. (class Custom<T>, Custom<Int>() as! Custom<String>).
What you can do is create an extension method to Array, where you define a method like this:
extension Array {
func cast<TOut>() -> [TOut] {
var result: [TOut] = []
for item in self where item is TOut {
result.append(item as! TOut)
}
return result
}
}
I think the canonical FP answer would be to use filter, as you are, in combination with map:
let filteredArray = myStructs.filter({ $0 is TypeA }).map({ $0 as! TypeA })
alternatively, you can use reduce:
let filtered2 = myStructs.reduce([TypeA]()) {
if let item = $1 as? TypeA {
return $0 + [item]
} else {
return $0
}
}
or, somewhat less FP friendly since it mutates an array:
let filtered3 = myStructs.reduce([TypeA]()) { ( var array, value ) in
if let item = value as? TypeA {
array.append(item)
}
return array
}
which can actually be shortened into the once again FP friendly flatMap:
let filtered4 = myStructs.flatMap { $0 as? TypeA }
And put it in an extension as:
extension Array {
func elementsWithType<T>() -> [T] {
return flatMap { $0 as? T }
}
}
let filtered5 : [TypeA] = myStructs.elementsWithType()

Sorting UIImage Type Array in Swift

How do I sort images in order in an array type UIImage?
I'm query images from Parse, putting them in an AnyObject type array and converting it into UIImage type array.
var imagesArray:[AnyObject] = []
var uiImageArray:[UIImage] = []
To display the images I'm doing this:
func updateImageOnUI() { //Changes the UI
if imageCounter < self.imagesArray.count {
var imageElement: AnyObject = self.imagesArray[imageCounter]
var imageUpdate: AnyObject = self.imagesArray[imageCounter]
println(imageUpdate["ImageFiles"])
let userImageFile = imageUpdate["ImageFiles"] as PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if !(error != nil) {
let image = UIImage(data:imageData)
self.image.image = image
}
}
} else {
imageQuery()
}
}
Problem is, I can't sort the images so that they appear in order. They are named "image1.jpg, image2.jpg, etc"
How do I sort the images so that they repeat in order?
I've tried the sort function, but it's giving me errors
let sortedArray = sorted(imageList, {
(str1:UIImage, str2: UIImage) -> Bool in
return str1. /*not sure what to put*/ > str2./*not sure what to put*/
})
The errors I'm redesign are saying that AnyObject or UIImage aren't compatible types.
It's not possible to get the fileName of an UIImage after it is set.
You could always store the array of UIImages as [(fileName: String, image: UIImage)]. When you insert them, you add the filename (or other name you want to sort by). Then you can sort the array of tuples by fileName.

Resources