How to pass an object property as a parameter in SwiftUI? - swift

I know the below is possible in Javascript. Is there anything similar I can use in SwiftUI to pass an object property as a String?
var a = { foo: 123 };
a.foo // 123
a['foo'] // 123
var str = 'foo';
a[str] // 123

What you likely want here is a key path. For example, given:
struct A {
var foo: Int
}
You can construct an A, and access it:
let a = A(foo: 123)
a.foo // 123
And given that, you can access the foo property by key path:
let kp = \A.foo
a[keyPath: kp] // 123
If your actual goal is to map strings to integer, that's just a [String: Int], and it would work identically. If you mean "I want to pass strings to objects has have fairly random things happen, and possibly crash like they do in JavaScript," then that's also possible, using a custom subscript like subscript(key: String) -> Int?.

Related

Trying to understand how constructors and different parameter types work inside classes in Swift

New to Swift but have some previous knowledge about C# and Java programming. Trying to understand how "Constructors" and parameter types work in Swift when creating and instantiating a class.
My problem is below:
public class MyClass {
private var somethingA : String
private var somethingB : String
private var somethingC : Int
private var complexes:[String:[String:Int]] = [String:[String:Int]]();
init() {
self.somethingA = "";
self.somethingB = "";
self.somethingC = 0;
self.complexes = [somethingA:[somethingB:somethingC]];
}
public func addSomething(somethingAA : String) {
self.somethingA = somethingAA;
}
public func addComplex(somethingAA: String, complex:(somethingBB: String, somethingCC: Int)) {
self.somethingA = somethingAA;
// How do I assign the complex:(somethingBB, somethingCC) parameter to my self variable 'complexes'?
}
}
When I tried doing it like the following, I get the following errors for each line:
self.somethingB = somethingBB; // Use of unresolved identifier 'somethingBB'
self.somethingC = somethingCC; // Use of unresolved identifier 'somethingCC'
self.complexes = [somethingAA:[somethingBB:somethingCC]]; //use of unresolved identifier 'somethingBB' and 'somethingCC'
the function addComplex(somethingAA: String, complex:(somethingBB: String, somethingCC: Int)) takes a tuple of name complex which consist of String and Int. To assign values from tuple to your properties, you need to use tupleName.propertyName kind of pattern, using this your addAddComplex function becomes
public func addComplex(somethingAA: String, complex:(somethingBB: String, somethingCC: Int)) {
somethingA = somethingAA
somethingB = complex.somethingBB
somethingC = complex.somethingCC
//create a dict with somethingB and somethingC values
complexes = [somethingAA: [somethingB: somethingC]]
print(complexes)
}
Now if you call function addComplex like below which will print ["AAA": ["BBB": 10]]
let myClass = MyClass()
myClass.addSomething(somethingAA: "Hellow")
myClass.addComplex(somethingAA: "AAA", complex: (somethingBB: "BBB", somethingCC: 10))
For error inside function addComplex, parameter complex is tuple, which contains two value named somethingBB of type String and somethingCC of typle Int.
So to access value from tuple you have to access it with dot notation.
self.somethingB = complex.somethingBB
self.somethingC = complex.somethingCC
or you can access parameters from tuple using below syntax also :
self.somethingB = complex.0
self.somethingC = complex.1
To assign value to complexes is type of Dictionary of key-value pair. Here key is type of String and value is another Dictionary of [String:Int]. So you have to construct same structure using raw values from function parameters inside function like below.
self.complexes = [somethingAA:[somethingBB:somethingCC]]
Try this code:
public func addComplex(somethingAA: String, complex:(somethingBB: String, somethingCC: Int)) {
self.somethingA = somethingAA;
self.somethingB = complex.somethingBB
self.somethingC = complex.somethingCC
self.complexes = [somethingAA:[somethingBB:somethingCC]]
}
Tuples are one of Swift's less visible language features. They occupy a small space between Structs and Arrays. In addition, there's no comparable construct in Objective-C (or many other languages).
A tuple can combine different types into one. Tuples are value types
and even though they look like sequences they aren't sequences, as
there's no direct way of looping over the contents.
There are different types of tuples:
// A simple tuple
let tuple1 = (2, 3)
let tuple2 = ("a", "b", "c")
// A named tuple
let tuple3 = (x: 5, y: 3)
// Different types of contents
let tuple4 = (name: "Carl", age: 78, friends: ["Bonny", "Houdon", "Miki"])
The ways in which you can access tuple elements are:
// Accessing tuple elements
let tuple5 = (13, 21)
tuple5.0 // 13
tuple5.1 // 21
let tuple6 = (x: 21, y: 33)
tuple6.x // 21
tuple6.y // 33
Now coming to your question the comlpex parameter is a named tuple In which
complex:(somethingBB: String, somethingCC: Int)
the first parameter in the tuple is somethingBB type of string and somethingCC type of Int.
You can simply access somethingBB and somethingCC by doing
self.somethingB = complex.somethingBB
self.somethingC = complex.somethingCC
or
self.somethingB = complex.0
self.somethingC = complex.1
It is unclear how you want to add the tuple to the complexes property. Do you want to add a new entry in the dictionary? Do you want to replace the existing dictionary with the parameter values?
You probably have a misunderstanding of how tuples work. This parameter:
complex:(somethingBB: String, somethingCC: Int)
Is a parameter called complex, with the type of (somethingBB: String, somethingCC: Int). There is not a single parameter called somethingBB or somethingCC. There is, however, complex.somethingBB and complex.somethingCC. Think of tuples in Swift like System.Tuple in C#, but better. That way, you wouldn't mistake somethingBB and somethingCC as parameters when they are just members of a tuple type.
Now, you should know how to add the tuple into the dictionary. If you want a new entry:
complexes[somethingAA] = [complex.somethingBB: complex.somethingCC]
If you want to replace the existing entry:
complexes = [somethingAA : [complex.somethingBB : complex.somethingCC]]
In production code, you should probably not use complex types like [String: [String: Int]], create a struct or class for it.
EDIT:
If you want to append an entry to the inner dictionary, you need to check whether there is a value associated with the key somethingAA first:
if complexes[somethingAA] == nil {
complexes[somethingAA] = [somethingBB: somethingCC]
} else {
complexes[somethingAA][somethingBB] = somethingCC
}

