How can I refer to properties from a struct within a struct/ - swift

I'm trying to get the hang of how to make my code the most efficient using Structs / Classes, and I'm trying to understand it via the following example.
I'd be really grateful if someone could correct me or guide me about the same:
Just as an example, I'll use Harry Potter. There are four houses, and each house has certain characteristics.
So now I have a struct for 2 of them:
struct Gryffindor {
let name = "Gryffindor"
let characteristic = "Brave"
let image = Image("Lion")
}
struct Slytherin {
let name = "Slytherin"
let characteristic = "Cunning"
let image = Image("Snake")
}
Now if I wish to have a wizard struct as follows, but I don't know how to include a House property within, such that when I try to create an instance of a wizard, I can call the properties from their respective houses.
struct Wizard {
let name: String
var house: ?
}
let harryPotter = Wizard(name: "Harry", house: Gryffindor)
Basically, I wish to be able to refer to harry's house using the harryPotter instance, as such:
print(harryPotter.characteristic) //should print "Brave"
Is what I'm trying to achieve even possible?

First of all you are mixing types with objects so you should have a type House
struct House {
let name: String
let characteristic: String
let image: Image
}
And then use that in the Wizard struct
struct Wizard {
let name: String
var house: House
}
And now you create first a House object for the Wizard and then the Wizard object
let gryffindor = House(name: "Gryffindor", characteristic: "Brave", image: Image("Lion"))
let harryPotter = Wizard(name: "Harry", house: gryffindor)
or all in one call
let harryPotter = Wizard(name: "Harry",
house: House(name: "Gryffindor", characteristic: "Brave", image: Image("Lion")))

Use protocol & generics, like below. Tested with Xcode 11.4.
protocol House {
var name: String { get }
var characteristic: String { get }
var image: Image { get }
}
struct Gryffindor: House {
let name = "Gryffindor"
let characteristic = "Brave"
let image = Image("Lion")
}
struct Wizard<H: House> {
let name: String
var house: H
}
let harryPotter = Wizard(name: "Harry", house: Gryffindor())

Related

Cannot convert value of type '<Int>' to expected element type <Any>

I'm trying to learn swift, but I have a problem where using <Object> in Java would fix my problem I think, and the Apple doc says I should use <Any> but I keep getting errors.
I'm trying to build a memorize card game, I have the following models:
Theme.swift <- In charge of modeling different kind of themes for the cards, the idea is that the cards could have numbers, images etc, thats why it has a generic type after the name
import Foundation
import UIKit
struct Theme<Type> {
internal init(name: String, emojis: [Type], numberOfPairs: Int, cardsColor: UIColor) {
self.name = name
self.emojis = emojis
if(numberOfPairs > emojis.count || numberOfPairs < emojis.count) {
fatalError("Index out of bounds")
}
self.numberOfPairs = numberOfPairs
self.cardsColor = cardsColor
}
var name: String
var emojis: [Type]
var numberOfPairs: Int
var cardsColor: UIColor
}
I also have a Game model in charge of the game logic and cards model, I still have to implement a lot of stuff, but here's the code
import Foundation
struct Game {
var themes: [Theme<Any>]
var cards: [Card<Any>]
var score = 0
var isGameOver = false
var choosenTheme: Theme<Any>
init(themes: [Theme<Any>]) {
self.themes = themes
self.choosenTheme = self.themes.randomElement()!
cards = []
for index in 0..\<choosenTheme.numberOfPairs {
cards.append(Card(id: index*2, content: choosenTheme.emojis[index]))
cards.append(Card(id: index*2+1, content: choosenTheme.emojis[index]))
}
}
mutating func endGame() {
isGameOver = true
}
mutating func penalizePoints() {
score -= 1
}
mutating func awardPoints () {
score += 2
}
struct Card<T>: Identifiable {
var id: Int
var isFaceUP: Bool = false
var content: T
var isMatchedUP: Bool = false
var isPreviouslySeen = false
}
}
As you can notice I've used the Any type for creating an array of Cards and themes, cuz they could have strings, numbers or images
In my ViewModel I have the following code, where I'm trying to fill the Array of themes with two themes, one of string type of content, and the other one of Int:
import Foundation
import SwiftUI
class GameViewModel {
static let halloweenTheme = Theme<Int>(name: "WeirdNumbers", emojis: [1, 2, 4, 9, 20, 30], numberOfPairs: 6, cardsColor: .darkGray)
static let emojisTheme = Theme<String>(name: "Faces", emojis: ["๐Ÿฅฐ", "๐Ÿ˜„", "๐Ÿ˜œ", "๐Ÿฅณ", "๐Ÿค“", "๐Ÿ˜Ž", "๐Ÿ˜‹", "๐Ÿคฉ"], numberOfPairs: 5, cardsColor: .blue)
var gameController: Game = Game(themes: [halloweenTheme, emojisTheme])
}
But I keep getting this or a similar error:
Cannot convert value of type 'Theme<Int>' to expected element type
'Array<Theme<Any>>.ArrayLiteralElement' (aka 'Theme<Any>')
Cannot convert value of type 'Theme<String>' to expected element type
'Array<Theme<Any>>.ArrayLiteralElement' (aka 'Theme<Any>')
And my mind is going crazy, I thought that by using [Theme<Any>] I would be able to have an array like this: [Theme<String>, Theme<Int>, Theme<Image>, ...] but it looks like not
Does anybody have a clue of what is going on here?
You can use a wrapper struct, basic example below. Note: if you need conformance to Codable, you need to implement encode/decode on your own.
struct Values<A> {
let value: A
}
struct Wrapper {
let wrappedValue: Values<Any>
}
class MyClass {
var wrappedValues: [Wrapper] = [Wrapper(wrappedValue: Values(value: "hello")), Wrapper(wrappedValue: Values(value: 1))]
}

