Realm passing selected category values - swift

I have a task app that I am working on. users can create task and in turn create items under their categories. I am using realm as my data storage but I want to be able to pass some details from the selected category to the create items viewcontroller. I decided to print the selected category to the console but it prints nil and I dont know why. Below is my code.
class CategoryModel: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name: String = ""
#objc dynamic var color: String = ""
#objc dynamic var isCompleted = false
let items = List<TodoListModel>()
override static func primaryKey() -> String? {
return "id"
}
}
Items VC
var selectedCategory: CategoryModel?
override func viewDidLoad() {
super.viewDidLoad()
print("Selected Category: \(String(describing: selectedCategory))")
}
I do not know why it keeps printing nil

Because you don't give it a value when you show the itemsVC
let item = ItemsVC() // if VC is inside IB use self.storyboard?.instantiate.....
item.selectedCategory = // set here or inside prepareForSegue if you use segues
// here present

You can parse in this way;
Items VC
let realm = try! Realm()
var category:Results< CategoryModel>?
override func viewDidLoad() {
super.viewDidLoad()
loadCategory()
}
func loadCategory(){
category = realm.objects(CategoryModel.self)
//tableView.reloadData() for example
}
//category?[indexPath.row].name for example using

Related

Primary key can't be changed after an object is inserted

I'm very new to programming.
I am trying to update an object in my Realm database but I get always an error.
I have tried to find the issue but I can't find anyone with a similar issue.
What I'm trying to do is:
I have a Game-Score-App.
It should display the names on Tab1 and on the Tab2 I want to give the user the ability, to change the names of the players. As soon as the ViewDidDisappear I want to write the changes to Realm.
I already figured out how to update the names in the database. And it works properly the first time.
But as soon as I go a second time on the Tab2 and go back to Tab1 again, I get the message "Primary key can't be changed after an object is inserted."
Any Ideas?
class Games: Object {
#objc dynamic var game_id = UUID().uuidString
#objc dynamic var gameName: String = ""
var playerNames = List<String>()
override class func primaryKey() -> String? {
return "game_id"
}
}
class FirstPageVC: UIViewController {
#IBOutlet var playerNameLabels: [UILabel]!
#IBOutlet weak var gameNameLabel: UILabel!
let realm = try! Realm()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
let games = realm.objects(Games.self)
gameNameLabel.text = games[0].gameName
for i in 0...playerNameLabels.count - 1 {
playerNameLabels[i].text = games[0].playerNames[i]
}
}
}
class SecondPageVC: UIViewController {
#IBOutlet var playerNameTextbox: [UITextField]!
#IBOutlet weak var gameNameTextbox: UITextField!
#IBOutlet weak var numberOfIndex: UITextField!
let realm = try! Realm()
var playerNames: [String] = []
var gameName: String = ""
var game = Games()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
if realm.objects(Games.self).count != 0 {
let games = realm.objects(Games.self)
gameNameTextbox.text = games[0].gameName
for i in 0...playerNameTextbox.count - 1 {
playerNameTextbox[i].text = games[0].playerNames[i]
}
}
}
#IBAction func addButton(_ sender: UIButton) {
gameName = gameNameTextbox.text!
for i in 0...playerNameTextbox.count - 1 {
playerNames.append(playerNameTextbox[i].text!)
}
let items = realm.objects(Games.self)
let number = Int(numberOfIndex.text!)
game.game_id = items[number!].game_id
game.gameName = gameName
game.playerNames.append(objectsIn: playerNames)
try! realm.write {
realm.add(game, update: .modified)
}
}
}
The problem is your Realm object structure. Anything that could possibly ever be changed should not be used as a primary key.
Also note from the Realm Documentation
Once an object with a primary key is added to a Realm, the primary key
cannot be changed.
To expand on that, it's often best practice to disassociate an objects key (e.g. primary key) from the rest of the properties of an object.
Here's how to do that
class Games: Object{
#objc dynamic var game_id = UUID().uuidString
#objc dynamic var gameName: String = ""
var playerNames = List<String>()
override class func primaryKey() -> String? {
return "game_id"
}
}
UUID().uuidString will generate a unique string for every object that's created and will look something like this string
CDEA69EA-AC84-4465-ABE3-DDA29D31B925
Once the object is created, you can use it to load that specific object or update it's properties.
See Objects with Primary Keys
Here's how to change the game name
let item = realm.object(ofType: Game.self, forPrimaryKey: "CDEA69EA-AC84-4465-ABE3-DDA29D31B925")!
try! realm.write {
game.gameName = "Pwn You!"
}
Try this solution:
Replace the code in viewDidDisappear after the end of for loop with the following code:
if let gameInRealm = realm.objects(Game.self).first{
try! realm.write {
gameInRealm.gameName = gameName
gameInRealm.playerNames = playerNames
}
}else{
game.gameName = gameName
game.playerNames.append(objectsIn: playerNames)
realm.add(game)
}
Explanation (if needed):
The code changes the existing Game properties in case a Game object exists. Otherwise, it creates a new one with the new properties.
Therefore, the else statement should get executed the first time you leave SecondPageVC, and then the if statement will get triggered every other time you leave SecondPageVC.

