Set class property inside an array in swift - arrays

I was wondering if there is a possibility to set a class property via an array:
class Test {
var x: Int = 0
var y: Int = 0
var z: Int = 0
}
let newTest = Test()
let myArray = [newTest.x, newTest.y, newTest.z]
So I created an Array with the properties of a class. Now I want to access the property object itself and set it to a value, kind of like this (I know this sets the array at index 0 to 1, but I hope the general idea is clear):
myArray[0] = 1 // I want to set newTest.x = 1
The following one of course works but I need the property objects themself in the array:
let myArray = [newTest, newTest, newTest]
myArray[0].x = 1
UPDATE
What I have now is an array with integers, but I need the reference to the class property, so I can set its value.
The Use Case:
I have an TextField as a user input. The user input should be splitted by a ','. And I don't know in advance, how many items I will get.
And therefore I thought I run a for loop, so I can set class properties based on the number of items.
newItem = Item()
var myClassPorperties = [newItem.cat1, newItem.cat2, newItem.cat3]
if let categoriesArray = categoriesTextField.text?.components(separatedBy: ",") {
for i in 0...categoriesArray.count - 1 {
myClassProperties[i] = categoriesArray[i]
}
}

It seems a bit strange to take a list of comma separated values and use them to update an object but given that here is one way to do it.
Create a function to map between an index (in the array) and a property in your class
func update(at index: Int, with value: Int) {
switch index {
case 0:
x = value
case 1:
y = value
case 2:
z = value
default:
print("Value \(value) at \(index) igonred")
}
}
and use it like this
categoriesArray.compactMap(Int.init).enumerated().forEach(newTest.update)
Another solution is to work with KeyPath
func keyPath(for index: Int) -> WritableKeyPath<Test, Int>? {
switch index {
case 0:
return \.x
case 1:
return \.y
case 2:
return \.z
default:
return nil
}
}
and then use the following code to update the object using your array
categoriesArray.compactMap(Int.init).enumerated().forEach {
if let keyPath = keyPath(for: $0.offset) {
newTest[keyPath: keyPath] = $0.element
}
}
I still think it would be better to have one input field in the UI for every property in your class so you can have a direct one-to-one connection between them.

Related

Problem on RealmSwift: "Invalid array input: more values (1) than properties (0)." while trying to persist an Array of String

I'm trying to persist in a table view cell, the result of a quiz test with questions and I needed the array of answers given (String Array) so I decided to use RealmSwift.
I created this class and of course I created also a RealmString object in the same file to handle the possibility to persist arrays of String in Realm in this way:
class RealmString: Object {
dynamic var stringValue = ""
}
class Test: Object {
#objc dynamic var ID = UUID().uuidString
#objc dynamic var testScore : String = String()
#objc dynamic var testTitle : String = String()
#objc dynamic var testSubTitle : String = String()
#objc dynamic var dateOfExecution: String = String()
#objc dynamic var answersGiven: [String] {
get {
return _backingAnswersGiven.map { $0.stringValue }
}
set {
_backingAnswersGiven.removeAll()
_backingAnswersGiven.append(objectsIn: (newValue.map({ RealmString(value: [$0]) })))
}
}
let _backingAnswersGiven = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["answersGiven"]
}
override static func primaryKey() -> String? {
return "ID"
}
Now in the view controller:
I have a variable that stores the result (is an Int array that will take ten answers with values from 0 to 5, and these will later be converted to String)
i.e.: [0,2,2,3,4,5,2,1,0,2] -> ["0","2","2","3","4","5","2","1","0","2"]
and when an option is selected in a question the value is set with this function, everything works fine.
public var questionResults: [Int] = []
func setValueToQuestion(questionNumber: Int) {
questionResults[questionNumber] = optionChosen
}
When the test is completed successfully everything is saved in this way:
let test = Test()
test.ID = currentTest?.ID ?? UUID().uuidString
test.testTitle = testTitleLabel.text!
test.testScore = resultNumberLabel.text!
test.testSubTitle = resultLabel.text!
test.dateOfExecution = dateTimeString
test.answersGiven = questionResults.map({String($0)})
DBManager.sharedInstance.addData(object: test)
I tried the code separately also adding breakpoints and everything works in the flow, expect this line:
test.answersGiven = questionResults.map({String($0)})
that raises the error shown in the title: "Invalid array input: more values (1) than properties (0)."
I guess it can be an error of mapping maybe?
This value is then treated in the rest of flow as a simple swift array of String = [String]
There are a few issues which may be leading to that error.
First the RealmString property is not persisted because it needs #objc
dynamic var stringValue = ""
should be
#objc dynamic var stringValue = ""
Secondly, and this is important, Realm does not support primitives in Lists. Well, it kinda does but not very well.
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
See my answer to this question but in a nutshell, you need another class to store the string in - kind of like your RealmString class.
class StringClass: Object {
#objc dynamic var myString = ""
}
and then change the Test object property to use the StringClass property
#objc dynamic var answersGiven = [StringClass]()
and then I see you're trying to use a backed var and computed property but I am not sure why. It may be simpler to use use the var itself
let _backingAnswersGiven = List<RealmString>()
since the List collection already handles what's being computed.
For example, if you set the list you can set it to another list (which wipes out the current list). Or when you get the list let myStringList = test._backingAnswersGiven, gets all of the StringClasses in the list without having to .map over them.