Type of expression is ambiguous without more context in Xcode 11

I'm trying to refer to an [Item] list within an #EnvironmentObject however when accessing it within a SwiftUI List, I get the error. What I don't understand is, this error doesn't pop up when following Apple's Landmark tutorial.
As far as I can tell, the [Item] list is loading correctly as I can print it out and do other functions with it. It just bugs out when using it for a SwiftUI List Is there something I've missed?
ItemHome.swift:
struct ItemHome : View {
#EnvironmentObject var dataBank: DataBank
var body: some View {
List {
ForEach(dataBank.itemList) { item in
Text("\(item.name)") // Type of expression is ambiguous without more context
}
}
}
}
Supporting code below:
Item Struct:
struct Item {
var id: Int
var uid: String
var company: String
var item_class: String
var name: String
var stock: Int
var average_cost: Decimal
var otc_price: Decimal
var dealer_price: Decimal
var ctc_price: Decimal
}
DataBank.swift:
final class DataBank : BindableObject {
let didChange = PassthroughSubject<DataBank, Never>()
var itemList: [Item] = load("itemsResults.json") {
didSet {
didChange.send(self)
}
}
}
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
itemsResults.json:
[
{
"id": 1,
"uid": "a019bf6c-44a2-11e9-9121-4ccc6afe39a1",
"company": "Bioseed",
"item_class": "Seeds",
"name": "9909",
"stock": 0,
"average_cost": 0.0,
"otc_price": 0.0,
"dealer_price": 0.0,
"ctc_price": 0.0
},
{
"id": 2,
"uid": "a019bf71-44a2-11e9-9121-4ccc6afe39a1",
"company": "Pioneer",
"item_class": "Seeds",
"name": "4124YR",
"stock": 0,
"average_cost": 0.0,
"otc_price": 0.0,
"dealer_price": 0.0,
"ctc_price": 0.0
}
]
Apparently I missed making sure my models (Item in this case) conformed to the Identifiable protocol fixed it. Still, I wish Apple was more clear with their error messages.
As you mentioned in your answer, a ForEach needs a list of Identifiable objects. If you don't want to make your object implement that protocol (or can't for some reason), however, here's a trick:
item.identifiedBy(\.self)
I had the same problem and it wasn't something related to the line itself, it was related to the curly braces/brackets, so that if someone faced the same problem and doesn't know where the problem is, try to trace the curly braces and the brackets
To conform to Identifiable, just give the id or uid variable a unique value.
An easy way to do this is this:
var uid = UUID()
So your full struct would be:
struct Item: Identifiable {
var id: Int
var uid = UUID()
var company: String
var item_class: String
var name: String
var stock: Int
var average_cost: Decimal
var otc_price: Decimal
var dealer_price: Decimal
var ctc_price: Decimal
}
Xcode may show this error in many cases. Usually when using higher order functions (like map) and reason may be "anything".
There are two types:
Error is in higher order function. In this case your only option is to carefully read the block of code. Once again there may be different kinds of problems.
In some cases higher order function does not have any problem by itself, but there may be a compile time error in function that's called from the body of the higher order function.
Unfortunately Xcode does not point to this error sometimes.
To detect such errors and save lot of time, easy workaround is to temporarily comment higher order function and try to build. Now Xcode will show this error function and will show more reasonable error.