Deleting Objects with subclasses in Swift Realm

I have a really simple database in Swift Realm for a todo app:
Items and their parent Categories.
The user can delete both the Items and the Categories with a simple swipe action. The action works fine, there are no issues when deleting Items. If I delete a Category, that works too, but I can still see the Items in the Realm Browser, those remain in the database even though there are no parent anymore. Obviously the user can't see these, they are doing nothing but still, it would be better to get rid of these with the parent Category. Are there any simple ways to do this?
class Category: Object{
#objc dynamic var name: String = ""
#objc dynamic var color: String = ""
#objc dynamic var order = 0
let items = List<Item>()
override static func primaryKey() -> String? {
return "order"
}
static func incrementalIDCat() -> Int {
let realm = try! Realm()
return (realm.objects(Category.self).max(ofProperty: "order") as Int? ?? 0) + 1
}
}
class Item: Object {
#objc dynamic var title: String = ""
#objc dynamic var done: Bool = false
#objc dynamic var dateCreated: Date?
#objc dynamic var order = 0
var parentCategory = LinkingObjects(fromType: Category.self, property: "items")
override static func primaryKey() -> String? {
return "order"
}
static func incrementalIDItem() -> Int {
let realm = try! Realm()
return (realm.objects(Item.self).max(ofProperty: "order") as Int? ?? 0) + 1
}
}
override func updateModel(at indexPath: IndexPath) {
if let categoryForDeletion = self.categories?[indexPath.row] {
do {
try self.realm.write {
self.realm.delete(categoryForDeletion)
}
} catch {
print("Error deleting category, \(error)")
}
}
tableView.reloadData()
}
You just delete items first.
self.realm.delete(categoryForDeletion.items)
self.realm.delete(categoryForDeletion)
Or, with this extension, you can do this.
self.realm.delete(categoryForDeletion, cascading: true)

Cannot get value of switch from different VC Swift

I want to access switch value with the code below. I get nil value. From the second View controller I get settings value to display content according to the value - englishRef.
SetConViewController is my settings view controller from where I want to get my value.
let languageSettingsRef = SetConvViewController()
var englishRef = ""
// Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
if languageSettingsRef.swithEnglish.isOn{
englishRef = "yes"
}
In the View Controller below, there are switches for the settings
class SetConvViewController: UIViewController {
var conversationSettingsReference: DatabaseReference!
let userID = Auth.auth().currentUser?.uid
var engS = "engS"
var engGoster = ""
#IBOutlet weak var swithEnglish: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let eng2 = defaults.value(forKey: engS) {
swithEnglish.isOn = eng2 as! Bool
}
}
let defaults = UserDefaults.standard
#IBAction func switchEng(_ sender: UISwitch) {
defaults.set(sender.isOn, forKey: engS)
}
}
I get nil when I want to access value from this class.
First It's nil because you don't load the VC from storyboard or xib
Second this line
let languageSettingsRef = SetConvViewController()
is not the presented one , so use delegate or any other observing technique to get current value of the presented VC
//
class SettingsViewController: UIViewController {
var setVc:SetConvViewController?
}
when you present SettingsViewController with with segue / push , set
settInstance.setVc = self
then ask the var about the switch value
if self.setVc.swithEnglish.isOn{
englishRef = "yes"
}
Making the var as Bool is another better option

