iOS - Choosing Model Class - ios

Can I use same model class for storing array data of multiple type array of dictionary with same keys?
Let's say for example I have a model class named ProductDetail used to store the product detail with key id, name and image and showing them in UITableViewController.
Now I have a different class named categories with same above mentioned keys.
Here is my model class:
class TrendingProductsData: NSObject {
var id : Int! = 0
var name : String! = ""
var image : String! = ""
}
My question is can I use the ProductDetail model to store categories data as well?

How about using a super model for common properties and extending the ones you have. Here is what I mean:
class BaseModel: NSObject {
var id : Int = 0
var name : String = ""
var image : String = ""
func setData(data: Any) {
// Parse id, name and image from data
}
}
class ProductDetail: BaseModel {
// Add your other properties and/or functions
var productProvider: String = "" // I added this to be an example
override func setData(data: Any) {
super.setData(data: data) // Since the key-value pairs are the same id, name and image will be parsed at BaseModel
// Parse extra values such as productProvider
}
}
class Categories: BaseModel {
// Add your other properties and/or functions
var categorySubtitle: String = "" // I added this to be an example
override func setData(data: Any) {
super.setData(data: data) // Since the key-value pairs are the same id, name and image will be parsed at BaseModel
// Parse extra values such as categorySubtitle
}
}
This way you can create both ProductDetail and Categories models with common properties and if the need occurs you can add separate properties and functions.

If you want to more complex structure like subCategory etc. it could be in a better way. But I think basically these classes are what you want.
Product can have detail only if isCategory is false.
class Product {
var id : Int = 0
var name : String = ""
var image : String = ""
var isCategory: Bool = false
var productDetail: ProductDetail? = nil
}
class ProductDetail {
var description : String = ""
var price : Decimal?
}

Related

Swift - toggle model to readonly momentarily