How do I correctly print a struct?

I'm trying to store an array of store structs within my users struct, but I can't get this to print correctly.
struct users {
var name: String = ""
var stores: [store]
}
struct store {
var name: String = ""
var clothingSizes = [String : String]()
}
var myFirstStore = store(name: "H&M", clothingSizes: ["Shorts" : "Small"])
var mySecondStore = store(name: "D&G", clothingSizes: ["Blouse" : "Medium"])
var me = users(name: "Me", stores: [myFirstStore, mySecondStore])
println(me.stores)
Youโ€™re initializing them just fine. The problem is your store struct is using the default printing, which is an ugly mangled version of the struct name.
If you make it conform to CustomStringConvertible, it should print out nicely:
// For Swift 1.2, use Printable rather than CustomStringConvertible
extension Store: CustomStringConvertible {
var description: String {
// create and return a String that is how
// youโ€™d like a Store to look when printed
return name
}
}
let me = Users(name: "Me", stores: [myFirstStore, mySecondStore])
println(me.stores) // prints "[H&M, D&G]"
If the printing code is quite complex, sometimes itโ€™s nicer to implement Streamable instead:
extension Store: Streamable {
func writeTo<Target : OutputStreamType>(inout target: Target) {
print(name, &target)
}
}
p.s. convention is to have types like structs start with a capital letter

Should a dictionary be converted to a class or struct in Swift?

I am working on a native iOS application that receives data in JSON format from a web-service which we are also in control of. The plan is to change out the backend database in a bout 18 months in favor of a different platform.
With that in mind, we want to be sure that that iOS app is going to be relatively easy to adapt to the new datasource, particularly as we may change the keys used in the associative array received from the server via JSON.
There are two goals:
Create a single location for each PHP request where the keys can be modified if needed. This would avoid digging through code to find things like job["jobNumber"].
Clean up our existing code to also eliminate references like job["jobNumber"].
We are both very new to Swift with no Objective-C experience, but I am was thinking a Struct or Class would be appropriate to create references like job.jobNumber.
Should a dictionary be converted into a class or struct? Sample code representing a reusable method of taking a Dictionary<String, String> as shown below and converting it to the recommended type would be extremely helpful.
Example Dictionary:
job = {
"jobNumber" : "1234",
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"
}
Desired result:
println("job name is \(job.name)")
// prints: job name is Awards Ceremony
To access it like this you need to convert your dictionary to Struct as follow:
edit/update: Swift 3.x
struct Job: CustomStringConvertible {
let number: Int
let name, client: String
init(dictionary: [String: Any]) {
self.number = dictionary["jobNumber"] as? Int ?? 0
self.name = dictionary["jobName"] as? String ?? ""
self.client = dictionary["client"] as? String ?? ""
}
var description: String {
return "Job#: " + String(number) + " - name: " + name + " - client: " + client
}
}
let dict: [String: Any] = ["jobNumber": 1234,
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"]
let job = Job(dictionary: dict)
print(job.number) // 1234
print(job.name) // "Awards Ceremony"
print(job.client) // "ACME Productions"
print(job) // "Job#: 1234 - name: Awards Ceremony - client: ACME Productions"""
edit/update:
Swift 4 or later you can use JSON Codable protocol:
struct Job {
let number: Int
let name, client: String
}
extension Job: Codable {
init(dictionary: [String: Any]) throws {
self = try JSONDecoder().decode(Job.self, from: JSONSerialization.data(withJSONObject: dictionary))
}
private enum CodingKeys: String, CodingKey {
case number = "jobNumber", name = "jobName", client
}
}
extension Job: CustomStringConvertible {
var description: String {
return "Job#: " + String(number) + " - name: " + name + " - client: " + client
}
}
let dict: [String: Any] = ["jobNumber": 1234,
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"]
do {
let job = try Job(dictionary: dict)
print(job.number) // 1234
print(job.name) // "Awards Ceremony"
print(job.client) // "ACME Productions"
print(job) // "Job#: 1234 - name: Awards Ceremony - client: ACME Productions\n"
} catch {
print(error)
}
Definitely a job for a struct.
1. Structs are thread-safe and don't need to be managed by ARC.
2. Some studies have found them to be about 30,000x faster to work with than classes in general.
3. Structs also provide default initializers so your code will be cleaner.
4. In this case, you don't have to worry about inheritance/subclassing.
5. The Protocol Oriented Programming paradigm recommends using structs over classes if you're able.
struct Job {
let number: Int
let name: String
let client: String
}
Initializer for free:
let newJob = Job(number: 2, name: "Honey", client: "Jeff")
Or you can create a custom initializer that takes the dictionary:
struct Job {
let number: Int
let name: String
let client: String
init(json: [String: Any]) {
self.number = Int(dictionary["jobNumber"] as? String) ?? 0
self.name = dictionary["jobName"] as? String ?? ""
self.client = dictionary["client"] as? String ?? ""
}
}
usage:
let newJob = Job(json: yourDictionary)
print(newJob.number)
// outputs: 1234
You can add an extension to Dictionary like this to get generic objects:
extension Dictionary where Key == String, Value: Any {
func object<T: Decodable>() -> T? {
if let data = try? JSONSerialization.data(withJSONObject: self, options: []) {
return try? JSONDecoder().decode(T.self, from: data)
} else {
return nil
}
}
}
and use like this on any [String: Any] dictionaries:
let object: MyDecodableStruct? = dictionary.object()
I generally make use of value classes to achieve what you want to do. In my project I do something like following:
protocol Request : class {
func toDictionary() -> [String : String]
}
protocol Response : class {
init(dictionary: [String : String])
}
class MyRequest : Request {
var var1: Int
var var2: String
//Other stuff in class...
func toDictionary() -> [String : String] {
//Convert the value to dictionary and return
}
}
class MyResponse : Response {
var var1: String
var var2: Int
//You could have nested object as members
var innerObject: MyInnerResponseClass
//Other stuff in class...
var someCalculatedProperty: String {
return //Calculate property value
}
required init(dictionary: [String : String]) {
//Initialize class from dictionary
}
}
class MyInnerResponseClass: Response {
//Class definition here...
}
For objects that could be used as request and response you could implement both of the protocols.
You need to write code for translation once, but then it could be easy to use everywhere. Also by using calculated properties you may get extra ease.
I am not sure if you could just do it out of the box in Swift. I will require reflection which is not yet very well supported by Swift. Also even if there is reflection and you come up with clever way to use to achieve what you need, it could be quite slow if the data is quite large.
My two cents about "logic". )all correct about using structs and so on...)
Do NOT keep (as many from web do..) data in dict or JSON, convert it to struct always.
a lot of efficiently, think about for example about sorting in a tableview..

