Indentifiable Protocol extension for SwiftUI list data - ios

I was experimenting with SwiftUI and came across a problem while implementing the data model for one of my List. My plan was to create a protocol CardProtocol as the data protocol for the elements of my lists and then have a CoreData implementation of the protocol as well as a dummy one for unit testing and Canvas use. If you are using a data collection in SwiftUI List the single elements need to conform to the Identifiable protocol.
The code looks like this:
import SwiftUI
import Combine
final class CardsModel: BindableObject {
var cards: [CardProtocol] = []
let didChange = PassthroughSubject<CardsModel, Never>()
}
protocol CardProtocol: Identifiable {
var id: Int { get set }
var firstName: String? { get set }
var lastName: String? { get set }
var email: String? { get set }
var phone: String? { get set }
}
This will not even compile as the Identifiable protocol has 2 associated types which needs to be specified if the protocol is to be used for a variable definition.
/// A type that can be compared for identity equality.
public protocol Identifiable {
/// A type of unique identifier that can be compared for equality.
associatedtype ID : Hashable
/// A unique identifier that can be compared for equality.
var id: Self.ID { get }
/// The type of value identified by `id`.
associatedtype IdentifiedValue = Self
/// The value identified by `id`.
///
/// By default this returns `self`.
var identifiedValue: Self.IdentifiedValue { get }
}
The exact error being error: protocol 'CardProtocol' can only be used as a generic constraint because it has Self or associated type requirements.
Now ID is not an issue and can be fixed, but IdentifiedValue it's by nature different in the CoreData and the dummy implementation.
The only reasonable solution that I found was to remove compliance to Identifiable from the protocol and reintroduce it later in the View using cardsModel.cards.identified(by: \.id). Is there any better way out of this, that let me keep the Identifiable compliance at protocol level?

The only solution, beside adding Identifiable through .identified(by: \.id), is using the type erasure pattern. This uses 3 classes to box and hide the associated type and allows then to declare an array of AnyCard objects. The implementation is quite bulky and probably not worth it for my problem. But here it goes:
final class CardsModel<IdentifiedValue:CardProtocol>: BindableObject {
var cards: [AnyCard<IdentifiedValue>] = []
let didChange = PassthroughSubject<CardsModel, Never>()
}
protocol CardProtocol: Identifiable{
var id: Int32 { get set }
var firstName: String? { get set }
var lastName: String? { get set }
var email: String? { get set }
var phone: String? { get set }
}
struct TestCard: CardProtocol {
var id: Int32
var firstName: String?
var lastName: String?
var email: String?
var phone: String?
}
extension CardsModel where IdentifiedValue == TestCard {
convenience init(cards: [TestCard]) {
self.init()
self.cards = cards.map({ (card) -> AnyCard<TestCard> in
return AnyCard(card)
})
}
}
private class _AnyCardBase<IdentifiedValue>: CardProtocol {
init() {
guard type(of: self) != _AnyCardBase.self else {
fatalError("_AnyCardBase<Model> instances can not be created; create a subclass instance instead")
}
}
var id: Int32 {
get { fatalError("Must override") }
set { fatalError("Must override") }
}
var firstName: String? {
get { fatalError("Must override") }
set { fatalError("Must override") }
}
var lastName: String? {
get { fatalError("Must override") }
set { fatalError("Must override") }
}
var email: String? {
get { fatalError("Must override") }
set { fatalError("Must override") }
}
var phone: String? {
get { fatalError("Must override") }
set { fatalError("Must override") }
}
}
private final class _AnyCardBox<Concrete: CardProtocol>: _AnyCardBase<Concrete.IdentifiedValue> {
var concrete: Concrete
init(_ concrete: Concrete) {
self.concrete = concrete
}
override var id: Int32 {
get {
return concrete.id
}
set {
concrete.id = newValue
}
}
override var firstName: String? {
get {
return concrete.firstName
}
set {
concrete.firstName = newValue
}
}
override var lastName: String? {
get {
return concrete.lastName
}
set {
concrete.lastName = newValue
}
}
override var email: String? {
get {
return concrete.email
}
set {
concrete.email = newValue
}
}
override var phone: String? {
get {
return concrete.phone
}
set {
concrete.phone = newValue
}
}
}
final class AnyCard<IdentifiedValue>: CardProtocol {
private let box: _AnyCardBase<IdentifiedValue>
init<Concrete: CardProtocol>(_ concrete: Concrete) where Concrete.IdentifiedValue == IdentifiedValue {
box = _AnyCardBox(concrete)
}
var id: Int32 {
get {
return box.id
}
set {
box.id = newValue
}
}
var firstName: String? {
get {
return box.firstName
}
set {
box.firstName = newValue
}
}
var lastName: String? {
get {
return box.lastName
}
set {
box.lastName = newValue
}
}
var email: String? {
get {
return box.email
}
set {
box.email = newValue
}
}
var phone: String? {
get {
return box.phone
}
set {
box.phone = newValue
}
}
}
//NSManagedObject extention
extension Card:CardProtocol {}