I have a phone number model which looks like this:
import UIKit
import Foundation
struct PhoneValidation : OptionSet {
let rawValue: Int
static let phoneInValid = PhoneValidation(rawValue: 1 << 0)
static let phoneValid = PhoneValidation(rawValue: 1 << 1)
static let smsValidationAttempted = PhoneValidation(rawValue: 1 << 2)
static let smsValidationFailed = PhoneValidation(rawValue: 1 << 3)
static let smsValidationSuccessful = PhoneValidation(rawValue: 1 << 4) // OTP is successfully validated in backend. The field should be non-editable in this duration
static let smsValidationOTPTriggered = PhoneValidation(rawValue: 1 << 5) // OTP validation triggered. The field should be non-editable in this duration
}
class PhonesViewModel: NSCopying {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}
As you can see above the phone model can transition between different states. The SMS validation is available for few countries and for few it is not applicable. So, I plan on setting smsValidationOTPTriggered state when SMS validation is applicable for a country and while the validation is in progress.
What I need here is, while the states smsValidationOTPTriggered or smsValidationSuccessful are set I would not want any module of the application to modify the values(phoneType, phone, code, countryCode) of the model. In other words, I would like the model to switch to a read-only mode while these 2 states are set in model and would like the module to be informed with an error or exception when a modification is attempted.
Is there a best practice already available for what I am trying to achieve here? I have searched before raising this question but did not find any. How can I achieve this?
Thanks,
Raj Pawan Gumdal
How about something like this, I think its better to use property wrappers for your case! The below is not an exact solution but can modify/change to accommodate your need
import UIKit
enum PhoneNumberType {
case mobile
}
enum PhoneValidation {
case phoneInValid
case phoneValid
case smsValidationAttempted
case smsValidationFailed
case smsValidationSuccessful
case smsValidationOTPTriggered
}
struct PhonesViewModel {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var phoneValidation : PhoneValidation?
func validate(value: [PhoneValidation]) -> Bool {
//add proper check here
return false
}
}
#propertyWrapper
struct Wrapper {
private(set) var value: PhonesViewModel? = nil
var validators: [PhoneValidation] = []
var wrappedValue: PhonesViewModel? {
get { value }
set {
if let model = newValue, model.validate(value: validators) {
value = newValue
print("Value assigned")
} else {
print("Value not assigned")
}
}
}
}
struct SomeOtherClass {
#Wrapper(validators: [PhoneValidation.phoneInValid])
var model: PhonesViewModel?
}
var a = SomeOtherClass()
a.model = PhonesViewModel()
a.model = PhonesViewModel()
You can use a technique with the name "popsicle immutability". An object is initially mutable, but can be "frozen". Modifications for frozen objects are forbidden. In your case PhonesViewModel become frozen when isValid property have value smsValidationOTPTriggered or smsValidationSuccessful.
Let's add Freezable protocol for requirements to objects that can become immutable and conforming for PhonesViewModel:
protocol Freezable: class {
var isFrozen: Bool { get }
}
extension PhonesViewModel: Freezable {
var isFrozen: Bool {
isValid == .smsValidationOTPTriggered || isValid == .smsValidationSuccessful
}
}
Now we must add validation for isFrozen value when a property is assigned. It can be added in property observers like:
...
public var phone: String? {
didSet {
validate()
}
}
...
private func validate() {
assert(!isFrozen)
}
Or using property wrapper:
#propertyWrapper
struct Guarded<Value> {
private var value: Value
init(wrappedValue: Value) {
value = wrappedValue
}
#available(*, unavailable)
var wrappedValue: Value {
get { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
set { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
}
static subscript<EnclosingSelf: Freezable>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
) -> Value {
get {
object[keyPath: storageKeyPath].value
}
set {
precondition(!object.isFrozen, "Object \(object) is frozen! Modifications are forbidden")
object[keyPath: storageKeyPath].value = newValue
}
}
}
So your class will look like:
class PhonesViewModel: NSCopying {
#Guarded
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
#Guarded
public var phone: String?
#Guarded
public var code: String?
#Guarded
public var countryCode: String?
#Guarded
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}

Pass value to a private property

I have a couple of private properties defined like so..
private var tableConfig : TableViewConfig<Company, CompanyCell>?
private var tabledata = [Company]() {
didSet {
tableConfig?.items = tabledata
}
}
Now there are 2 other properties defined like so...
var model: Model?
var companyCell: TableviewCell?
Now, if get the value in model as Industry and the value in companyCell as IndustryCell, how can I update these values to private var tableConfig : TableViewConfig.... and private var tabledata = [Company]().... so that they will ultimately have the values like so..
private var tableConfig : TableViewConfig<Industry, IndustryCell>?
private var tabledata = [Industry]() {
didSet {
tableConfig?.items = tabledata
}
}
you can use a get set variabile to achieve this result
var _tabledata: [Industry] {
get {
return tabledata
}
set {
tabledata = newValue
}
}
As the attribute private implies properties and functions declared as private can only be accessed within the scope they are declared.
You have two options:
Change the access control to fileprivate (if the caller is in the same file) or internal or lower restriction.
Add an internal or lower restriction computed property or method in the scope of the private property to set it.
You can expose an init() or a method to set the value of private properties, i.e.
class SampleClass {
private var tableConfig : TableViewConfig<Company, CompanyCell>?
private var tabledata = [Company]() {
didSet {
tableConfig?.items = tabledata
}
}
init(tableData: [Company]) {
self.tabledata = tabledata
}
func add(item: Company) {
self.tabledata.append(item)
}
}
You can create an init in case you want to initialise your private variable and use a method in case you want to update the private variables.

how to implement multiple user on realm database in iOS?

I am a beginner on programming and especially on iOS development
currently I am trying to use realm as the local database in an ecommerce app. I have Product and WishList object of Realm like below
The product:
class Product : Object {
#objc dynamic var productID : Int = 0
#objc dynamic var name : String = ""
#objc dynamic var categoryID : Int = 0
#objc dynamic var categoryName : String = ""
#objc dynamic var unitPrice: Double = 0.0
#objc dynamic var quantityInCart = 0
#objc dynamic var quantityFromServer = 0
#objc dynamic var descriptionProduct : String = ""
#objc dynamic var hasBeenAddedToWishList : Bool = false
#objc dynamic var hasBeenAddedToCart : Bool = false
#objc dynamic var isNewProduct : Bool = false
#objc dynamic var productWeight : String = ""
#objc dynamic var weightUnit : String = ""
#objc dynamic var minimumOrderQuantity = 0
#objc dynamic var maximumOrderQuantity = 0
override static func primaryKey() -> String? {
return "productID"
}
}
the Wishlist:
class WishList : Object {
var products = List<Product>()
}
my app can change the user. so let say if User A put some products on the wishlist (populates the WishList object with the product), after that he performs Log Out,
then it will make the User B's wishlist object will already have object after User B login.
so I think I need to insert userID both on Product and Wishlist object, and I have to also remove the primary key on Product object.
override static func primaryKey() -> String? {
return "productID"
}
so when I perform query or filter from realm database I can filter based on productID and also userID.
do I have the correct approach using way like that ? or is there a better approach? because to I am not comfortable having userID as the property of Product object like code below:
class Product : Object {
#objc dynamic var userID : Int = 0 <--- like this
#objc dynamic var productID : Int = 0
#objc dynamic var name : String = ""
#objc dynamic var categoryID : Int = 0
#objc dynamic var categoryName : String = ""
#objc dynamic var unitPrice: Double = 0.0
#objc dynamic var quantityInCart = 0
#objc dynamic var quantityFromServer = 0
#objc dynamic var descriptionProduct : String = ""
}
class WishList : Object {
#objc dynamic var userID : Int = 0 <--- and like this
var products = List<Product>()
}
and with this approach, I will also have 'duplicate' Product data on Product object with the same productID, but the userID on the property of the Product is different
You shouldn't add userID to either the Product or the Wishlist. A Product should be independent of specific users, so it should have no relationship to a user. On the other hand, a Wishlist is specific to a user, but instead of manually having to query the userID property of a specific Wishlist, you should take advantage of Realm's built-in relationships and declare a one-to-one relationship between a User and a Wishlist (you can either declare the relationship on WishList or on User, both work just fine).
So your Product definition should be left unchanged with its primaryKey being intact as well. Your Wishlist should look like the following:
class WishList : Object {
#objc dynamic var user: User?
let products = List<Product>()
}
Or if you want to access the WishList of a specific User from the user, you can modify your User like so:
class User: Object {
#objc dynamic var wishList: WishList?
...
}

Realm object changed, another copy of that object has changed too, why?

I am stuck in a problem. Let's assume I have this Realm Model:
class Table: Object {
dynamic var id = 0
dynamic var x: Int = 0
dynamic var y: Int = 0
dynamic var width:Int = 0
dynamic var height: Int = 0
dynamic var text: String = ""
dynamic var color: String = ""
dynamic var type: String = ""
let food = List<Food>()
override static func primaryKey() -> String {
return "id"
}
}
class Food : Object {
dynamic var id: Int = 0
dynamic var name = ""
dynamic var ingredients: String = "" // bigger text field
dynamic var size: Int = 0 // none, small, medium, big size
dynamic var price: Float = 0.0
dynamic var category: Category?
let additionalIngredients = List<Ingredient>()
override static func primaryKey() -> String {
return "id"
}
}
Let's say I have one table and added 2 times the same food on that table like so :
try! realm.write(){
table.food.append(food) // A
table.food.append(food) // B
realm.add(table, update: true)
}
If I change the additionalIngredients for food A , also at the same food B changes its values. I am doing that changes with this transaction :
try! realm.write(){
table.food.first!.additionalIngredients.removeAll()
for ingredient in ingredientsToAdd{
table.food.first!.additionalIngredients.append(ingredient)
}
realm.add(table, update: true)
}
I guess I am doing something wrong regarding the reference/instance, can someone give me a hint?
Thanks in advance!
List.append() adds the object itself to the list and not a copy of the object, so you only have one Food object.

Many-to-one with primary key (unique constraint)

I've got an Article and a Category model linked by a many-to-one relationship. However, the Category model has a unique constraint on the id property because it's the primary key as you can see below.
class Article: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
dynamic var category: Category()
override static func primaryKey() -> String? {
return "id"
}
}
class Category: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
This will work until an Article got the same Category and throw an exception because of the unique constraint.
How am I supposed to implement this kind of relationship ? Is there any built-in way to persist only the Category id and retrieve the corresponding Category ?
Thanks
As you can read in Realm doc (0.92.1), you have to use a List<Object> for a many-to-one relationship.
See this link :
http://realm.io/docs/swift/latest/
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person? // Can be optional
}
class Person: Object {
... // other property declarations
let dogs = List<Dog>()
}
let someDogs = Realm().objects(Dog).filter("name contains 'Fido'")
jim.dogs.extend(someDogs)
jim.dogs.append(rex)
So in your case, I guess it should be something like that :
class Article: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
class Category: Object
{
dynamic var id: String = ""
dynamic var title: String = ""
dynamic var articles = List<Article>()
override static func primaryKey() -> String? {
return "id"
}
}
If your Realm version is older :
class Category: Object
{
...
dynamic var categories = RLMArray(objectClassName: Article.className())
}

Resources