How to check if a value is nil or not from a Set<T> - swift

I have a Set and now I'm trying to get an object in a index position:
var testItem: ExpandableCell
if let object = expandableCells[indexPath.row] {
testItem = object as! ExpandableCell
}
But I get an error.
UPDATE:
I just convert from SET to Array my collection class variable and now I have this:
let realItem : ExpandableCell?
if let testItem: AnyObject = expandableCells[indexPath.row] {
realItem = testItem as! ExpandableCell
print(realItem?.title)
}
It compiles without a problem but when i run the code and the index its outside of its boundaries then I get the following error:
So seems that my if let statement is not working.
Any clue?

Set is an unordered collection of unique objects. Because they're unordered, getting them by an Int index makes no sense.
Perhaps you meant to use an Array, an ordered collection of objects. Array doesn't guarantee the uniqueness of its objects, but it does preserver ordering, thus Int indexes make sense.

you can use loop Instead if/let condition
for testItem in expandableCells {
if let realItem = testItem as? ExpandableCell {
print(realItem?.title)
}
}

Related

Append Values in NSSet To An Array of Type [String]

I am attempting to get the values from an NSSet in core data and append those values to an array of type String.
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
recipeStep.append(String(i))
}
}
}
entity.steps is the list of steps tied to a core data entity. This is an NSSet. I am trying to copy those values to an array of type String.
#State var recipeStep: [String]
When trying to do this in my for in loop, I receive the following error: No exact matches in call to initializer
If I remove the conversion of "I" to String, I receive the following error:
Cannot convert value of type NSSet.Element (aka Any) to expected argument type String
Any idea on how to get this to work?
NSSet is defined in Objective C, which didn't have generics. It's an untyped collection, so you don't statically know anything about its elements.
As you've noticed, your i variable isn't a String, it's an Any.
You're confusing type coercion ("casting") with type conversion. If i were a Double, you could call String(i) to invoke an initializer which takes a double, and processes into a String.
You tried something similar by calling String(i), where you're making the Swift compiler find an initializer on String with the signitiure init(_: Any).
There is no such initializer. And besides, that's not what you want. You don't want to create a new String from a different kind of value. You already have a string, it's just "hidden" behind an Any reference.
What you're looking for is to do a down-cast, from Any to String:
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
guard let step = i as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
recipeStep.append(i as String)
}
}
}
I'll warn you though, there are several issues/blemishes with this code:
You're using a for loop to do what is ultimately just a mapping operation
Your computation doesn't return its ouput, and instead indirectly achieves its goal through a side-effect on recipeStep
Your computation doesn't take a its input as a parameter, and instead indirectly achieves its goal through a side-effect on entity
i is conventionally expected to be an integer index of a for loop iterating over a sequence of numbers. Here it's an Any (a String at runtime)
Here's what I would suggest instead:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
guard let steps = entity.steps else { return [] }
return steps.map { step in
guard let stringStep = step as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
return step
}
}
Then in the rest of your code (and your tests), you can write self.recipeSteps = getRecipeSteps(from: myEntity). Elegant!
If you're certain that these entity.steps values can only ever be strings, then you can boil this down to a single map with a force-cast:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
entity.steps?.map { $0 as! String } ?? []
}
Just convert directly:
let set = Set(["1", "2", "3"])
let array = Array(set)
DDLog(set)//Set<String>)
DDLog(array)//[String]

Cast Array<Any> to Array<Int> in Swift

I have array of Any objects called timestamps, in which first object is string, and rest objects are Int. What i want is, to drop first element and treat rest of array as Array of Int objects..
I tried:
let timestamps = xAxisData.dropFirst()
if let timestamps = timestamps as? Array<Int> {
}
It compile, but it says - Cast from 'ArraySlice<Any>' to unrelated type 'Array<Int>' always fails
I could possibly iterate through array and create new temporary array with check every element whether it Int or not, but i think there is a better cleaner way?
You need to create an Array from the ArraySlice created by dropFirst.
let timestamps = Array(xAxisData.dropFirst())
if let timestamps = timestamps as? [Int] {
}
I like to use something like a flatMap so no matter were the non-Int values are in the array they are not consider for the resulting array.
let originalArray:[Any] = ["No int", 2, 3, "Also not int", 666, 423]
let intArray:[Int] = originalArray.flatMap { $0 as? Int }
print(intArray)
Will print this:
[2, 3, 666, 423]
So in your case you could do something like:
let timestamps = xAxisData.flatMap { $0 as? Int }
As stated by Steve Madsen in the comment below, if you are using Swift 4.1 or newer Apple suggest to used compactMap for getting rid of optional nils so that specific use of flatMap is deprecated.
let timestamps = xAxisData.compactMap { $0 as? Int }
Here is another method to convert a subset of an array with mixed types to something more specific:
let intArray = anyArray.compactMap { $0 as? Int }

Value of type _ cannot convert to specified type Set<> Swift