Whether to use var or let during instances creation in Swift?

What should be used to create the instances of classes in Swift and why?
please explain the usage of let and var during the instances creation in Swift
below is the code :-
class ConstantTest{
let constant: String
init(constant: String) {
self.constant = constant
}
func printConstant() {
print(constant)
}
}
let constanttest = ConstantTest(constant: "Hello")
constanttest.printConstant()
var test = ConstantTest(constant: "Hie")
test.printConstant()
Use let if:
it is a constant (or in the case of reference types like class, if the reference cannot be replaced with another reference); and
you're able to set it during the initialization process.
But if you need to be able to change it at a later point, use var, such as true variables whose values are not constant (or in the case of reference types, if you need to replace it with another instance). But variables and properties whose values are not set during the initialization process, you have to use var. For example, lazy stored properties use var because their value is not set when the initialization process completes, but only when you first reference it. Another example includes view controllers' references to their outlets that are hooked up only when the view hierarchy is created at a later point.
Needless to say, computed properties use var, too.
But, if you can use let, you should do so, as it's easier to write safe code if you know what is a constant and what is a variable. In your example, you'd use let.
Note: In the case of reference types, like class types, let does not mean that that the object itself is necessarily immutable, merely that you cannot replace it with another instance. If you want to enjoy control over whether it's immutable or not, consider using a value type (e.g. a struct).
Let me see if I can make that final note more clear. Consider:
class Foo {
var value: String
init(value: String) {
self.value = value
}
}
Then the following is permitted:
let obj = Foo(value: "a")
obj.value = "b" // changing mutable property of reference type, Foo, is permitted
But the following is not:
let obj = Foo(value: "a")
obj = Foo(value: "b") // replacing `obj` with a new instance of `Foo`, is not
If you don't want to be able to change value property, you can define value to be immutable (or at least, not publicly mutable), e.g.:
class Foo {
let value: String // or `private(set) var value: String`
init(value: String) {
self.value = value
}
}
Or don't define Foo as class (a reference type) and instead define it to be a struct (a value type):
struct Foo {
var value: String
init(value: String) {
self.value = value
}
}
let obj = Foo(value: "a")
obj.value = "b" // this is not permitted, because Foo value-type, `obj`, was declared with `let`, making it immutable
Note, that final example, declaring Foo as a value type (a struct) does change it fairly fundamentally, e.g.
var obj1 = Foo(value: "a")
var obj2 = obj1 // this value is a copy of `obj1` not a reference to the same object that `obj1` pointed to
obj1.value = "b"
print("\(obj1.value)") // will print "b"
print("\(obj2.value)") // will print "a"
But value types, while it requires a slightly different mindset to use them, are incredibly useful for easily writing safe code. See WWDC 2015 Building Better Apps with Value Types in Swift.
There are varying reasons to use each. The simplest way to explain this is that let is for defining constants, while var is for defining variables. When using let the value cannot be changed. So in your application if you need a value that can be changed, use var. As well you might think of searching for your answer, as there are many duplicates of this question.
Source: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html

Swift: Cast array of objects to array of sub type