How to transfer new data to array using swift?

I have a tableView(purchases cart), which include magazines in multiple sections. In section, we can see cells with stuff from magazine. In the cell, I did display UILabel(price), UILabel(count) and UILabel with summary price (item * count) in one cell. Also, we can see two buttons (plus and minus), for changing count of item. Example -
var count: Float = Float(cell.countLabel.text!)!
guard Int(count) > 1 else { return }
var shortPrice = cell.newPrice.text
shortPrice?.removeLast(2)
let floatPrice = Float(shortPrice!)
count -= 1
let newSumShortPrice = floatPrice! * count
cell.countLabel.text = String(Int(count))
cell.summPrice.text = "\(newSumShortPrice) ₽"
But changes didn't work with an array.
The strcuct of my model -
struct ViewModel {
var name: String?
var offers: [Offers]?
}
struct Offers : Mappable {
var count : Int?
var fullPrice : String?
var shortPrice : String?
}
var purchasesViewModel = [PurchaseList.Fetch.ViewModel]()
I know, that I must pass changed data (count) to my array and use method tableView.reloadData(). But I can't, because I don't know how to do that.
How I can transfer new count value (check struct Offers) to array purchasesViewModel?
You can go to the index of the array and delete the particular data(old data) from purchasesViewModel and recreate new data and insert it on that index. I hope this will work.

Swift: Is it a good idea to use enum's raw value to access a UIButton and a string from a constant array?

I apologize in advance, this is hard to explain. I will provide more detail if needed.
This is the Constants struct that I use to reference UIButtons in a collection array and use as keys for dictionaries.
struct Constants {
static let scoreA = "score_a"
static let scoreB = "score_b"
static let scoreC = "score_c"
static let scoreD = "score_d"
static let constantsArray = [kScoreA, kScoreB, kScoreC, kScoreD]
enum Scores: Int, CaseIterable { case scoreA = 1, ScoreB, ScoreC, ScoreD}
}
My initial view controller has a lot of UIButtons. All the score UIButtons are tagged from 1 and up. The UIButtons are hooked to an IBOutlet UIButton array. This way I can avoid having too many IBOutlets
#IBOutlet var collectionOfScoreButtons: Array<UIButton>!
I reference the UIButtons using code like this throughout my App.
if let scoreAButton = collectionOfScoreButtons[Constants.Scores.scoreA.rawValue - 1]
The UIButtons order is the same as the enum's order e.g. scoreA is the first item in the enum and scoreA button is the first button in the array.
And I can retrieve a dictionary key like this, so I can update its value
// after pushing a score button
func handleScoreValue(tag: Int) {
let scoreKey = Constants.constantScoreArray[tag - 1]
dictionary[scoreKey, default: 0] += 1
}
I am not sure if there is a better way to handle this situation. The code works well, but I feel like there is a better way.
I can't see any advantage of using Scores enum to get reference for certain button, you have to specify index anyway
if let scoreAButton = collectionOfScoreButtons[0]
also you can make your Constants enum and implement CaseIterable protocol which allows you to make array of all enum's cases using Enum.allCases
enum Score: String, CaseIterable {
case A = "score_a"
case B = "score_b"
case C = "score_c"
case D = "score_d"
}
then I believe you have IBAction for your button so you can get index of sender in your array of buttons. Then you don't have to set tag of UIButton
#IBAction func buttonPressed(_ sender: UIButton) {
if let index = collectionOfScoreButtons.index(of: sender) {
handleScoreValue(index: index)
}
}
Finally you can get scoreKey as rawValue of case for certain index in allCases array
func handleScoreValue(index: Int) {
let scoreKey = Score.allCases[index].rawValue
dictionary[scoreKey, default: 0] += 1
}
Why not just use an enum directly ?
enum Constants: String, CaseIterable {
case scoreA = "score_a"
case scoreB = "score_b"
case scoreC = "score_c"
case scoreD = "score_d"
}
So you can loop through your enum cases like
Constants.allCases[anyIndex].rawValue

Retrieve key from Value Dictionary Array

