Swift Struct Identity - swift

I have a following struct which defines a shoppingList.
struct ShoppingList {
var shoppingListId :NSNumber
var title :String
}
let shoppingList = ShoppingList(shoppingListId: NSNumber, title: String) // I don't want to assign shoppingListId
Now, the problem is that it auto-generates the constructors which takes in the shoppingListId as parameter. Since shoppingListId is an identity or unique key I don't want to pass in with the constructor. It will be set later by the ShoppingListService after inserting shoppingList into the database.
Is this a place where class will make more sense than structures?
UPDATE:
struct ShoppingList {
var shoppingListId :NSNumber
var title :String
init(title :String) {
self.title = title
// but now I have to assign the shoppingListId here unless I make it NSNumber? nullable
}
}
let shoppingList = ShoppingList(title: "Hello World")
UPDATE 3:
let shoppingList = ShoppingList()
// THE FOLLOWING CODE WILL NOT WORK SINCE IT REQUIRES THE shoppingList to // BE var instead of let
shoppingList.shoppingListId = NSNumber(int: results.intForColumn("shoppingListId"))
shoppingList.title = results.stringForColumn("title")
shoppingLists.append(shoppingList)

If you don't want the default constructor, then make one of your own. However, it is important to keep in mind that unless your property is an Optional, it has to be initialized to some value. In this case, you will have to give shoppingListId a value, or you will need to make it an Optional. Also, I don't see any reason why a class would be better than a struct for this scenario.
struct ShoppingList {
var shoppingListId: NSNumber
var title: String
init(title: String) {
self.title = title
shoppingListId = NSNumber(integer: 0)
}
}
let newList = ShoppingList(title: "Groceries")
Update:
Just saw you updated your question. If you are not able to pick a reasonable initial value for your shoppingListId, then make it an Optional like so:
struct ShoppingList {
var shoppingListId: NSNumber?
var title: String
init(title: String) {
self.title = title
}
}
let newList = ShoppingList(title: "Groceries")
Just realize shoppingListId will be nil till you set it to something, and everywhere you use it you should bind the value in an if let.

Related

How to properly use optional properties of structs in Swift

I have the following struct:
extension Models {
struct Book: Identifiable {
let id: UUID
var title: String
var pagesCount: Int
var cover: Optional<Cover>;
struct Cover {
let image: String
init(image: String) {
self.image = image
}
}
init(id: UUID = UUID(), title: String, pagesCount: Int, cover: Optional<Cover> = nil) {
self.id = id
self.title = title
self.pagesCount = pagesCount
self.cover = cover
}
}
}
extension Models.Book {
static let sampleData: Models.Book = Models.Book(title: "Orconomics", pagesCount: 100, cover: Models.Book.Cover(image: "actual_image.png"))
}
and I am creating a SwiftUI view that displays each "model" Book.
I have made the Cover parameter optional, as some books won't have covers, the Models.Book.Cover struct has a non-optional property image.
Here's what I have in the body of my SwiftUI file:
HStack {
if book.cover != nil {
Text(book.cover?.image ?? "test") // <- can I avoid this?
}
}
Isn't the book.cover != nil sufficient for Swift? Why do I need the "default value" (test) and the ? after book.cover?
you could try something like this:
HStack {
if let cover = book.cover {
Text(cover.image)
}
}
What I use to do is handle this in my view model so I guess you should have a default value in your init, so I don't need to handle that in the View, since I like the view to be dumb and not deal with much logic.
I think you should use insteaad guard if you want to add an else option IMO guard is a bit more readable, if not if let as mentioned before should be fine.
View? conforms to View. So just use map.
book.cover?.image.map(Text.init)

State variables in Property Wrappers