A simple type-eraser solves that problem.
final class CardsModel: BindableObject {
var cards: [AnyCardModel] = [AnyCardModel]()
let didChange = PassthroughSubject<CardsModel, Never>()
}
protocol CardProtocol: Identifiable {
var id: Int { get set }
var firstName: String? { get set }
var lastName: String? { get set }
var email: String? { get set }
var phone: String? { get set }
}
class AnyCardModel: CardProtocol {
var _id: Int
var _firstName: String?
var _lastName: String?
var _email: String?
var _phone: String?
init<T: CardProtocol>(card: T) {
_id = card.id
_firstName = card.firstName
_lastName = card.lastName
_email = card.email
_phone = card.phone
}
var id: Int {
get { return _id }
set { _id = newValue}
}
var firstName: String? {
get { return _firstName }
set { _firstName = newValue}
}
var lastName: String? {
get { return _lastName }
set { _lastName = newValue}
}
var email: String?{
get { return _email }
set { _email = newValue}
}
var phone: String?{
get { return _phone }
set { _phone = newValue}
}
}

Related

Confirm to Equatable for Custom struct

I have below kind of response model, where the body is decided by another variable. How can i confirm equatable to this Model
public struct Model {
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType {
var items: [String]?
}
struct Response2: ResponseType {
var item: String?
}
What i want to achive:
extension Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
// How to equate the body?
}
}
When im trying to add Equatable to ResponseType protocol it says below error.
Protocol 'ResponseType' can only be used as a generic constraint because it has Self or associated type requirements
You need to implement == manually. swift doesn't know that body can only be two types, which are?
public struct Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
if lhs.type != rhs.type {
return false
}
if let lhsBody = lhs.body as? Response1, let rhsBody = rhs.body as? Response1 {
return lhsBody == rhsBody
} else if let lhsBody = lhs.body as? Response2, let rhsBody = rhs.body as? Response2 {
return lhsBody == rhsBody
} else {
return false
}
}
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType, Equatable {
var items: [String]?
}
struct Response2: ResponseType, Equatable {
var item: String?
}
It might be easier if you change Model into an enum:
enum Model: Codable, Equatable {
case type1(items: [String]?)
case type2(item: String)
var type: String {
switch self {
case .type1: return "type1"
case .type2: return "type2"
}
}
}
You probably need to change the Codable implementation so that it encodes and decodes the way you want to.

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
}
}

Passing data in PageViewControllers swift