Call a func from another class in swift

I would like to call a function which is coded on another class.
So far I have made a struct on the file structs.swift for my data:
struct defValues {
let defCityName: String
let loadImages: Bool
init(defCity: String, loadImgs: Bool){
self.defCityName = defCity
self.loadImages = loadImgs
}
}
I have made the file Defaults.swift containing:
import Foundation
class DefaultsSet {
let cityKey: String = "default_city"
let loadKey: String = "load_imgs"
func read() -> defValues {
let defaults = NSUserDefaults.standardUserDefaults()
if let name = defaults.stringForKey(cityKey){
print(name)
let valuesToReturn = defValues(defCity: name, loadImgs: true)
return valuesToReturn
}
else {
let valuesToReturn = defValues(defCity: "No default city set", loadImgs: true)
return valuesToReturn
}
}
func write(city: String, load: Bool){
let def = NSUserDefaults.standardUserDefaults()
def.setObject(city, forKey: cityKey)
def.setBool(load, forKey: loadKey)
}
}
in which I have the two functions read, write to read and write data with NSUsersDefault respectively.
On my main ViewController I can read data with:
let loadeddata: defValues = DefaultsSet().read()
if loadeddata.defCityName == "No default city set" {
defaultCity = "London"
}
else {
defaultCity = loadeddata.defCityName
defaultLoad = loadeddata.loadImages
}
But when I try to write data it gives me error. I use this code:
#IBOutlet var settingsTable: UITableView!
#IBOutlet var defaultCityName: UITextField!
#IBOutlet var loadImgs: UISwitch!
var switchState: Bool = true
#IBAction func switchChanged(sender: UISwitch) {
if sender.on{
switchState = true
print(switchState)
}else {
switchState = false
print(switchState)
}
}
#IBAction func saveSettings(sender: UIButton) {
DefaultsSet.write(defaultCityName.text, switchState)
}
You need an instance of the DefaultsSet class
In the view controller add this line on the class level
var setOfDefaults = DefaultsSet()
Then read
let loadeddata = setOfDefaults.read()
and write
setOfDefaults.write(defaultCityName.text, switchState)
The variable name setOfDefaults is on purpose to see the difference.
Or make the functions class functions and the variables static variables and call the functions on the class (without parentheses)
From the code you posted, it seems you either need to make the write method a class method (just prefix it with class) or you need to call it on an instance of DefaultsSet: DefaultsSet().write(defaultCityName.text, switchState).
Another issue I found is that you also need to unwrapp the value of the textField. Your write method takes as parameters a String and a Bool, but the value of defaultCityName.text is an optional, so String?. This results in a compiler error.
You can try something like this:
#IBAction func saveSettings(sender: UIButton) {
guard let text = defaultCityName.text else {
// the text is empty - nothing to save
return
}
DefaultsSet.write(text, switchState)
}
This code should now compile and let you call your method.
Let me know if it helped you solve the problem

acessing variables from one class to another in swift

In the app the user types in infomations as integers into two different textfields. After a action button click the user now access two other textfields which the user again types data as integers. Now after a click on a button I want a label's text to show this typed in information from the two situations earlier
However in my code, I can't access my variables (the information from the user input) because this label's text is in a new class and the user input variables are stored in two other classes).
How can I access these variables in my new class where my label is?
An example of my code:
#IBOutlet var nextStagetwo: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func check(sender: AnyObject) {
var strValue = boxShots.text
var floatValue = Double((strValue as! NSString).integerValue)
var strValue2 = boxMakes.text
var floatValue2 = Double((strValue2 as! NSString).integerValue)
var stage = "stage 2"
var procent = floatValue2 / floatValue * 100
enter code here
I now want to access my variable procent in this class:
class stagethree: UIViewController {
#IBOutlet var stats: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func check(sender: AnyObject) {
// this is where I want to write: stats.text = "\(procent)"
// however I can't, because it doesn't register the variable "procent" because it is in another class.
You can either make a global store that stores the values for you
struct Store {
static var valueOne:Int?
}
and then in your view controllers you can set those values
Store.valueOne = 1
Other option would be to send those integers to the next view controller in your
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destinationController = segue.destinationViewController as? SecondController {
destinationController.valueOne = valueOne
}
}