I would like to use a variable to cache user defaults which are fetched by a propert wrapper. I used the boilerplate from this quite good tutorial regarding property wrappers as a basis https://www.avanderlee.com/swift/property-wrappers/
#propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
#State var cacheValue: T?
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
if (self.cacheValue == nil){
self.cacheValue = UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
return self.cacheValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
During debugging I found out that my cacheValue always returns nil, even if there is a corresponding user property value that is returned.
So the question is: Are #State variables supposed to work in property wrapppers? And if not, is there a workaround, that does not mean coding it in the class/struct that uses the property wrapper construct?
#State is a property wrapper declared inside SwiftUI and it should only be used inside Views.

Swift : retrieving struct values in swiftui view is not working

I am new to swift and need to define some form of Global Dictionary that I can access the content throughout my project. My understanding is that struct class can be used for that so
I created a struct and appended values to it, now I want to access each of those values in view
this is my product struct
struct Product {
let name: String
let aisleNo:Int
let location_section: Int
let location_zone: String
let productPrice: Int
}
then created a global
import Foundation
struct Global {
static var productList = [Product]()
}
this is how I append many products to Product
class SearchResult : ObservableObject {
var productList = [Product]()
//There could be hundreds of product in the array
for product in productArray {
let productName = product.productName!
let aisleNo = product.productLocation_aisle.value!
let location_section = product.productLocation_section.value!
let location_zone = product.productLocation_zone!
let productPrice = product.productPrice.value!
let product_real_id = product._id!
Global.productList.append(Product(name: productName, aisleNo: aisleNo, location_section: location_section, location_zone: location_zone, productPrice: Int(productPrice)))
}
this is my search result view where I want to display the content of the Product
struct SearchResultView: View {
var searchResults = Global.productList
var body: some View {
VStack {
List {
ForEach(model.searchResults, id: \.self) { text in
Text(text)
}
}
}
}
}
I can seem to get it to show in the searchResultView. What am doing wrong ?
I keep getting this error
Generic struct 'ForEach' requires that 'Product' conform to 'Hashable'
Initializer 'init(_:)' requires that 'Product' conform to 'StringProtocol'
You need to set "searchResults" equal to your "productList"
Right now your searchResults is EMPTY. It simply exists as an instance of your struct with no data in it.
one option is to make the variable scope global and then set your new variable = to it
self.searchResults = Global.productList
--EDIT
You are close.
Where you set your var here
var searchResults = Global.productList
it needs to be like this.
var searchResults = [Product]() // ->Creates an instance of the struct object
Then set it equal to your global array.
self.searchResults = Global.productList
ALSO you should remove you redundant variable var productList = [Product]()
Furthermore, some things to note
for product in productArray {
let productName = product.productName!
let aisleNo = product.productLocation_aisle.value!
let location_section = product.productLocation_section.value!
let location_zone = product.productLocation_zone!
let productPrice = product.productPrice.value!
let product_real_id = product._id!
Global.productList.append(Product(name: productName, aisleNo: aisleNo, location_section: location_section, location_zone: location_zone, productPrice: Int(productPrice)))
}
you are doing extra work by using all the let variables.
A better way is doing it like so.
for product in productArray {
Global.productList.append(Product(name: product.name, aisleNo: product.aisleNo, location_section: product.location_section, location_zone: product.location_zone, productPrice: Int(product.productPrice)))
}
EDIT - Hashable Erorr
try this out
struct Product: Hashable {
let name: String
let aisleNo:Int
let location_section: Int
let location_zone: String
let productPrice: Int
}

How to access struct values inside of another struct in Swift 4?

I currently have
struct cellData {
var opened = Bool()
var title = String()
var iconName = String()
var sectionData = [Any]()
}
struct SectionData {
var subTitle: String
var iconName: String
}
And in another function I call:
let test = tableViewData[indexPath.section].sectionData[dataIndex]
print(test)
Which outputs:
SectionData(subTitle: "Terms", iconName: "")
How do I access the subTitle value because doing test.subTitle throws the following error:
Value of type 'Any' has no member 'subTitle'
This is because, in your line var sectionData = [Any](), you have defined the type as Any. So when you access it via tableViewData[indexPath.section], you get back the value as Any.
You should change var sectionData = [Any]() to var sectionData = [SectionData]()
Otherwise, once you get the value from tableViewData[indexPath.section], you can convert to SectionData and then access the value.
Change your sectionData array to an array of SectionData like so: var sectionData = [SectionData](). Once you do this, you'll able to access it by calling: tableViewData[indexPath.section].sectionData[dataIndex].subTitle

How do I create global object in swift?

I think my previous question was too vague. Let me try again.
I'm trying to hold user information by creating a singleton.
After a bit of research, this is what I understood.
Class UserInfo {
var userFirstName: String?
var userLastName: String?
/*I'll add all other variables here*/
}
let sharedInfo = UserInfo()
Am I on right path?
EDIT: For Swift 1.2 and higher (Thanks Will M)
class UserInfo {
static let sharedUserInfo = UserInfo()
var firstName: String?
var lastName: String?
}
For Swift earlier than 1.2
Something along the lines of:
class UserInfo
{
// Singleton Setup
private static var info: UserInfo?
class func sharedUserInfo() -> UserInfo
{
if self.info == nil
{
self.info = UserInfo()
}
return self.info!
}
// Class properties
var firstName: String?
var lastName: String?
}
let user = UserInfo.sharedUserInfo()
user.firstName = "Fred"
user.lastName = "Smith"
I would also recommend making sure that a singleton is really what you want, because depending on how you use it, you could run into some race conditions with threading.