I have a page controller where I added UIViewControllers and display a bunch of form in each viewcontroller. The issue I am facing now is that I need to get the data supplied in each of the forms and save it which is done in the last view controller. I have tried using delegates but the moment the next button is clicked, the previous value stored becomes nil and only the value of the latest VC is displayed. How can I pass data in this textfields. Any help is appritated.
My delegate
protocol NextDelegate: AnyObject {
func next(pageIndex: Int, model: CreatePropertyModel)
func previous(pageIndex: Int, model: CreatePropertyModel)
}
how I created array of VC
lazy var controllers: [UIViewController] = {
let descVC = DescVC()
descVC.delegate = self
let priceVC = PriceVC()
priceVC.delegate = self
let featuresVC = FeaturesVC()
featuresVC.delegate = self
let picturesVC = PicturesVC()
picturesVC.delegate = self
return [descVC, priceVC, featuresVC, picturesVC]
}()
Model Example
class CreatePropertyModel: DictionaryEncodable {
var title: String?
var desc: String?
var property_type_id: Int?
var property_sub_type_id: Int?
var location_id: Int?
var currency: String?
var price: Int?
}
For all your steps, store it in a singleton.
protocol Answer {
var isDone: Bool { get }
}
class Answer1: Answer {
static public let updatedNotification = Notification.Name("Answer1Updated")
var name: String? {
didSet {
NotificationCenter.default.post(name: Answer1.updatedNotification, object: nil)
}
}
var isDone: Bool {
return name != nil
}
}
class Answer2: Answer {
var age: Int?
var isDone: Bool {
return age != nil
}
}
class Form {
static let shared = Form()
var answers: [Answer] = [Answer1(), Answer2()]
var isDone: Bool {
return answers.allSatisfy { $0.isDone == true }
}
private init() {}
func reset() {
answers = [Answer1(), Answer2()]
}
var answer1: Answer1? {
return Form.shared.answers.filter { $0 is Answer1 }.first as? Answer1
}
var answer2: Answer2? {
return Form.shared.answers.filter { $0 is Answer2 }.first as? Answer2
}
}
Then, in your view controller, read / write values like this.
class MyViewControllerForAnswer1: UIViewController {
var answer: Answer1? {
return Form.shared.answer1
}
var name: String? {
get {
return answer?.name
}
set {
answer?.name = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(answerUpdated(notification:)), name: Answer1.updatedNotification, object: nil)
}
#objc func answerUpdated(notification: Notification) {
// Update your content
}
}

Realm add(_, update: true) removes existing relationships