Unable to add object to a NSMutableArray

I'm attempting to load a mutable array and got stuck.
Essentially what I want to do is to be able to add any class object to a global array. In this example I merely have name & address.
Here's a playground snippet:
import Foundation
var gDownloaders:NSMutableArray?
class downloader {
var name:String?
var address:String?
init(name:String, address:String) {
self.name = name
self.address = address
}
}
let one = downloader(name: "Ric Lee", address: "901 Edgewood")
let two = downloader(name: "Richard Brauer", address:"1010 Red Oak")
let three = downloader(name: "Meredith Lind", address: "410 Sunset Blvd")
gDownloaders?.addObject(one)
gDownloaders?.addObject(two)
gDownloaders?.addObject(three)
println(gDownloaders)
BTW: Xcode insist that I have the '?'/gDownloaders.
All I'm getting is nil for gDownloaders; even though each of the three objects are bona fide with data.
What am I doing wrong?
...should I use the Array vs NSMutableArray class instead?
You never initialize gDownloaders. You declare what type the variable gDownloaders should have, but you don't put anything in that variable.
Try:
var gDownloaders:NSMutableArray? = NSMutableArray()
In swift, in a playground try this:
import Foundation
class downloader {
var name:String?
var address:String?
init(name:String, address:String) {
self.name = name
self.address = address
}
}
var gDownloaders = [downloader]()
let one = downloader(name: "Ric Lee", address: "901 Edgewood")
let two = downloader(name: "Richard Brauer", address:"1010 Red Oak")
let three = downloader(name: "Meredith Lind", address: "410 Sunset Blvd")
gDownloaders.append(one)
gDownloaders.append(two)
gDownloaders.append(three)
println(gDownloaders[0].name!)