Reference member of class by string and/or enum - swift

Not sure if I'm asking this question properly or not. But basically I want to reference a member of a class by a string value and/or enum
For example I have the following.
var total:Int = 0
if locale.currencyCode == "JPY" {
for item in Cart.items {
total += item.fabric.price.JPY * item.quantity
}
}else{
for item in Cart.items {
total += item.fabric.price.USD * item.quantity
}
}
subtotalAmountLabel.text = "\(total.formatMoney())"
But, I'd like to write something like the following instead.
var total:Int = 0
for item in Cart.items {
total += item.fabric.price[locale.currencyCode]* item.quantity
}
subtotalAmountLabel.text = "\(total.formatMoney())"
Is there a possible way to do this?

You should use a dictionary instead.
// this should be the declaration of item.fabric.price
let prices = [
"JPY": <some price>,
"USD": <some price>,
// and so on
]
Then you can use a string to access it:
if let price = item.fabric.prices[locale.currencyCode] {
// ...
} else {
// If execution ever reaches here, that means there is no price available in that currency.
}

Related

Cannot assign to a property of an instance that get returned from a get-only subscript

I return an instance from a get-only subscript, but I cannot assign to a property of the returned instance
struct Student {
var id: String
var attendance = 0
var absence = 0
}
class StudentStore {
var students = [Student]()
subscript(id: String) -> Student? {
students.first(where: { $0.id == id } )
}
}
var studentStore = StudentStore()
var newStudent = Student(id: "2014901001")
studentStore.students.append(newStudent)
studentStore["2014901001"]?.attendance = 1 // error: Cannot assign to property: subscript is get-only
The subscription doesn't have a setter. What you have is the implicit getter which is a short form of
subscript(id: String) -> Student? {
get {
students.first(where: { $0.id == id } )
}
}
But it's impossible to update a value type item in an array with key subscription anyway.
An alternative is to add an update function to the store which takes the id, a keyPath and a value
func updateStudent<Value>(withID id: String, keyPath: WritableKeyPath<Student,Value>, value: Value) {
guard let index = students.firstIndex(where: {$0.id == id}) else { return }
students[index][keyPath: keyPath] = value
}
Getting the index is mandatory to be able to modify the item in the array directly.
Then you can replace
studentStore["2014901001"]?.attendance = 1
with
studentStore.updateStudent(withID: "2014901001", keyPath: \.attendance, value:1)
When you do
studentStore["2014901001"]?.attendance = 1
You are trying to set one of the Student objects in the students array, aren't you?
However, the subscript getter doesn't return (a reference to) one of the objects in the students array. Due to the value semantics of the Student struct, you return a copy of the Student object that is in the students array. You can't achieve what you want if you just set the attendance of that copy. Therefore, the Swift compiler compiles your code to something like this:
let copy = studentStore["2014901001"]
copy?.attendance = 1
studentStore["2014901001"] = copy
You can see that it sets the setter with the modified copy again. But your subscript does not have a setter, which is where the error comes from.
To solve this, you can make Student a class, which has reference semantics. This enables you to return the Student object that is in the students array, allowing the caller of the subscript to set their attendance.
class Student {
var id: String
var attendance = 0
var absence = 0
init(id: String, attendance: Int = 0, absence: Int = 0) {
self.id = id
self.attendance = attendance
self.absence = absence
}
}
Alternatively, you can add a setter. However, that would allow nonsense like this:
studentStore["1"] = Student(id: "2")
You need to decide what you want to happen here.
Error show exactly what you have to change. Because subcript will return something likes:
let updateStudent = studentStore["studentStore"]
You can not change with let
So update with this:
if var updateStudent = studentStore["studentStore"] {
updateStudent.attendance = 1
}

Listing all class attributes swift 3