I would to know how to get key if I have the values. Which class get higher marks?
let higherMarks = [
"ClassA": [10,20,30,40,50,60],
"ClassB": [15,25,35,45,55,65],
"ClassC": [18,28,38,48,58,68],
]
var largest = 0
var className = ""
for (classTypes, marks) in higherMarks {
for mark in marks {
if mark > largest {
largest = mark
}
}
}
print(largest)
What I'm saying in my comment is that you need to get the classTypes when you get the mark. Because when you get the higher mark, you want to also get the corresponding key value.
Keeping your code's logic I would do something like this:
let higherMarks = [
"ClassA": [10,20,30,40,50,60],
"ClassB": [15,25,35,45,55,65],
"ClassC": [18,28,38,48,58,68],
]
func findBestClass(in results: [String: [Int]]) -> (name: String, score: Int) {
var largest = 0
var type = ""
for (classType, marks) in results {
if let max = marks.max(), max > largest {
largest = max
type = classType
}
}
return (type, largest)
}
let best = findBestClass(in: higherMarks)
print("The best class is \(best.name) with a score of \(best.score).")
I just replaced your inner loop with .max() and changed the name of the key variable because it should not be plural. My method also returns a tuple because I find it relevant in this situation. But I didn't change your logic, so you can see what I meant by "also get the classTypes".

Access and modify easily Array of Dictionaries in Swift

I have this structure: [String: [String: Double]]()
Specifically, something like that: var dictionaries = ["GF": ["ET": 4.62, "EO": 21.0],"FD": ["EE": 80.95, "DE": 0.4]]
How can I easily access and modify nested dictionaries?
EXAMPLE UPDATED: I want to append "TT": 6 at FD and later I want to append another dictionary inside the array. At the end I'll print the results.
for (key,value) in dictionaries {
// if array contains FD, add the record to FD
if key.contains("FD") {
dictionaries["FD"]!["TT"] = 6
}
else {
// if array doesn't contain FD, add FD and add the record to it
dictionaries = dictionaries+["FD"]["TT"] = 6 // <-- I know that it's wrong but I want to achieve this result in this case.
}
}
Result of print will be:
GF -> ET - 4.62, EO - 21.0
FD -> EE - 80.95, DE - 0.4, TT - 6
MISSION: I need to append new dictionary records like in the example above, update existing ones in a simple and straightforward way, loop easily through records to read the values and print them out.
Can anyone help me? Never had the chance to manage dictionaries in Swift since now.
It is not clear what exactly you need but the exercise amused me, so I came up with this solution: we extend Dictionary so that it provides convenience methods if it is a nested dictionary.
First, because of Swift idiosyncrasies, we have to create a dummy protocol to "mark" Dictionary¹:
protocol DictionaryProtocol {
associatedtype Key: Hashable
associatedtype Value
subscript(key: Key) -> Value? { get set }
var keys: LazyMapCollection<[Key : Value], Key> { get }
}
extension Dictionary: DictionaryProtocol {}
Basically, just copy-paste² all declarations you need later from Dictionary to DictionaryProtocol.
Then, you can happily extend away. For instance, add a two-parameter subscript:
extension Dictionary where Value: DictionaryProtocol {
typealias K1 = Key
typealias K2 = Value.Key
typealias V = Value.Value
subscript(k1: K1, k2: K2) -> V? {
get {
return self[k1]?[k2]
}
set {
if self[k1] == nil {
self.updateValue([K2: V]() as! Value, forKey: k1)
}
self[k1]![k2] = newValue
}
}
}
Or an alternative pretty-print³:
extension Dictionary where Value: DictionaryProtocol {
func pretty() -> String {
return self.keys.map { k1 in
let row = self[k1]!.keys.map { k2 in
return "\(k2) - \(self[k1]![k2]!)"
}.joined(separator: ", ")
return "\(k1) -> \(row)"
}.joined(separator: "\n")
}
}
You can also create a type alias for this special dictionary:
typealias D2Dictionary<K: Hashable, V> = Dictionary<K, Dictionary<K, V>>
Going back to the example in your question:
var dictionary = D2Dictionary<String, Double>()
dictionary["GF", "ET"] = 4.62
dictionary["GF", "EO"] = 21.0
dictionary["FD", "EE"] = 80.95
dictionary["FD", "DE"] = 0.4
dictionary["FD", "TT"] = 6
print(dictionary.pretty())
// > GF -> ET - 4.62, EO - 21.0
// > FD -> EE - 80.95, DE - 0.4, TT - 6.0
Background: Only protocols can be used in type bounds on extension conditions.
Make sure to get the types right. If we write var keys: [Key] { get }, for instance, the compiler dies with a seg fault.
Unfortunately, extension Dictionary: CustomStringConvertible where Value: DictionaryProtocol { ... } is not allowed, for whatever reason.
It'll be much simpler if you have the key "FD" you can use
dictionaries["FD"]!["TT"] = 6
to add a new value ["TT":6] or modify existing value of TT to 6. Note that the ! between ["FD"]!["TT"] assumes that ["FD"] exists regardless. You need to check if ["FD"] exists otherwise. Like:
if dictionaries["FD"] != nil {
dictionaries["FD"]!["TT"] = 6
} else {
dictionaries["FD"] = ["TT" : 6]
}
If you need to look for the key you will have to run the entire dictionary like you already tried, but dictionaries support fast enumeration for key and values like
for (key,value) in dictionaries {
if key.contains("FD") { //or any other checks you need to identify your key
value["TT"] = 6
}
}

Resources