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
}
}
Related
I started a new project and tried to implement Realm but I can't get it to work properly. My problem is that when I kill my app and relaunch it, all my previously added objects have disappeared and I get empty results from realm.objects.
class RealmManager {
static let shared = RealmManager()
let realm: Realm
init() {
realm = try! Realm()
}
func write(_ completion: ()->Void) {
do {
try realm.write() {
completion()
}
} catch {
print(error)
}
}
func add(_ object: Object) {
realm.add(object)
}
func delete(_ object: Object) {
realm.delete(object)
}
func objects<Element>(_ type: Element.Type) -> Results<Element> where Element : Object {
return realm.objects(type)
}
}
I created this singleton so I don't have to repeat this realm = try! Realm()everywhere in my code. I have this exact same class in another project which works fine.
My model looks like this :
class PrepFile: Object {
#objc dynamic var creationDate: Date = Date()
#objc dynamic var lastModificationDate: Date = Date()
#objc dynamic var title: String = "Pas de titre"
#objc dynamic var activityKind: String = ""
#objc dynamic var seanceNumber: Int = 0
#objc dynamic var level: String = ""
#objc dynamic var duration: Int = 0
#objc dynamic var date: Date = Date()
#objc dynamic var cycle: Int = 0
#objc dynamic var mainGoal: String = ""
#objc dynamic var specificGoal: String = ""
#objc dynamic var material: String = ""
#objc dynamic var isDraft: Bool = true
convenience init(title: String? = nil, activityKind: String? = nil, seanceNumber: Int? = nil, level: String? = nil, duration: Int? = nil, date: Date? = nil, cycle: Int? = nil, mainGoal: String? = nil, specificGoal: String? = nil, material: String? = nil, phases: [Phase] = [], isDraft: Bool = true) {
self.init()
if let tt = title {
self.title = tt
}
if let ak = activityKind {
self.activityKind = ak
}
if let sn = seanceNumber {
self.seanceNumber = sn
}
if let lv = level {
self.level = lv
}
if let dt = duration {
self.duration = dt
}
if let dt = date {
self.date = dt
}
if let cl = cycle {
self.cycle = cl
}
if let mg = mainGoal {
self.mainGoal = mg
}
if let sg = specificGoal {
self.specificGoal = sg
}
if let mt = material {
self.material = mt
}
self.isDraft = isDraft
}
required init() {
}
}
Then in my VC here's what I do :
class PrepFileListViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
lazy var prepFiles: Results<PrepFile> = { RealmManager.shared.objects(PrepFile.self) }()
var completePrepFiles: [PrepFile] = []
var draftPrepFiles: [PrepFile] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
RealmManager.shared.write {
for file in prepFiles {
RealmManager.shared.delete(file)
}
}
RealmManager.shared.write() {
RealmManager.shared.add(PrepFile(title: "Fiche de prep 1"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 2"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 3"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 4"))
RealmManager.shared.add(PrepFile(title: "Fiche de prep 5"))
}
}
override func viewWillAppear(_ animated: Bool) {
prepFiles = RealmManager.shared.objects(PrepFile.self)
completePrepFiles = prepFiles.filter({ !$0.isDraft })
draftPrepFiles = prepFiles.filter({ $0.isDraft })
tableView.reloadData()
}
}
Now when I run this, it works fine. My PrepFile are added to Realm and I retrieve them alright with my RealmManager.shared.objects(PrepFile.self). Now, I comment the part where I delete/add my files in the viewDidLoad and I get nothing. I don't get empty objects from RealmManager.shared.objects(PrepFile.self), I get an empty result like nothing was ever saved there.
What am I doing wrong ?
I am using Xcode 12 and running my app on an iPhone 11 / 13.3 simulator. Realm version is 5.5.0.
Hmm from what I can guess, you either have somewhere else a configuration for realm to be "inMemoryIdentifier" or you do not set the proper configuration with fileURL at beggining of the app.
Configuration local realm: https://realm.io/docs/swift/latest/#realms
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}
}
}
I'm trying to pass data between viewControllers, but something seems wrong.
The first viewController I want to set the "Bool" to the protocol function to be able to recover in the other screen. What am I doing wrong, I always used protocols but at this time I got in trouble.
That's how I'm doing that:
//
// ComboBoxNode.swift
//
import Foundation
import SWXMLHash
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool)
}
class ComboBoxNode: FormControlNode, IFormControlDataSource {
var listType: String?
var dataSource: String?
var dataSourceValue: String?
var dataSourceText: String?
var hasCustomOption:Bool?
var customOptionText: String?
var ctrlDataSourceType: String?
var parameters = [ParameterNode]()
var staticList: FormControlStaticListNode?
var delegate:ComboBoxNodeDelegate?
override init(indexer: XMLIndexer) {
super.init(indexer: indexer)
guard let element = indexer.element else {
preconditionFailure("Error")
}
let isCustomOption = element.bool(by: .hasCustomOption) ?? hasCustomOption
if isCustomOption == true {
self.delegate?.getCustomOption(data: hasCustomOption!)
}
self.readFormControlDataSource(indexer: indexer)
}
override func accept<T, E: IViewVisitor>(visitor: E) -> T where E.T == T {
return visitor.visit(node: self)
}
}
That's how I'm trying to recover on next screen:
// FormPickerViewDelegate.swift
import Foundation
import ViewLib
import RxSwift
class FormPickerViewDelegate: NSObject {
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
private var controlViewModel: FormControlViewModel
private var customText:Bool?
private var PickerNodeDelegate:ComboBoxNodeDelegate?
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
}
func getItemByValue(_ value: Any) -> (AnyHashable, String)? {
if value is AnyHashable {
let found = items.value.filter {$0.value == value as! AnyHashable}
if found.count >= 1 {
return found[0]
}
}
return nil
}
}
extension FormPickerViewDelegate:ComboBoxNodeDelegate {
func getCustomOption(data: Bool) {
customText = data
}
}
Instead of setting PickerNodeDelegate = self in didSet {} closure
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
Assign it in your init() function instead
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
PickerNodeDelegate = self
}
Note, your should declare your delegate to be weak also, since it's a delegate, your protocol should conform to be a class type in order to be weakified.
protocol ComboBoxNodeDelegate: class
...
weak var delegate: ComboBoxNodeDelegate?
Here is an example, hope it helps!
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool) -> String
}
class ViewOne:ComboBoxNodeDelegate {
var foo:Bool = false
var bar:String = "it works!"
/** Return: String */
func getCustomOption(data:Bool) -> String { //conform here to protocol
// do whatever you wanna do here ...example
self.foo = data // you can set
return bar // even return what you want
}
//initialize
func initalizeViewTwo() {
let v2 = ViewTwo()
v2.delegate = self //since `self` conforms to the ComboBoxNodeDelegate protcol you are allowed to set
}
}
class ViewTwo {
var delegate:ComboBoxNodeDelegate?
func getCustomOption_forV1() {
let view2_foo = delegate.getCustomOption(data:true)
print(view2_foo) // should print "it works!"
}
}
All parameters passed around in Swift are constants -- so you cannot change them.
If you want to change them in a function, you must declare your protocol to pass by reference with inout:
protocol ComboBoxNodeDelegate {
func getCustomOption(data: inout Bool)
}
Note: you cannot pass a constant (let) to this function. It must be a variable -- which I see you are doing!
In my struct Main Model (MainModel.swift):
private var charLevel: Int = 1
mutating func setCharLevel(_ value: Int) {
charLevel = value
}
var getCharLevel: Int? {
get {
return charLevel
}
}
In my firstViewController.swift:
private var MyModel = MainModel()
let sb = UIStoryBoard(name: "Main", bundle: nil)
#IBAction func addLevel(_ sender: UIButton){
if let secondVC = sb.instantiateViewController(withIdentifier: "SecondVC") as? SecondViewController {
self.present(secondVC, animated: true, completion: nil)
let charNewLevel = MyModel.getCharLevel! + 1
MyModel.setCharlevel(charNewLevel)
}
}
The SecondVC has only one label that shows charLevel from MainModel.swift and a button with self.dismiss that returns to firstViewController. So it just goes in a loop / circle. My problem is the level only goes to 2, even if I do 3+ runs, what am I missing? I would like it to increment by 1 every run (1st run: 2, 2nd run: 3, 3rd run: 4 and so on). Thank you from a learning student.
EDIT:
firstViewController -> ViewController (original name)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var battleDescription: UILabel!
#IBOutlet weak var heroHealthLabel: UILabel!
#IBOutlet weak var enemyHealthLabel: UILabel!
#IBOutlet weak var byBattleDescription: UILabel!
private var QuestModel = MainModelQuest()
let sb = UIStoryboard(name: "Main", bundle: nil)
override func viewDidLoad() {
super.viewDidLoad()
QuestModel.setHeroHealth(1000)
QuestModel.setHeroMaxHealth(1000)
QuestModel.setEnemyHealth(150)
if let initialTempHeroHealth = QuestModel.getHeroHealth {
checkHeroLabel = initialTempHeroHealth
}
if let initialTempEnemyHealth = QuestModel.getEnemyHealth {
checkEnemyLabel = initialTempEnemyHealth
}
}
var checkHeroLabel: Int {
get {
return Int(heroHealthLabel.text!)!
} set {
heroHealthLabel.text = String(newValue)
}
}
var checkEnemyLabel: Int {
get {
return Int(enemyHealthLabel.text!)!
} set {
enemyHealthLabel.text = String(newValue)
}
}
#IBAction func attackOption(_ sender: UIButton) {
let tempHeroRandomX = arc4random_uniform(5) + 1
let tempHeroRandomY = arc4random_uniform(5) + 1
let tempEnemyRandomX = arc4random_uniform(5) + 1
let tempEnemyRandomY = arc4random_uniform(5) + 1
if tempHeroRandomX == tempHeroRandomY {
battleDescription.text = "Hero's attacked missed."
QuestModel.setHeroDamage(0)
} else {
if let chosenAttack = sender.currentTitle {
switch chosenAttack {
case "Attack1":
// 30 - 40
let tempHeroRandAttack = arc4random_uniform(11) + 30
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
case "Attack2":
// 20 - 30
let tempHeroRandAttack = arc4random_uniform(11) + 20
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
case "Attack3":
// 10 - 20
let tempHeroRandAttack = arc4random_uniform(11) + 10
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
case "Attack4":
// 1 - 10
let tempHeroRandAttack = arc4random_uniform(10) + 1
QuestModel.setHeroDamage(Int(tempHeroRandAttack))
default:
break
}
}
}
if tempEnemyRandomX == tempEnemyRandomY {
byBattleDescription.text = "The enemy's attacked missed"
QuestModel.setEnemyDamage(0)
} else {
let tempEnemyRandAttack = arc4random_uniform(11) + 10
QuestModel.setEnemyDamage(Int(tempEnemyRandAttack))
}
if QuestModel.getEnemyDamage! > QuestModel.getHeroHealth! {
QuestModel.setHeroHealth(0)
if let heroKilledVC = sb.instantiateViewController(withIdentifier: "HeroFaintedVC") as? ThirdViewController {
self.present(heroKilledVC, animated: true, completion: nil)
}
} else {
let heroDamage = QuestModel.getHeroHealth! - QuestModel.getEnemyDamage!
QuestModel.setHeroHealth(heroDamage)
}
if QuestModel.getHeroDamage! > QuestModel.getEnemyHealth! {
QuestModel.setEnemyHealth(0)
if let enemyKilledVC = sb.instantiateViewController(withIdentifier: "EnemyFaintedVC") as? SecondViewController {
self.present(enemyKilledVC, animated: true, completion: nil)
let checkTotalExpi = QuestModel.getHeroExpi! + 30
if checkTotalExpi >= QuestModel.getHeroMaxExpi! {
let heroNewLevel = QuestModel.getHeroLevel! + 1
QuestModel.setHeroLevel(heroNewLevel)
let newHeroMaxExpi = QuestModel.getHeroMaxExpi! * 2
QuestModel.setHeroMaxExpi(newHeroMaxExpi)
QuestModel.setHeroExpi(0)
}
enemyKilledVC.infoObject = QuestModel.getHeroLevel
}
} else {
let enemyDamage = QuestModel.getEnemyHealth! - QuestModel.getHeroDamage!
QuestModel.setEnemyHealth(enemyDamage)
}
if QuestModel.getHeroDamage! > 0 {
if QuestModel.getEnemyHealth! <= 0 {
battleDescription.text = "Hero dealt \(QuestModel.getHeroDamage!) damage and the enemy fainted."
} else {
if let attackName = sender.currentTitle {
battleDescription.text = "Hero used " + attackName + " and dealt \(QuestModel.getHeroDamage!) damage."
}
}
}
if QuestModel.getEnemyDamage! > 0 {
if QuestModel.getHeroHealth! <= 0 {
byBattleDescription.text = "the enemy dealt \(QuestModel.getEnemyDamage!) and the hero fainted."
} else {
byBattleDescription.text = "Enemy attacked and dealt \(QuestModel.getEnemyDamage!) damage."
checkHeroLabel = QuestModel.getHeroHealth!
checkEnemyLabel = QuestModel.getEnemyHealth!
}
}
}
}
MainModel -> MainModelQuest (original name)
import Foundation
struct MainModelQuest {
// Hero Properties
private var heroHealth: Int?
private var heroMaxHealth: Int?
private var heroMana: Int?
private var heroMaxMana: Int?
private var heroStamina: Int?
private var heroMaxStamina: Int?
private var heroLevel: Int = 1
private var heroDamage: Int?
private var heroMaxDamage: Int? // Not yet used
private var heroExpi: Int = 25
private var heroMaxExpi: Int = 30
private var heroGold: Int? // Not yet used
private var heroGainedGold: Int? // Not yet used
// Enemy Properties
private var enemyHealth: Int?
private var enemyDamage: Int?
private var enemyLevel: Int? // Not yet used
mutating func setHeroHealth(_ value: Int) {
heroHealth = value
}
mutating func setHeroMaxHealth(_ value: Int) {
heroMaxHealth = value
}
mutating func setHeroLevel(_ value: Int) {
heroLevel = value
}
mutating func setHeroDamage(_ value: Int) {
heroDamage = value
}
mutating func setHeroExpi(_ value: Int) {
heroExpi = value
}
mutating func setHeroMaxExpi (_ value: Int) {
heroMaxExpi = value
}
mutating func setEnemyHealth(_ value: Int) {
enemyHealth = value
}
mutating func setEnemyDamage(_ value: Int) {
enemyDamage = value
}
var getHeroHealth: Int? {
get {
return heroHealth
}
}
var getHeroMaxHealth: Int? {
get {
return heroMaxHealth
}
}
var getHeroLevel: Int? {
get {
return heroLevel
}
}
var getHeroDamage: Int? {
get {
return heroDamage
}
}
var getHeroExpi: Int? {
get {
return heroExpi
}
}
var getHeroMaxExpi: Int? {
get {
return heroMaxExpi
}
}
var getEnemyHealth: Int? {
get {
return enemyHealth
}
}
var getEnemyDamage: Int? {
get {
return enemyDamage
}
}
}
SeconfViewController
import UIKit
class SecondViewController: UIViewController {
var infoObject: Int? {
didSet {
enemyDefeatedObject.text = "Lv. " + String(infoObject!)
}
}
#IBOutlet weak var enemyDefeatedObject: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
// Return to map
#IBAction func BacktoMapVC(_ sender: Any) {
let sb = UIStoryboard(name: "Main", bundle: nil)
if let mapVC = sb.instantiateViewController(withIdentifier: "MapVC") as? MapViewController {
self.present(mapVC, animated: true, completion: nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I understand that this may be a little bulk of code but this completes what I have been practicing so far, I may have incorrect conventions here but you are more than welcome to correct me on those point. My problem is upon running the program the 1st time, it gets correct level which is 2, 2nd run and conceding runs after are still two. Any suggestion is welcome. Thanks you.
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).