Getting nil from Enum Swift - ios

I have created a class
class NewTabContainerModal {
var newContainerDate = Date()
var newContainerSelectedFilter : NewAllErrorFIlter = .Yearly
func resetModal () {
newContainerSelectedFilter = .Yearly
newContainerDate = Date()
}
}
I have created an enum to get the values from it
enum NewAllErrorFIlter : String {
case Monthly = "2"
case Yearly = "1"
case Daily = "3"
}
Now in my ViewController class I have created a variable
var newContainerModal: NewTabContainerModal?
And in viewWillAppear I am trying to print the value of the enum like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let newContainerModelData = newContainerModal?.newContainerSelectedFilter
print(newContainerModelData)
}
but it is giving me nil instead of yearly value. I don't understand what I am doing wrong. Please help?

Its because newContainerModal in your Viewcontroller is nil so newContainerModal?.newContainerSelectedFilter also gonna be a nil change it with
var newContainerModal: NewTabContainerModal = NewTabContainerModal()
In addition
let newContainerModelData = newContainerModal.newContainerSelectedFilter
print(newContainerModelData)
will print Yearly. to get that value use newContainerModelData.rawValue

Related

Swift NavigationBar Press "Back" to get values, why?

I am using some values to perform some calculations. For testing purposes I show in Label1 a value as string, since it is stored as a string and in Label2 I show a casted value as a Double since I need them at the end as doubles for my calculations.
The weird thing is, that when I access the ViewController the first time it doesn't show any values. But if I go back and klick on it again using the navigation controller it actually works. But I need the values right away cause my original intention is as I said, not showing some labels but rather making some calculations with it.
I made a little gif to show you what the problem is but I have problem with adding photos. Basically what happens is, that I click on the ViewController with the labels and nothing is showed. I go back and press again and the values will be showed in the labels.
Why is that and how can it be showed right away/ used for calculations right away
Thanks for the help. :)
class AHPfinalPreferencesViewController: UIViewController {
var ahpPrios = [AHPPriorityStruct]()
let decoder = JSONDecoder()
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
let ajkpXc = globaLajkpXc
let ajkpXijr = globaLajkpXijr
let valueA = globaLajkpXc
let valueB = Double(globaLajkpXijr)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UserService.ahpPref(for: User.current) { (ahpPrios) in
self.ahpPrios = ahpPrios
print("This is our AHP PRIOS", ahpPrios)
for ahpPrio in ahpPrios {
print(ahpPrio)
}
print("this is the global ajk. ", self.ajkpXc)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Mark: - Get Data
label1.text = valueA
label2.text = "\(String(describing: valueB))"
// MARK: - Set Values for calculation
// setValues()
// ahpCalculation()
}
}
Could it be because of the globalVariables? I know that it is not the right way to do it but for my purposes its absolutely "okay"
import Foundation
import FirebaseAuth.FIRUser
import FirebaseDatabase
import FirebaseUI
import FirebaseAuth
import CodableFirebase
var globaLajkpXc: String = String()
var globaLajkpXijr: String = String()
var globaLajkpXqpa: String = String()
struct UserService {
static func ahpPref(for user: User, completion: #escaping ([AHPPriorityStruct]) -> Void) {
let ref = Database.database().reference().child("AHPRatings").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
guard let value = snapshot.value else { return }
do {
let ahpPrios = try FirebaseDecoder().decode(AHPPriorityStruct.self, from: value)
print(ahpPrios)
// MARK: - lets store the values in the actual constants :)
let ajkpXc = ahpPrios.ajkpXc
let ajkpXijr = ahpPrios.ajkpXijr
let ajkpXqpa = ahpPrios.ajkpXqpa
globaLajkpXc = ajkpXc ?? "no Value"
globaLajkpXijr = ajkpXijr ?? "no Value"
globaLajkpXqpa = ajkpXqpa ?? "no Value"
} catch let error {
print(error)
}
})
}
}
[1]: https://i.stack.imgur.com/VKxaE.png
You are calling UserService's ahpPref in your controller's viewWillAppear. BUT you are attempting to put your valueA (globaLajkpXc's value) to your label in your controller's viewDidLoad.
So what does that mean? Do you know which of these two controller's life cycle method gets called and when they do get called?
To solve your problem, have your label assigning value code
label1.text = globaLajkpXc
move in the completion block of your ahpPref (in the viewWillAppear).
Here's the Apple's documentation about the UIViewController's lifecycle: https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/WorkWithViewControllers.html
Also, below this line: globaLajkpXqpa = ajkpXqpa ?? "no Value"
add your completion call, like:
completion([ahpPrios]).
This should make my answer above work.

How to edit realm object and save editing?

I have 2 Controllers: TableViewController and ViewController. TableViewController is responsible for displaying all data, View Controller is responsible for creating new data.
Now I want to make it possible to edit the current data also in ViewController. When we click on data, we need to switch to the ViewController and replace all default values with the current values. When we change and click save, we go back to TableViewController, where we already see the change.
class OperationsViewController: UITableViewController {
// MARK: - Stored Properties
var transactions: Results<Transaction>!
var sections = [(date: Date, items: Results<Transaction>)]()
// MARK: - UITableViewController Methods
override func viewDidLoad() {
super.viewDidLoad()
transactions = realm.objects(Transaction.self)
}
override func viewWillAppear(_ animated: Bool) {
super .viewWillAppear(animated)
assembleGroupedTransactions()
tableView.reloadData()
}
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow {
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
print(item)
if segue.identifier == "editOrDeleteOperationCell" {
let addTableViewController = segue.destination as! AddTableViewController
addTableViewController.defaultTransaction = item
}
}
}
}
// MARK: - User Interface
extension OperationsViewController {
#discardableResult private func assembleGroupedTransactions() -> Array<Any> {
// fetch all Items sorted by date
let results = realm.objects(Transaction.self).sorted(byKeyPath: "date", ascending: false)
sections = results
.map { item in
// get start of a day
return Calendar.current.startOfDay(for: item.date)
}
.reduce([]) { dates, date in
// unique sorted array of dates
return dates.last == date ? dates : dates + [date]
}
.compactMap { startDate -> (date: Date, items: Results<Transaction>) in
// create the end of current day
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
// filter sorted results by a predicate matching current day
let items = results.filter("(date >= %#) AND (date < %#)", startDate, endDate)
// return a section only if current day is non-empty
return (date: startDate, items: items)
}
return sections
}
But when I trying to send current data to next ViewController I get error:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.'
I guess that I have problem with Category. Look on my model:
class Transaction: Object {
#objc dynamic var controlStatus = 0
#objc dynamic private var privateCategory: String = Category.consumption.rawValue
var category: Category {
get { return Category(rawValue: privateCategory)! }
set { privateCategory = newValue.rawValue }
}
#objc dynamic var amount = "0"
#objc dynamic var date = Date()
#objc dynamic var note = ""
}
controlStatus needs for monitors the status of the transaction that will be needed in the future. Where 0 is the expense, 1 is the income.
The big problem I suppose is that I created categories by enum.
I need to change the arrays with categories depending on the controlStatus.
Now is this my model of Category:
indirect enum Category: String {
case income = "+"
case consumption = "-"
case salary = "salary"
case billingInterest = "billingInterest"
case partTimeJob = "partTimeJob"
etc.
}
extension Category: RawRepresentable {
typealias RawValue = String
init?(rawValue: RawValue) {
switch rawValue {
case "+": self = .income
case "-": self = .consumption
case "salary": self = .salary
case "billingInterest": self = .billingInterest
case "partTimeJob: self = .partTimeJob
case "pleasantFinds": self = .pleasantFinds
case "debtRepayment": self = .debtRepayment
case "noCategories": self = .noCategories
case "food": self = .food
etc.
default:
return nil
}
}
var rawValue: RawValue {
switch self {
case .salary:
return "salary"
case .billingInterest:
return "billingInterest"
case .partTimeJob:
return "partTimeJob"
case .pleasantFinds:
return "pleasantFinds"
case .debtRepayment:
return "debtRepayment"
case .noCategories:
return "noCategories"
case .food:
return "food"
case .cafesAndRestaurants:
return "cafesAndRestaurants"
etc.
}
}
}
You need to enclose your realm write transactions in write block
try realm.write({ () -> Void in
realm.add(object, update: true)
})
From Realm doc
try! realm.write {
realm.add(myDog)
}