Say I have an array of Animals and I'd like to cast it to an array of Cats. Here, Animal is a protocol that Cat adopts. I'd like something like let cats: [Cat] = animals as! [Cat] but this seg faults in compilation (btw I'm on both Linux Swift 3 and Mac Swift 2.2). My workaround is to just create a function that downcasts each item individually and adds it to a new array (see small example below). It produces the desired result, but isn't as clean as I'd like.
My questions are:
is this totally dumb and I'm just missing an easier way to do this?
how can I pass a type as the target parameter in the function below, rather than passing an instance? (e.g. I'd like to pass Cat.self rather than Cat(id:0) but doing so causes an error saying cannot convert Cat.Type to expected argument type Cat)
Here's what I have so far:
protocol Animal: CustomStringConvertible
{
var species: String {get set}
var id: Int {get set}
}
extension Animal
{
var description: String
{
return "\(self.species):\(self.id)"
}
}
class Cat: Animal
{
var species = "felis catus"
var id: Int
init(id: Int)
{
self.id = id
}
}
func convertArray<T, U>(_ array: [T], _ target: U) -> [U]
{
var newArray = [U]()
for element in array
{
guard let newElement = element as? U else
{
print("downcast failed!")
return []
}
newArray.append(newElement)
}
return newArray
}
let animals: [Animal] = [Cat(id:1),Cat(id:2),Cat(id:3)]
print(animals)
print(animals.dynamicType)
// ERROR: cannot convert value of type '[Animal]' to specified type '[Cat]'
// let cats: [Cat] = animals
// ERROR: seg fault
// let cats: [Cat] = animals as! [Cat]
let cats: [Cat] = convertArray(animals, Cat(id:0))
print(cats)
print(cats.dynamicType)
Am I missing an easier way to do this?
You can use map to make the conversion:
let cats: [Cat] = animals.map { $0 as! Cat }
how can I pass a type as the target parameter in the function below, rather than passing an instance?
First, you need to remove the instance parameter:
func convertArray<T, U>(array: [T]) -> [U] {
var newArray = [U]()
for element in array {
guard let newElement = element as? U else {
print("downcast failed!")
return []
}
newArray.append(newElement)
}
return newArray
}
Since you cannot specify type parameters explicitly, you need to provide the compiler with some info to deduce the type of U. In this case, all you need to do is to say that you are assigning the result to an array of Cat:
let cats: [Cat] = convertArray(animals)
As of Swift 4.1 using compactMap would be the preferred way, assuming you don't want the method to completely fail (and actually crash) when you have any other Animal (for example a Dog) in your array.
let animals: [Animal] = [Cat(id:1),Dog(id:2),Cat(id:3)]
let cats: [Cat] = animals.compactMap { $0 as? Cat }
Because compactMap will purge any nil values, you will end up with an array like so:
[Cat(1), Cat(3)]
As a bonus, you will also get some performance improvement as compared to using a for loop with append (since the memory space is not preallocated; with map it automatically is).

"Deep-copy" of Swift dictionary with map()?

I have a GKGameModel that stores its internal state in an array a of Cards and a dictionary b that maps from Ints to arrays of Cards. GameplayKit mandates that I must copy this internal state in setGameModel:.
The following code is meant to just-copy the array and "deep-copy" the dictionary. FWIK this should be sufficient since Cards themselves never change.
var a: [Card]
var b: [Int: [Card]]
func setGameModel(gameModel: GKGameModel) {
let otherGameModel = gameModel as! GameModel
a = otherGameModel.a
b = otherGameModel.b.map { (i: Int, cards: [Card]) in (i, cards) }
}
However, this causes the following syntax error in the line that attempt the "deep-copy":
Cannot assign a value of type '[(Int, [Card])]' to a value of type
'[Int, [Card]]'.
What am I doing wrong?
In your case:
b = otherGameModel.b
is sufficient.
Because, Array and Dictionary are both value types. So when it is assigned to another variable, it will be deep copied.
var bOrig: [Int: [Int]] = [1: [1,2,3], 2:[2,3,4]]
var bCopy = bOrig
bCopy[1]![2] = 30
bOrig[1]![2] // -> 3
bCopy[1]![2] // -> 30
The error message reveals there is a type mismatch:
variable b is declared as Dictionary<Int,[Card]> but the map function returns an Array of tuplets (Int, [Card])

How to only initialise some of the properties in a Swift class

It's known that Swift compiler forces all the properties to be initialised in a constructer. However, sometimes I only want to have some of the properties to be initialised. Below is an example, I only have to use variable b in the second constructer and no need for the variable a but the complier is complaining "a is not initialised". I wonder how to deal with this case.
class aClass{
var a:String
var b:String
init(a:String){
self.a = "a"
}
init(a:String, b:String){
self.a = "a"
self.b = "b"
}
}
This is what optionals are for. your declaration of property b can change to var b: String?. This will make it so you don't have to give it a value in init. Then, when you want to use it, if let self.b = "my string" {...} or, if you know it has a value, you can force unwrap it: var myOtherString = b!.