I'm trying to create a set of random exercises. I have made my struct Hashable and Equatable following the tutorial here https://medium.com/#JoyceMatos/hashable-protocols-in-swift-baf0cabeaebd and that is working fine so it's ready to be put in a Set<>.
When I use an Array to collect the workout exercises, as per below, it works fine. But when I switch to a Set<> I get an error "cannot convert value of type [_] to specified type 'Set'. What is it about 'Sets' that mean you can't map in the same way as an Array?
func generateWorkout() {
let allPossibleExercises = masterExerciseArray
let numberOfExercisesKey = Int(arc4random_uniform(4)+3)
//error triggers on the line below if I switch [WorkoutExercise]
//for Set<WorkoutExercise> (which conforms to Hashable/Equatable
let workoutSet : [WorkoutExercise] = (1...numberOfExercisesKey).map { _ in
let randomKey = Int(arc4random_uniform(UInt32(allPossibleExercises.count)))
return WorkoutExerciseGenerator( name: allPossibleExercises[randomKey].name,
maxReps: allPossibleExercises[randomKey].maxReps).generate()
}
print (workoutSet)
}
There is an answer here with a similar error message Cannot convert value of type '[_]' to specified type 'Array' but my array wouldn't be empty as in this example so I don't think this is the same root cause?
UPDATE : for anyone having the same problem, you can use Array but then simply convert the Array to a Set afterwards if the correct elements are Hashable/Equatable
If creating the array works create the array and then make the Set from the array. If all involved objects conform to Hashable this is supposed to work.
func generateWorkout() {
let allPossibleExercises = masterExerciseArray
let numberOfExercisesKey = Int(arc4random_uniform(4)+3)
let workoutArray : [WorkoutExercise] = (1...numberOfExercisesKey).map { _ in
let randomKey = Int(arc4random_uniform(UInt32(allPossibleExercises.count)))
return WorkoutExerciseGenerator( name: allPossibleExercises[randomKey].name,
maxReps: allPossibleExercises[randomKey].maxReps).generate()
}
let workoutSet = Set(workoutArray)
print (workoutSet)
}

Check if variable is computed or stored

In my app I translate objects from custom classes into dictionaries so that they can be saved locally in a plist as well as on a server. I use the following to turn the properties of a class into a dictionary:
func dictionary() -> [String : Any] {
var count: UInt32 = 0;
let myClass: AnyClass = self.classForCoder;
let properties = class_copyPropertyList(myClass, &count);
var dictionaryRepresentation: [String:Any] = [:]
for i in 0..<count {
let property = properties![Int(i)]
let cStringKey = property_getName(property);
let key = String(cString: cStringKey!)
dictionaryRepresentation[key] = self.value(forKey: key) as Any
}
return dictionaryRepresentation
}
I have a problem, however, with computed properties. It seems that those are computed and the returned value gets put into the dictionary as well, which I would like to avoid. So here is my question:
Is it possible to check whether is a property computed programatically using only its name?
I am assuming this could be possible by trying to assign a value to it which would give me an error or some similar approach.
Here is what seems to be a working solution, based on suggestion by dasblinkenlight.
Rather than using the Objective-C method outlined above, create a Mirror of the class which has a children made up of all settable properties, therefore excluding computables.
Used like this:
let mirror = Mirror(reflecting: MyObject)
for case let (label?, value) in mirror.children {
print (label, value)
}
Here label is the name of the variable and value is obviously the value.
EDIT: In case anyone wants to convert objects into dictionary, I am posting the full code here as well. Do however remember that if values are custom objects as well, those will need to be converted too.
func dictionary() -> [String:Any] {
let mirror = Mirror(reflecting: self)
var dictionaryRepresentation = [String:Any]()
for case let (label, value) in mirror.children {
guard let key = label else { continue }
dictionaryRepresentation[key] = value
}
return dictionaryRepresentation
}
You can try property_copyAttributeList(_:_:) function, it may contain a read-only marker for swift's computed properties. Although I guess let properties also will have that marker, so you must find a way to differ them.

Swift Dictionary access value through subscript throws Error

Accessing a Swift Dictionary value through subscript syntax is gives error Ambiguous reference to member 'subscript'
Here is the code
class Model {
struct Keys {
static let type :String = "type" //RowType
static let details :String = "details"
}
var type :RowType = .None
var details :[Detail] = []
init(with dictionary:Dictionary<String, Any>) {
if let type = dictionary[Keys.type] as? String {
self.type = self.rowTypeFromString(type: type)
}
if let detailsObj = dictionary[Keys.details] as? Array { //Error : Ambiguous reference to member 'subscript'
}
}
}
if i remove the type casting as? Array at the end of optional-binding it compiles fine
I am expecting the value of details key to be an Array, I know that i can use [String,Any] instead of Dictionary<Key, Value>, What is causing the issue ?
Array doesn't work like NSArray, you explicitly need to tell what type your array is storing.
If you want to store Detail objects in it, the proper syntax is Array<Detail> or simply [Detail]
if let detailsObj = dictionary[Keys.details] as? Array<Detail> {
}
Issue is solved by explicitly specifying the Type of objects the array contains, In my case it was Array<[String:Any]>
if let detailsObj = dictionary[Keys.details] as? Array<[String:Any]> { //we should also specify what type is present inside Array
}
Credits: #Hamish, #Dávid Pásztor
Thanks!