How to store user data as string, and load that string in viewDidLoad

I am trying to load a value that has been inputted by the user in the viewDidLoad via a String. I am using UserDefaults to save the users value that they input into a UITextField (userValue), I then save this to the String 'search'. I am able to print out the value of search in the GoButton function, and it works fine, but when I load my ViewController as new, the value of 'search' is equal to nil. The aim here is to have the users previous search saved, and loaded into the UITextField (that is used as a search box) upon loading the ViewController.
Code Below:
class ViewController: UIViewController {
#IBOutlet weak var userValue: UITextField!
var search: String!
}
viewDidLoad:
override func viewDidLoad() {
if (search != nil)
{
userValue.text! = String (search)
}
}
Button Function:
#IBAction func GoButton(_ sender: Any) {
let userSearch: String = userValue.text!
let perference = UserDefaults.standard
perference.set(userSearch, forKey: "hello")
perference.value(forKey: "hello")
let value = perference.value(forKey: "hello") as! String
search = value
print (search) // <<this works, it prints out the users search value
}
#VishalSharma has the right idea, but the code should probably look more like…
override func viewDidLoad() {
super.viewDidLoad()
if let search = UserDefaults.standard.string(forKey: "hello") {
userValue.text = search
}
}
or even more simply…
userValue.text = UserDefaults.standard.string(forKey: "hello")
When you load, search is effectively nil.
So either you read userDefaults in viewDidload or you come through a segue: then you can load search in the prepare.
I've always found it convenient and useful to store all UserDefault properties as an extension within the same file along with their getters and setters. It is far easier to maintain, use and read. by using the #function keyword for the key you are referencing the variable's name and not a string that can be accidentally changed somewhere else in code.
UserDefaults.swift
import Foundation
// An Extension to consolidate and manage user defaults.
extension UserDefaults {
/// A value Indicating if the user has finished account setup.
/// - Returns: Bool
var finishedAcountSetup: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
/// The hello text at the start of the application.
/// - Returns: String?
var helloText: String? {
get { return string(forKey: #function) }
set {set(newValue, forKey: #function) }
}
//etc...
}
When you use these values reference the standard settings:
//Setting
UserDefaults.standard.helloText = "Updated Hello Text"
// Getting
// for non-optional value you can just get:
let didCompleteSetup = UserDefaults.standard.finishedAcountSetup
// Otherwise, safely unwrap the value with `if-let-else` so you can set a default value.
if let text = UserDefaults.standard.helloText {
// Ensure there is text to set, otherwise use the default
label.text = text
} else {
// helloText is nil, set the default
label.text = "Some Default Value"
}
obviously, it provides nil because when view controller load the search is nil try this.
let perference = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
if (perference.value(forKey: "hello") != nil) {
search = perference.value(forKey: "hello") as! String
userValue.text! = String (search)
}
}

Value does not update in array

When I searched on here I now know that almost everything in Swift is value based, not referenced base. I also have read that a class holds references. I tried my best, but below code will not print anything. If anyone could help me out printing out that line with the help of an array, that would be great :) (if it is possible ofcourse...).
Edit: I want 5 booleans in the array, in which they all have a didSet method. When I access them, that specific didSet will trigger. Is that possible?
class PowerUpBooleans{
var boolean: Bool
init(boolean: Bool){
self.boolean = boolean
}
}
var iWantToChangeThis = false{
didSet{
print("it worked")
}
}
var powerUpBooleans = [PowerUpBooleans]()
override func viewDidLoad() {
super.viewDidLoad()
powerUpBooleans.append(PowerUpBooleans(boolean: iWantToChangeThis))
powerUpBooleans[0].boolean = true
}
I guess you want set some booleans that have their own trigger.
As I known, making value type wrapped by class can only make it be reference type.
So try this.
class PowerUpBooleans{
var boolean: Bool {
didSet {
trigger()
}
}
var trigger: () -> ()
init(boolean: Bool, trigger: #escaping () -> ()){
self.boolean = boolean
self.trigger = trigger
}
}
let trigger1 = {
print("one worked.")
}
let trigger2 = {
print("two worked.")
}
var powerUpBooleans = [PowerUpBooleans]()
powerUpBooleans.append(PowerUpBooleans(boolean: false, trigger: trigger1))
powerUpBooleans.append(PowerUpBooleans(boolean: false, trigger: trigger2))
powerUpBooleans[0].boolean = true // print one worked
powerUpBooleans[1].boolean = false // print two worked

Firebase restoring initial values in textfields after changing text value

I am practicing iOS (Swift) with Firebase. the first viewcontroller retrieves all the records from firebase db and populate the tableView from an array. when the user selects an item from that tableview a new viewcontroller pops up segueing the object from the listView viewcontroller to the detail viewcontroller. data is populated to the fields successfully!
however when i try to update any of the textfield, the moment i switch to another textfield the initial value is restored in the edited textfield.
I have tried to removeAllObservers... but nothing worked. i even removed "import Firebase" and all associated objects and still it restores the initial value.
am i missing any concept here?
this is the code from the ListViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated);
self.activityIndicator.startAnimating();
self.observeIngrdUpdates = ref.observeEventType(.Value, withBlock: { (snapshot) in
self.ingredients.removeAll();
for child in snapshot.children {
var nutrition:String = "";
var type:Int = 0;
var desc:String = "";
var img:String = "";
var name:String = "";
var price:Double = 0.0;
if let _name = child.value["IngredientName"] as? String {
name = _name;
}
if let _nutrition = child.value["NutritionFacts"] as? String {
nutrition = _nutrition;
}
if let _type = child.value["IngredientType"] as? Int {
type = _type;
}
if let _desc = child.value["UnitDescription"] as? String {
desc = _desc;
}
if let _img = child.value["IngredientImage"] as? String {
img = _img;
}
if let _price = child.value["IngredientPrice"] as? Double {
price = _price;
}
let ingredient = Ingredient(name: name, type: type, image: img, unitDesc: desc, nutritionFacts: nutrition, price: price);
ingredient.key = child.key;
self.ingredients.append(ingredient);
}
self.tableView.reloadData();
})
}
and the PrepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! EditIngredientVC;
if segue.identifier?.compare(SEGUE_EDIT_INGREDIENTS) == .OrderedSame {
destinationVC.ingredient = ingredients[tableView.indexPathForSelectedRow!.row];
destinationVC.controllerTitle = "Edit";
} else if segue.identifier?.compare(SEGUE_ADD_INGREDIENT) == .OrderedSame {
destinationVC.controllerTitle = "New";
}
}
this is the code for populating the fields in DetailViewController:
override func viewDidLayoutSubviews() {
lblControllerTitle.text = controllerTitle;
if controllerTitle?.localizedCaseInsensitiveCompare("NEW") == .OrderedSame {
self.segVegFru.selectedSegmentIndex = 0;
} else {
if ingredient != nil {
self.txtIngredientName.text = ingredient!.name;
self.txtUnitDesc.text = ingredient!.unitDesc;
self.segVegFru.selectedSegmentIndex = ingredient!.typeInt;
self.txtNutritionFacts.text = ingredient!.nutritionFacts;
self.txtPrice.text = "\(ingredient!.price)";
}
}
}
Thank you all for your help.
It's probably because you are putting your textField populating code, in viewDidLayoutSubviews() method.
This method will be triggered every time the layout of a views changes in viewcontroller.
Move it to viewDidLoad() and it should be fine.
The reason that it's being reverted to the previous value is because. You are populating the textField.text from the "ingredient" object. The ingredient will have the same value retained that you passed from previous view controller. Until you mutate it at some point.
And by the way Firebase is very cool I like it too :)

Resources