I have an array of object, each object contains a discount rate , I need to sort them increasingly by their rate,
struct ShopDetails {
var shopId: Int?
var discountRate: String?
init(with json: Dictionary<String,Any>) {
shopId = json["id"] as? Int
discountRate = json["discount"] as? String
}
I tired to sort them using this method;
func getShopsByDiscount() {
let sortedImages = self.copyOfShops.sorted(by: { (shop1: ShopDetails, shop2: ShopDetails) -> Bool in
return Int(shop1.discountRate) < Int(shop2.discountRate)
})
}
I tried to cast the rate to integer, since its been received form the backend as string, but I got an error:
value of type Any has no member discountRate.
any idea how to do it? if there is a way to do it without casting it will be better
First, you need to verify that the array you're starting with is of type [ShopDetails]. The error indicates that this is probably an Objective-C NSArray, which won't work as well in Swift. If you're unclear about this, I suggest you Google the topic: there's no reason to use NSArray in Swift.
Below, I assume the array is the correct type ([ShopDetails]). From here, you need to do two additional things, because discountRate is of type String?.
You need to check if the string is actually there
You need to check if it can actually be expressed as an Int.
With these things in mind, your sort function can look like this:
let sortedImages = copyOfShops.sorted(by: {
(shop1: ShopDetails, shop2: ShopDetails) -> Bool in
if let shop1String = shop1.discountRate, let shop1Value = Int(shop1String),
let shop2String = shop2.discountRate, let shop2Value = Int(shop2String) {
return shop1Value < shop2Value
}
return true
})
That being said, the best way to handle this is to change the type of discountRate from String? to Int, and do the above checks when you init(with json: Dictionary<String,Any>). If the server giving you the dictionary is something you control, have it switch to passing back Ints instead of Strings, and stop dealing with optionals if you don't have to.
Related
I have an array of Strings and I want to use that as a list.
I followed this example but I didn't get for an array. Link
My current code is given below:
struct ListOfPeripherals: Identifiable {
var id = UUID()
var peripheralName: String
}
struct RestaurantRow: View {
var peripheralFromBLE: ListOfPeripherals
var body: some View {
// List to be implemented here
}
func getListOfAlphabets() -> [String] {
let listOfAlphabets = [A,B,C,D,E]
return listOfAlphabets
}
}
You dont have to convert your Array of String objects to make the List work. You should describe your error pasting it along with your code to get helped faster. So, this is my assumption of your concept of error.
The Array type is fine, but however, his Elements, in this case the strings, need to be identified. This means you have to conform your String to the Identifiable protocol, and providing an id property which must be unique (a simple UUID object will do the trick).
In your code, the function getListOfAlphabets returns an array of Strings which cannot be used. If you were to use a [ListOfPeripherals] array that would work because it conforms to the protocol. I think you got a bit of confusion and you are basically returning an array of wrong type.
I'm trying to parse a json which has an array with Strings, Ints and Arrays
So I'm to iterate on the json {["item1", 2, ["subItem1", "subitem2"] ]} members in the array:
func parse(json : [Any])
for item in json{
if let arr = item as? Array { //
//do stuff for array
}
}
but I'm getting this compile error:
Generic parameter 'Element' could not be inferred in cast to
'Array<_>'
Optional bind item to the different types
for item in json {
if let stringItem = item as? String {
//do stuff for String
} else if let intItem = item as? Int {
//do stuff for Int
} else if let arrayItem = item as? [String] {
//do stuff for Array
} else {
// it's something else
}
}
You receive the error is because in Swift, Array is a generic container that holds values of a specific type. So you can have an Array<Int> that holds integers, or an Array that holds strings. But you can’t have just an Array. The type of the thing the array contains is the generic parameter, and Swift is complaining because it can’t figure out what that type should be. Sometimes it can infer that type from the context of the code around it, but not always, as in this case.
func parse(json : [Any])
for item in json{
if let arr = item as? Array<Any> { //
//do stuff for array
}
}
Instead of writing Array<Any>, you can write the shorter form, [Any].
You can also solve the problem by using NSArray, as you’ve found. Unlike Array, NSArray doesn’t use generics, since it originates in Objective-C which has a different approach to Swift. Instead, NSArray holds only one kind of thing, an AnyObject. This is is a reference that can point to instances of any class.
We imported an outdated project, it prompted us to convert it to Swift 3. As individuals who are not highly knowledgeable in Swift, we are having difficulties fixing an error.
import Foundation
class CellDescriptorHelper {
let itemKey = "Items"
let isExpandableKey = "isExpandable"
let isExpandedKey = "isExpanded"
let isVisibleKey = "isVisible"
let titleKey = "title"
let locationKey = "location"
let descriptionKey = "description"
let imageURLKey = "imageURL"
let typeKey = "type"
let cellIdentifierKey = "cellIdentifier"
let additionalRowsKey = "additionalRows"
fileprivate var cellDescriptors: NSMutableArray!
fileprivate var cellsToDisplay: NSMutableArray!
func getCellDescriptors(_ type: String) -> NSMutableArray {
loadCellDescriptors(type)
return cellDescriptors;
}
func loadCellDescriptors(_ type: String) {
cellDescriptors = PlistManager.sharedInstance.getValueForKey(itemKey)! as! NSMutableArray
for i in 0..<(cellDescriptors[0] as AnyObject).count-1 {
let cellType = cellDescriptors[0][i][typeKey] as! String //creates error
if(cellType != type) {
cellDescriptors[0][i].setValue(false, forKey: isVisibleKey) //creates error
}
}
}
}
The reason you are getting this error is because the type of the object that is in your array is ambiguous to the compiler.
This is due to the fact that NSArrays aren't typed concretely.
NSArray is basically the Objective-C bridge of Array<Any> or [Any].
Let me walk you through your code...
let cellType = cellDescriptors[0][i][typeKey] as! String
The compiler knows that cellDescriptors is an NSArray as it is declared as one above. NSArrays can be subscripted to get the value at a given index, as you are doing with cellDescriptors[0]. The value this gives is of type Any, as explained above, so when you try and subscript it again with cellDescriptors[0][i], you are getting your error. As it happens, you can likely cast that Any object to an array, then you'll be able to perform the subscript, like so:
if let newArr = (cellDescriptors[0] as? [Any])[i] { }
However, this really isn't a nice approach and you end up dealing with a load of nasty optionals.
A better approach would be to concrete your declaration of cellDescriptors. I don't know how your type structure lies, but by the looks of things, it's an array of arrays of dictionaries (yuck). So in a raw form, your declaration should be var cellDescriptors = [[[AnyHashable:Any]]]() in order to subscript as you are now.
This said, the code you have in place is messy and I would consider changing the way you model your objects to make it more usable.
These lines have two problems:
fileprivate var cellDescriptors: NSMutableArray!
fileprivate var cellsToDisplay: NSMutableArray!
First, they're ! types (implicitly unwrapped optional or IUOs). They should never have been ! types, but in Swift 3 these work differently than they did in Swift 2, and that is likely breaking you. Get rid of the ! and assign these to an empty array to start. There are very few places that ! types are still useful (#IBOutlet is one of the last hold outs where ! types can be appropriate, at least as a matter of opinion.)
The other problem is that NSMutableArray is a sloppy type, and it creates a ton of headaches in Swift. It should only be used in very rare cases where a bridge to Objective-C requires it (this is exceedingly rare for NSMutableArray).
Convert cellDescriptors and cellsToDisplay to an appropriate Array type rather than NSMutableArray. "Appropriate" means "an array of whatever is actually in it." That means [Any] is not an appropriate type. If cellsToDisplay contains a bunch of Cell objects, then it should be [Cell].
NSMutableArray really doesn't tell much... Avoid force unwrapping and try to think of which types should the array be first. At first the array should have inside next array according to subscripting... so try to OverType it instead of NSMutableArray to type : [[Any]] but this option is really not safe at all!
Please provide more information about what the itemKey value should be... but overally my best tip is this:
func loadCellDescriptors(_ type: String) {
guard let cellDescriptors = PlistManager.sharedInstance.getValueForKey(itemKey)? as? [[String: Any]], let firstDescriptor = cellDescriptors[0]
else { return }
for someKeyInDict in 0..<cellDescriptor.count{
if let cellType = cellDescriptor[i][typeKey] as? String{
if(cellType != type) {
cellDescriptors[0][i].setValue(false, forKey: isVisibleKey)
}
}
}
This won't on 100% work, but will get you closer, I just tip that the value you are seraching is dictionary...
This should be pretty simple. I have a data source that always gives me UInt16s. I derive different data sets from this raw data and plot the results. Some of the derived data sets are Floats, some are UInt8s, and some are UInt16s.
I queue the derived data where it is later retrieved by my graphing classes.
Queues are arrays of arrays and look like this: [[UInt16]], [[Float]], or [[UInt8]].
I'm trying to make use of generics, but I get a compiler error when I try to append a generic-typed array to an array that is declared to be [[AnyObject]].
As I'm learning Swift, I keep bumping into this AnyObject / generic problem quite a bit. Any help/insight is appreciated.
class Base: NSObject {
var queue : [[AnyObject]] = Array()
func addtoQueue<T>(dataSet: [T]) {
queue.append(dataSet)
}
func removeFromQueue() -> [AnyObject]? {
return queue.removeAtIndex(0)
}
}
class DataSet1 : Base {
func getSomeData(rawData: [UInt16]) {
var result : [Float] = processRawData(rawData)
addToQueue(result)
}
}
It may be that you don't understand what AnyObject is. It is the protocol type automatically adopted by all classes. But Float, UInt16, and UInt8 are not classes; they are structs.
It may be that you meant [[Any]] as the type of your array. In that case, you don't need a generic. This works:
var queue : [[Any]] = Array()
func addToQueue(dataSet:[Any]) {
queue.append(dataSet)
}
let f = Float(1)
let i1 = UInt8(2)
let i2 = UInt16(3)
addToQueue([f])
addToQueue([i1])
addToQueue([i2])
If you insist on [[AnyObject]], you have two problems:
Your generic is too generic. Not everything in the universe is an AnyObject, so how can the compiler know that this thing will be an AnyObject? Write your generic like this:
func addToQueue<T:AnyObject>(dataSet:[T]) {
queue.append(dataSet)
}
Now the compiler knows that only something conforming to AnyObject will be used when calling this method. At that point, however, it is a little hard to see what your generic is for; you are not using T elsewhere, so just write a normal function:
func addToQueue(dataSet:[AnyObject]) {
queue.append(dataSet)
}
The second problem is that you will still have to convert (as Drew's answer tells you), because there is no automagic bridging between, say, a UIInt16 and an AnyObject. Float is _ObjectiveCBridgeable, but UInt16 and UInt8 are not.
var queue : [[AnyObject]] = Array()
func addToQueue(dataSet:[AnyObject]) {
queue.append(dataSet)
}
let f = Float(1)
let i1 = UInt8(2)
let i2 = UInt16(3)
addToQueue([f])
addToQueue([NSNumber(unsignedChar: i1)])
addToQueue([NSNumber(unsignedShort: i2)])
What you'll need to do is 'box' up the underlying value. You could accomplish this using a protocol which exposed a setter/getter and an enum property of the underlying type that you can 'switch' on. Alternatively, you could see if you can make Foundation's built in NSNumber work for you. It's doing exactly that: boxes up any number of numerical types for you to store and retrieve later on:
var queue : Array<NSNumber> = []
queue.append(NSNumber(int: 1))
queue.append(NSNumber(double: 2.5))
queue.append(NSNumber(float: 3.5))
var types : Array<String> = []
for item in queue{
println("number type: \(item)")
}
I'm reading through the Swift documentation, looking at the section regarding type casting.
The documentation talks about getting an array of type [AnyObject] from Foundation frameworks stuff (what would be an NSArray * in Objective-C).
First, the documentation provides this example:
for object in someObjects {
let movie = object as Movie
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
Now, I want to change the example slightly, to a case where I don't know all the objects are of type Movie, so I'd do this:
for object in someObject {
if let movie = object as? Movie {
println("Movie: '\(movie.name', dir. \(movie.director)")
}
}
The documentation then provides an example of a better way to write the first loop:
for movie in someObjects as [Movie] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
Where we downcast someObjects from an [AnyObject] to a [Movie] so we don't have to downcast within the loop.
And this got me thinking, can the array be option downcast as a whole?
if let someMovies = someObjects as? [Movie] {
for movie in someMovies {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
}
Does this work? And if so, how bad is this from a performance standpoint? How long would it take to check the type of every object in a 10,000 element array using the optional downcast?
I understand that the implications between this snippet and my previous optional downcast snippet are different. The first will iterate through every object and only attempt to print if the object is a Movie, where the second will only enter the loop if the array can be downcast to a [Movie] array, in which case it will either print all or none, but I can imagine there are situations where this would be preferable.
You've got it -- it works exactly like your example code:
let strings = ["Hi", "Hello", "Aloha"]
let anyObjects: [AnyObject] = strings
if let downcastStrings = anyObjects as? [String] {
println("It's a [String]")
}
// console says "It's a [String]"
No idea about performance, but I wouldn't assume that it will have to iterate through the full array to determine if a downcast is possible.
So I got curious, and ran a quick test with 100,000 simple values in a couple different [AnyObject] configurations, where I'm trying to downcast the array to a [String] vs. downcasting the individual elements:
// var anyObjects: [AnyObject] = [AnyObject]()
// filled with random assortment of Int, String, Double, Bool
Running test with mixed array
downcast array execution time = 0.000522
downcast elements execution time = 0.571749
// var actuallyStrings: [AnyObject] = [AnyObject]()
// filled with String values
Running test with all strings
downcast array execution time = 1.141267
downcast elements execution time = 0.853765
It looks like it's super fast to dismiss the mixed array as non-downcastable, since it just needs to scan until it finds a non-String element. For an array that it can downcast, it clearly has to crunch through the whole array, and takes much longer, although I'm not sure why it's not the same speed as looping through the array and manually checking each element.
Let's try this
var someObjects = [
NSString(),
NSUUID()
]
let uuids = someObjects as? NSUUID[]
uuids is nil
var someOtherObjects = [
NSUUID(),
NSUUID()
]
let all_uuids = someOtherObjects as? NSUUID[]
all_uuids is equal to someOtherObjects
So it looks like it does work. You can use the expression to test if all elements of the array are of the expected type but it will not filter the array to select only the expected type.