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

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

Related

Insert 'from: <#Decoder#>'

I have the following Model:
struct MyModel: Codable, Hashable {
let data: ASubModelData
let error: Bool?
let error_message: String?
let error_code: Int?
}
and I'm trying to create a variable in a view so I can then assign the values to it like this:
#State var myVar: MyModel? = MyModel()
but it's throwing an error:
Insert 'from: <#Decoder#>'
If I hit fix, it would be like:
#State var myVar: MyModel? = MyModel(from: Decoder)
and that's wrong, it also gives an error, how can I create a variable that is an empty instance of that model?
Your struct has no initializer other than the one for decoding. You have to add the initializer init():
init() {
data = ?
error = nil
error_message = nil
error_code = nil
}
However, since MyModel contains properties that are not optional, it would be probably better to initialize it with a nil:
#State var myVar: MyModel?

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
}

Swift Realm filter all object with a null value

I have two objects as follow:
class NextAction: Object {
#objc dynamic var title: String = ""
#objc dynamic var notes: String? = ""
#objc dynamic var deadline: Date?
#objc dynamic var deadlineID: String = ""
#objc dynamic var reminder: Date?
#objc dynamic var reminderID: String = ""
#objc dynamic var finished: Bool = false
#objc dynamic var favorite: Bool = false
#objc dynamic var priority: Int = 0
var duration = RealmOptional<Int>()
#objc dynamic var tag: String?
let tags = List<Tag>()
}
class Tag: Object {
#objc dynamic var title: String = ""
let owners = LinkingObjects(fromType: NextAction.self, property: "tags")
}
Not all NextAction objects has a tag, but I want to filter out and show all who are missing one, I have tried
var test = realm.objects(NextAction.self).filter("ANY tags == nil")
But I get this error
'Invalid value', reason: 'Expected object of type Tag for property 'tags' on object of type 'NextAction', but received: (null)'
Your tags cannot be nil.
If you want to find objects whose tags is empty, you can do that this way.
var test = realm.objects(NextAction.self).filter("ANY tags.#count == 0")

Swift Struct Identity

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.

Compare two instances of an object in Swift

Given the following class, how can all the values in two instances be compared to each other?
// Client Object
//
class PLClient {
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
convenience init (copyFrom: PLClient) {
self.init()
self.name = copyFrom.name
self.email = copyFrom.email
self.mobile = copyFrom.mobile
self.companyId = copyFrom.companyId
self.companyName = copyFrom.companyName
}
}
var clientOne = PLClient()
var clientTwo = PLClient(copyFrom: clientOne)
if clientOne == clientTwo { // Binary operator "==" cannot be applied to two PLClient operands
println("No changes made")
} else {
println("Changes made. Updating server.")
}
The use-case for this is in an application which presents data from a server. Once the data is converted into an object, a copy of the object is made. The user is able to edit various fields etc.. which changes the values in one of the objects.
The main object, which may have been updated, needs to be compared to the copy of that object. If the objects are equal (the values of all properties are the same) then nothing happens. If any of the values are not equal then the application submits the changes to the server.
As is shown in the code sample, the == operator is not accepted because a value is not specified. Using === will not achieve the desired result because these will always be two separate instances.
Indicate that your class conforms to the Equatable protocol, and then implement the == operator.
Something like this:
class PLClient: Equatable
{
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
//The rest of your class code goes here
public static func ==(lhs: PLClient, rhs: PLClient) -> Bool{
return
lhs.name == rhs.name &&
lhs.id == rhs.id &&
lhs.email == rhs.email &&
lhs.mobile == rhs.mobile &&
lhs.companyId == rhs.companyId &&
lhs.companyName == rhs.companyName
}
}
Working off of Duncan C's answer, I have come up with an alternative which is slightly clearer that it is being used in a custom way:
// Client Object
//
class PLClient {
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
convenience init (copyFrom: PLClient) {
self.init()
self.name = copyFrom.name
self.email = copyFrom.email
self.mobile = copyFrom.mobile
self.companyId = copyFrom.companyId
self.companyName = copyFrom.companyName
}
func equals (compareTo:PLClient) -> Bool {
return
self.name == compareTo.name &&
self.email == compareTo.email &&
self.mobile == compareTo.mobile
}
}
var clientOne = PLClient()
var clientTwo = PLClient(copyFrom: clientOne)
if clientOne.equals(clientTwo) {
println("No changes made")
} else {
println("Changes made. Updating server.")
}
You could loop through fields by using keypath
I haven't tested this, but the general idea is there. Give a list of valid fields and loop through them instead of writing every single equatable. So it's the same as #duncan-c suggested but with looping.
Something like:
class PLClient:Equatable {
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
public static func ==(lhs: PLClient, rhs: PLClient) -> Bool{
let keys:[KeyPath<PLClient, String>] = [\.name, \.id, \.email, \.mobile, \.companyId, \.companyName]
return keys.allSatisfy { lhs[keyPath: $0] == rhs[keyPath: $0] }
}
}
Try "is" keyword in swift,
e.g.
if self.navigationController.topViewController is TestViewController {
//Logic here
}