I am facing an issue where I am unable to keep existing relationships after calling add(_, update: true) function.
I wrote a TaskSync class that is responsible for creating/updating Task objects:
class TaskSync: ISync {
typealias Model = Task
func sync(model: Task) {
let realm = try! Realm()
let inWrite = realm.isInWriteTransaction
if !inWrite {
realm.beginWrite()
}
let _task = realm.object(ofType: Task.self, forPrimaryKey: model.id)
// Persist matches as they are not getting fetched with the task
if let _task = _task {
print("matches: \(_task.matches.count)")
model.matches = _task.matches
}
realm.add(model, update: true)
if _task == nil {
var user = realm.object(ofType: User.self, forPrimaryKey: model.getUser().id)
if (user == nil) {
user = model.getUser()
realm.add(user!, update: true)
}
user!.tasks.append(model)
}
if !inWrite {
try! realm.commitWrite()
}
}
func sync(models: List<Task>) {
let realm = try! Realm()
try! realm.write {
models.forEach { task in
sync(model: task)
}
}
}
}
When a model is to be synced, I check if it already exists in the Realm and if so, I fetch it and try to include the matches property as this one is not included in the model.
Right before the call realm.add(model, update: true), model contains list of matches, however right after the realm.add is executed, the matches list is empty.
Here are the two models:
class Task: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var title: String = ""
dynamic var desc: String = ""
dynamic var price: Float = 0.0
dynamic var calculatedPrice: Float = 0.0
dynamic var location: String = ""
dynamic var duration: Int = 0
dynamic var date: String = ""
dynamic var category: Category?
dynamic var currency: Currency?
dynamic var longitude: Double = 0.0
dynamic var latitude: Double = 0.0
dynamic var state: Int = 0
dynamic var userId: Int = 0
// Existing images
var imagesExisting = List<URLImage>()
// New images
var imagesNew = List<Image>()
// Images deleted
var imagesDeleted = List<URLImage>()
private let users = LinkingObjects(fromType: User.self, property: "tasks")
var user: User?
var matches = List<Match>()
dynamic var notification: Notification?
override static func ignoredProperties() -> [String] {
return ["imagesExisting", "imagesNew", "imagesDeleted", "user", "tmpUser"]
}
override static func primaryKey() -> String? {
return "id"
}
func getImageMain() -> URLImage? {
for image in imagesExisting {
if image.main {
return image
}
}
return imagesExisting.first
}
func getSection() -> Int {
return state
}
func getSectionFieldName() -> String? {
return "state"
}
func getId() -> Int {
return id
}
func getURL() -> URL? {
if let image = getImageMain() {
return image.getResizedURL()
}
return nil
}
func getState() -> TaskOwnState {
return TaskOwnState(rawValue: state)!
}
func getUser() -> User {
return (user != nil ? user : users.first)!
}
}
class Match: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var state: Int = -1
dynamic var priorityOwnRaw: Int = 0
dynamic var priorityOtherRaw: Int = 0
dynamic var user: User!
var messages = List<Message>()
private let tasks = LinkingObjects(fromType: Task.self, property: "matches")
var task: Task?
dynamic var notification: Notification?
override static func primaryKey() -> String? {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["task"]
}
func getId() -> Int {
return id
}
func getSection() -> Int {
return 0
}
func getURL() -> URL? {
if let image = user.getImageMain() {
return image.getResizedURL()
}
return nil
}
func getPriorityOwn() -> PriorityType {
if priorityOwnRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getPriorityOther() -> PriorityType {
if priorityOtherRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getSectionFieldName() -> String? {
return nil
}
func getTask() -> Task {
return (task != nil ? task : tasks.first)!
}
}
I spent hours trying to figure out why I am unable to keep the matches relationship when updating the task. Every advice will be highly appreciated!
This question was also asked upon Realm's GitHub issue tracker. For posterity, here is the solution.
List properties should always be declared as let properties, as assigning to them does not do anything useful. The correct way to copy all objects from one List to another is model.tasks.append(objectsIn: _user.tasks).

Parse PFSubclassing in Swift of Object type

I'm pretty new to iOS/Swift/Parse and I'm trying to build a model of a class using PFSubclassing.
The data I'm trying to represent should look something like this
{
text: ""
location : {
name: "",
longitude: "",
latitude: ""
}
}
So fare the model I'm have is
class LocationModel {
var name: String?
var longitude: Float?
var latitude: Float?
}
class PostModel: PFObject, PFSubclassing {
class func parseClassName() -> String! {
return "Post"
}
#NSManaged var text: String?
var location: LocationModel?
}
The test property is being saved successfully but I'm unable to get the location properties to save.
The code I'm using to save a record to parse is
var test = PostModel()
test.location?.name = "ESB"
test.location?.latitude = 1
test.location?.longitude = 1
test.text = "This is a test post to see if this works!"
test.saveEventually { (success: Bool, error: NSError!) -> Void in
println(error)
println(success)
}
I did a lot of digging online but I'm unable to find a solution on how to represent an Object datatype in Swift using Parse PFSubclassing
Any help would be greatly appreciated.
Thank you
Here's my solution:
I will create a Hero object for example.
class Hero: PFObject, PFSubclassing {
#NSManaged var strengthPoint: Double
#NSManaged var name: String
static func parseClassName() -> String {
return "Hero"
}
init(strengthPoint: Double, name: String) {
super.init()
self.strengthPoint = strengthPoint
self.name = name
}
init(pfObject: PFObject) {
super.init()
self.strengthPoint = pfObject.object(forKey: "strengthPoint") as! Double
self.name = pfObject.object(forKey: "name") as! String
}
override init() {
super.init()
}
override class func query() -> PFQuery<PFObject>? {
let query = PFQuery(className: self.parseClassName())
query.order(byDescending: "createdAt")
query.cachePolicy = .networkOnly
return query
}
}
Now, after defining your model, you can use these methods to store and retrieve
Create your object in server
func createHero() {
let hero = Hero(strengthPoint: 2.5, name: "Superman")
hero.saveInBackground { (isSuccessful, error) in
print(error?.localizedDescription ?? "Success")
}
}
Retrieve object from server
func retrieveHero() {
let query = Hero.query()
query?.getFirstObjectInBackground(block: { (object, error) in
if error != nil {
print(error?.localizedDescription ?? "Something's wrong here")
} else if let object = object {
let hero = Hero(pfObject: object)
print(hero.strengthPoint) // 2.5
print(hero.name) // Superman
}
})
}
I have seen several different methods for PFSubclassing in Swift 1.2, but the following works best for me:
To begin with, make sure that you have the following in your Objective-C Bridging Header:
#import <Parse/PFObject+Subclass.h>
Here is a very basic example of subclassing PFObject:
import Foundation
import Parse
class Car: PFObject, PFSubclassing {
override class func initialize() {
self.registerSubclass()
}
static func parseClassName() -> String {
return "Car"
}
#NSManaged var model: String
#NSManaged var color: String
#NSManaged var yearManufactured: Int
}
So in your case, this would be:
class PostModel: PFObject, PFSubclassing {
override class func initialize() {
self.registerSubclass()
}
static func parseClassName() -> String {
return "Post"
}
#NSManaged var text: String?
}
Concerning your LocationModel...I'm a bit confused as to what exactly you are trying to accomplish with that. I hope this helps.

Resources