I'm trying to print all the values from an object that inherits from a class, here is my example:
I create the class:
class Pokemon {
var name: String?
var type: String?
var level: Int?
var exp = 0.0
}
Create the object and assign some values:
var pikachu = Pokemon()
pikachu.name = "Pika Pika"
pikachu.level = 1
pikachu.type = "electricity"
pikachu.exp = 0
Now I would like to loop through all the pikachu object attributes and print the values. I'm thinking in a for each loop but I'm not sure how to implement it.
I know I can do something like this:
func printStats(pokemon: Pokemon) {
if pokemon.name != nil {
print(" name: \(pokemon.name!)\n level:\(pokemon.level!)\n type:\(pokemon.type!)\n exp: \(pokemon.exp!)")
}
}
printStats(pokemon: pikachu)
output:
name: Pika Pika
level:1
type:electricity
exp: 0.0
But I just want to loop through all values, instead of explicit writing every attribute in the function.
I found it the way of doing it:
let pokeMirror = Mirror(reflecting: pikachu)
let properties = pokeMirror.children
for property in properties {
print("\(property.label!) = \(property.value)")
}
output:
name = Optional("Pika Pika")
type = Optional("electricity")
level = Optional(1)
exp = Optional(0.0)
and if you want to remove the "Optional" just initialize the attributes.
Looks like a duplicate of Does Swift support reflection?
Alternatively, you can use a dictionary to store the attributes of Any? type.
e.g.
class Pokemon {
var attributes = [String:Any?]()
}
var pikachu = Pokemon()
pikachu.attributes["name"] = "Pika Pika"
pikachu.attributes["level"] = 1
pikachu.attributes["type"] = "electricity"
pikachu.attributes["exp"] = 0
func printStats(pokemon: Pokemon) {
pokemon.attributes.forEach { key, value in
if let value = value {
print("\(key): \(value)")
}
}
}
In Swift 5 you can create a new func in your class:
func debugLog() {
print(Mirror(reflecting: self).children.compactMap { "\($0.label ?? "Unknown Label"): \($0.value)" }.joined(separator: "\n"))
}
And then call it with MyObject().debugLog()
use Mirror API to get instance's properties
if you are developing iOS app, using NSObject, you may want to override description. Then can use print to print the instance.
A mirror describes the parts that make up a particular instance, such as the instance’s stored properties, collection or tuple elements, or its active enumeration case.
class YourClass: NSObject {
public override var description: String {
var des: String = "\(type(of: self)) :"
for child in Mirror(reflecting: self).children {
if let propName = child.label {
des += "\(propName): \(child.value) \n"
}
}
return des
}
}
let instance = YourClass()
print(instance)
see more in Reflection in Swift

Random values of Int in dictionary instead of zeros

I have an Avatar struct. This struct has properties called elements that hold several parts of the face like Eyes, Mouth and so on. In the init(withGender: AvatarGender) method. I want all these elements to have their default to zero.
But instead of having zeros I get huge random Ints. I can't figure out why!
struct Avatar {
var gender: AvatarGender
var skin: AvatarSkinColor
var elements: [AvatarElement: Int]
var rawString: String {
// a computed property
}
init(withGender: AvatarGender) {
gender = withGender
skin = .White
elements = [AvatarElement: Int]()
var allElementsType = [AvatarElement]()
if gender == .Man {
allElementsType = AvatarElement.allMaleValues
} else if gender == .Woman {
allElementsType = AvatarElement.allFemaleValues
}
for element in allElementsType {
elements[element] = 0 // <= This doesn't work !!!
}
}
init(fromRawString: String) {
// Another init method that works properly
}
}
Above AvatarGender, AvatarSkinColor and AvatarElement are enums.

Swift return bool in method

I've made this method:
func checkScore(player: Int) -> Bool {
var checkedFields: [Int] = []
var won: Bool = false
for var i = 0; i <= 9; i += 1 {
if(winningCombinations[i] == player) {
checkedFields.append(i)
}
}
for value in winningCombinations {
var hits = 0
for n in checkedFields {
if value.contains(n){
hits += 1
}
}
if hits == 3 {
won = true
}
}
return won
}
But when I try to build it everything becomes white and the build crashes. Am I doing something wrong here? I pass the value like this:
if self.checkScore(player) {
print("Won!")
}
(I see no error message!)
Your func checkScore(player: Int) accepts player, which is of type Int.
In your code you also say : if(winningCombinations[i] == player), meaning that you expect the elements in array winningCombinations to also be of type Int
But then you say
for value in winningCombinations {
var hits = 0
for n in checkedFields {
if value.contains(n){
If value is an element in winningCombination, it means that value is an int.. how can you say value.contains(n). Int cannot perform contains operation. Arrays can.

How to sort an array by boolean filed in swift?

I have a class defined like this:
class someClass {
var isCompleted = false
}
how to sort the list of the someClass? if want to move the completed items to the top of the list.
You can sort according to the boolean property by converting
the values to Int:
let arrayOfClasses = ... // array of classes
let sortedArrayOfClasses = sorted(arrayOfClasses) {
Int($0.isCompleted) > Int($1.isCompleted)
}
Or with "in-situ" sort:
var arrayOfClasses = ... // array of classes
sort(&arrayOfClasses) {
Int($0.isCompleted) > Int($1.isCompleted)
}
parameter closure for sort or sorted returns true iff the first parameter must be ordered before the second, otherwise false.
So you can:
let array = [ ... ]
let sortedArray = array.sorted { a, b in
a.isCompleted && !b.isCompleted
}
var yourArrayOfBooleans:[SomeClass] = ...
var sortedArrayOfBooleans:[SomeClass] = []
var tmpArray:[SomeClass] = []
for ar in yourArrayOfBooleans
{
if ar.isCompleted
{
sortedArrayOfBooleans.append(ar)
}
else
{
tmpArray.append(ar)
}
}
sortedArrayOfBooleans += tmpArray