I am using static variable to keep track of the number of hotels added in firebase. Let's say, in start, the value of static variable is 1, then when data is added in firebase, the number is incremented to 2. But, when again the data is added and this view controller is loaded again, the value of static variable gets back to 1 and the new data posted replace the older data. How can I manage that thing? I know that pretty basic and silly question, but sometimes the brain just don't work. Below is the code.
class OwnerAddListing2ViewController: UIViewController {
static var numberOfHotels:Int = 1
let DataForCurrency : [String] = ["USD", "Rs"]
let DataForDays : [String] = ["PerNight", "PerWeek", "PerMonth"]
override func viewDidLoad() {
super.viewDidLoad()
currencyField.inputView = currencyPicker
daysField.inputView = daysPicker
}
#IBAction func nextButtonTapped(_ sender: UIButton) {
let currency = currencyField.text
let charges = chargesField.text
let days = daysField.text
let phone = phoneField.text
let email = emailField.text
//Get reference to firebase Database
let db = Firestore.firestore()
//Post data tw database
db.collection("Property").document("\(Auth.auth().currentUser!.uid)").collection("Hotel").document("\(OwnerAddListing2ViewController.numberOfHotels)").setData(["Currency": currency!, "Charges" : charges!, "Days" : days!, "Phone" : phone!, "EmailAddress" : email!], merge: true) {(error) in
if error != nil {
}
else {
print("Data Posted Succesfully")
OwnerAddListing2ViewController.numberOfHotels = OwnerAddListing2ViewController.numberOfHotels + 1
}
}
}
Static Variables only keep the data saved in One application Life cycle. As soon as you restart the application, the static variable will be initialized from the default value. If you want to persist the value of your variables throughout , may be you should consider using UserDefaults which can store small amount of information. But be careful not to store any sensitive data like passwords.
Related
I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.
I have 2 ViewControllers, one displays the UI and the 2nd one displays a segmented control used as a settings button. Im using the below code to save the segmented control state:
UserDefaults.standard.set(selectorLabel.selectedSegmentIndex, forKey: "stateSelected")
I then retrieve that usedefault on the viewdidload method:
if let value = UserDefaults.standard.value(forKey: "stateSelected"){
let selectedIndex = value as! Int
selectorLabel.selectedSegmentIndex = selectedIndex
}
So far this works as intended and the state of the segmented controlled is loaded properly each app load.
The segmented control has two text titles - one is "LBs & INs" and the second is "KGs & CMs".
How would I save those two segmented control text titles as UserDefaults and then call them on the first ViewController to set two labels on the viewdidload?
Define a model to represent data you want to store and restore:
struct SegmentedControlState: Codable {
let selectedIndex: Int
let titles: [String]
}
Initialize a model, encode and store it somewhere (like user default):
func saveState(of segmentedControl: UISegmentedControl) {
let state = SegmentedControlState(
selectedIndex: segmentedControl.selectedSegmentIndex,
titles: (0..<segmentedControl.numberOfSegments).map { segmentedControl.titleForSegment(at: $0) ?? ""})
let plist = try! PropertyListEncoder().encode(state)
UserDefaults.standard.set(plist, forKey: "SegmentedControlState")
//UserDefaults.standard.synchronize() //if targeting older iOS
}
for restoring, you should reverse the order like this:
func loadState(on segmentedControl: UISegmentedControl) {
guard let plist = UserDefaults.standard.value(forKey: "SegmentedControlState") as? Data else { return }
let state = try! PropertyListDecoder().decode(SegmentedControlState.self, from: plist)
for element in state.titles.enumerated() {
segmentedControl.setTitle(element.element, forSegmentAt: element.offset)
}
segmentedControl.selectedSegmentIndex = state.selectedIndex
}
usage:
// store `selectorLabel` data
saveState(of: selectorLabel)
// restore `selectorLabel` data
loadState(on: selectorLabel)
Note that it is not a good idea to store data like this to userdefaults at all. If you want to access some data from anywhere in code, you should follow singleton pattern and define your own singleton instance instead of standard userdefault.
Just store the value as a string, instead of an integer index.
UserDefaults.standard.set(selectorLabel.titleForSegment(at: selectorLabel.selectedSegmentIndex), forKey: "stateSelected")
And then to retrieve:
UserDefaults.standard.string(forKey: "stateSelected")
EDIT: #rmaddy is correct above - you should ideally be storing an index value like you're already doing, and then using an array to determine which title the index refers to (cleaner than just using a title as a reference). You could make this array global so you can access from anywhere, if you must.
segmentedControlTitles: [String] = ["LBs & INs", "KGs & CMs"]
And then call by
let index = UserDefaults.standard.integer(forKey: "stateSelected")
let title = segmentedControlTitles[index]
I'm building an app that uses firebase for authentication and database functionality. Once a user signs up, a database record is stored for that user, containing some basic information like first name, last name etc.
Once a user logs in with his credentials I want to set a global variable (perhaps userDefaults?) which contains the user data for that specific user. Otherwise I have to fetch user data for every time I want to fill a label with for instance, a user's first name.
I managed to set userdefaults upon login and use this info in UIlables. But when I let users make changes to their data, of which some is important for the functioning of the app, I can update the server AND the userdefaults but the app itself doesn't update with the correct data. It keeps the old data in (for example) UIlables.
I would love to get some more insight on what the best work-flow is to manage situations like these.
When opening the app, i have a tabBarController set as rootviewcontroller. In the load of tabbarcontroller I have the following code retrieving the user data from firebase and saving it to userdefaults:
guard let uid = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.value ?? "")
guard let dictionary = snapshot.value as? [String: Any] else { return }
let firstname = dictionary["First name"] as? String
let lastname = dictionary["Last name"] as? String
print("first name is: " + firstname!)
UserDefaults.standard.set(firstname, forKey: "userFirstName")
print(UserDefaults.standard.value(forKey: "userFirstName"))
self.setupViewControllers()
}
Then I continue on loading in all the viewcontrollers in the tabBarController:
self.setupViewControllers()
During that process the labels in those viewcontrollers get filled in with the userdefaults data.
This is an example of a label being filled in with userDefaults but not being updated upon changing of userdefaults:
let welcomeLabel: UILabel = {
let label = UILabel()
let attributedText = NSMutableAttributedString(string: "Welcome ")
attributedText.append(NSAttributedString(string: "\(UserDefaults.standard.string(forKey: "userFirstName")!)"))
label.attributedText = attributedText
label.font = UIFont.systemFont(ofSize: 30, weight: .bold)
return label
}()
this is a function i'm using to update the first name (via a textfield filled in by the user):
#objc func updateName() {
guard let uid = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("users").child(uid).updateChildValues(["First name" : updateNameField.text ?? ""])
UserDefaults.standard.set(updateNameField.text, forKey: "userFirstName")
print(UserDefaults.standard.value(forKey: "userFirstName"))
}
So you'll have to organize things first. In a new file define constants such as below. These constant will be accessible in global scope unless private
Constants.swift
private let storedusername = "usname"
private let storedName = "uname"
private let displaypic = "udp"
private let aboutme = "udesc"
var myusername : String {
get {
return (UserDefaults.standard.string(forKey: storedusername)!)
} set {
UserDefaults.standard.set(newValue, forKey: storedusername)
}
}
var myname : String {
get {
return (UserDefaults.standard.string(forKey: storedName)!)
} set {
UserDefaults.standard.set(newValue, forKey: storedName)
}
}
var myProfileImage : Data {
get {
return (UserDefaults.standard.data(forKey: displaypic)!)
} set {
UserDefaults.standard.set(newValue, forKey: displaypic)
}
}
var myAboutMe : String? {
get {
return (UserDefaults.standard.string(forKey: aboutme)!)
} set {
UserDefaults.standard.set(newValue, forKey: aboutme)
}
}
Now the next time you want to save anything in UserDefaults, you'll just do the following anywhere throughout your code base :
myusername = "#CVEIjk"
And to retrive it, just call it :
print(myusername)
IMPORTANT NOTE --
Always remember to initialize them. You can do this as the user signs up. As soon as they fill out their details and hit submit, just save them to these variables. That wouldn't cause unnecessary crash.
You'll have to save them at every location you perform updates regarding these nodes in the database.
Now, the refreshing views part. I am taking a scenario where your ProfileView.swift has the view and user goes to EditProfile.swift for updating the content.
You initialize all your observers the place where the update will have the immediate effect. Because the view immediately after the update matters. The rest will be called through the getter of the aboutme
ProfileView.swift
func openEditView() {
NotificationCenter.default.addObserver(self, selector: #selector(fetchUserDetails), name: Notification.Name("update"), object: nil)
//setting this right before the segue will create an observer specifically and exclusively for updates. Hence you don't have to worry about the extra observers.
perform(segue: With Identifier:)// Goes to the editProfile page
}
This function will be initially called in viewDidLoad(). At this time you need to make sure you have all the data, else it will produce no values. But if you are storing everything as the user signs up, you are safe.
#objc func fetchUserDetails() {
if uid != nil {
if myname.count > 0 { // This will check if the variable has anything in the memory or not. Dont confuse this with [Array].count
self.nameLabel = myname
}
}
}
This function also acts an ab observer method. So when the notifications are posted they can run again.
Now, EditProfile.swift
In the block where you are updating the server, save the values and then create a Notification.post and put this method right before you dismiss(toViewController:)
func updateUserCacheData(name: String, username: String, aboutme: String, ProfilePhoto: UIImage? = nil) {
DispatchQueue.global().async {
myname = name
myusername = username
myAboutMe = aboutme
if self.newImage != nil {
myProfileImage = self.newImage!.jpegData(compressionQuality: 1)!
}
DispatchQueue.main.async {
NotificationCenter.default.post(name: .refreshProfileViews, object: nil)
}
}
}
func updateToServerAndBackToProfileView() {
self.updateUserCacheData(name: iname!, username: iusername, aboutme: iaboutme!)
self.dismiss(animated: true, completion: nil)
}
}
As long as this goes back to ProfileView, your views will be instantly refreshed. You can keep an observer wherever you view will be first displayed after the dismiss. the rest will fetch updated content always. Also, don't forget to deinit your Observer in ProfileView
//This code is in ProfileView.swift
deinit {
NotificationCenter.default.removeObserver(self, name: Notification.Name("update"), object: nil)
}
Also, in cases where the content might be empty, simply initialize it with empty content. For example, if user doesn't choose to add aboutme while signing up, you can just put
`myaboutme = ""`
This will create a safe environment for you and you are well set.
I have an app where I time myself and see how long it takes me to complete a bunch of questions. I have the time transferred to another VC and displayed in a label. I have it being stored by pressing a button but when i have a new variable(time) it replaces it. How do i store an Array of values and that can be displayed in a label?
Button to save the value:
#IBAction func saveScore(_ sender: Any) {
scoreLabel.text = label.text
UserDefaults.standard.set(scoreLabel.text, forKey: "score")
}
The code that permanently holds the data:
override func viewDidAppear(_ animated: Bool) {
if let x = UserDefaults.standard.object(forKey: "score") as? String {
scoreLabel.text = x
}
}
My scoreLabel displays all my scores and label shows the time you just got.
Use the following extentions on UserDefaults to store an array of times:
extension UserDefaults {
var times: [String] {
get {
if let times = UserDefaults.standard.object(forKey: "times") as? [String] {
return times
} else {
return []
}
}
set {
UserDefaults.standard.set(newValue, forKey: "times")
}
}
}
While you don't need to extend UserDefaults, using extension can simplify a bit working with persisted values and it makes the code cleaner.
Then at the point where you show the data, use the following line to access the array:
let arrayOfTimes = UserDefaults.standard.times
scoreLabel.text = "\(arrayOfTimes)" // or any other formatting you'd like
And instead of setting the times to persist a new score, just add the new score to the array, e.g.:
// This will not only add the scoreLabel.text to the array, but also persists it
UserDefaults.standard.times.append(scoreLabel.text)
In Swift 4,
To save an array to User Defaults you would do:
let defaults = UserDefaults.standard
let array = [25, 50]
defaults.set(array, forKey: "Scores")
And to access the array from User Defaults:
let defaults = UserDefaults.standard
let retrievedArray = defaults.array(forKey: "Scores") as? [Int] ?? []
And if you were to display a score of your array in a label then, you would just do:
scoreLabel.text = String(describing: retrievedArray[0])
If you are using integers for your scoring system, I would suggest you
storing your scores as Int in User Defaults.
If you prefer using Strings though, please note that you can use the User Defaults' stringArray(forKey:) method directly, instead of the array(forKey:) method, and therefore, in that case, you wouldn't need to type cast your array:
let someStringArray = defaults.stringArray(forKey: "ArrayOfStrings")
Note: To answer your question, I will consider that you are using Int scores, but feel free to use whichever you prefer.
If you want to store your array to the same key in User Defaults every time you get a new score, you could do it easily like this:
let defaults = UserDefaults.standard
// Your new score:
let newScore = 75
// Get your current scores list from User Defaults:
var currentArray = defaults.array(forKey: "Scores") as? [Int] ?? []
// Append your new score to the current array:
let updatedArray = currentArray.append(newScore)
// And save your updated array to User Defaults:
defaults.set(updatedArray, forKey: "Scores")
// In this example, your User Defaults now contains the updated array [25, 50, 75]
And that's it :).
Please note that there is no need to use an extension for that..
UPDATE: Also, if you want to add something inside your viewDidAppear method, don't forget to add super.viewDidAppear(animated). The same goes for viewDidLoad, etc.
The documentation states:
You can override this method to perform additional tasks associated
with presenting the view. If you override this method, you must call
super at some point in your implementation.
So you would have:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let retrievedArray = defaults.array(forKey: "Scores") as? [Int] {
print(retrievedArray)
// You can access your scores array safely here
}
}
I am trying to save an integer so it shows up after I switch the page or close the game. I made this to change the number but how do I save that number when I switch pages and load it when I go back to that page.
Change Code:
#IBAction func MoneyPress(sender: AnyObject) {
Money += 1
var MoneyNumberString:String = String(format: "Dollars:%i", Money)
self.DollarsLabel.text = (string: MoneyNumberString)
}
If it isn't a lot of data, the strategy I use to save data, pass it between pages, and persist it between app runs is to store the value in NSUserDefaults.
Setting A Value: When you first get or when you change the data, store it in NSUserDefaults.
#IBAction func MoneyPress(sender: AnyObject) {
Money += 1
var MoneyNumberString:String = String(format: "Dollars:%i", Money)
self.DollarsLabel.text = (string: MoneyNumberString)
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults() //This class variable needs to be defined every class where you set or fetch values from NSUserDefaults
defaults.setObject(MoneyNumberString, forKey: "money")
defaults.synchronize() //Call when you're done editing all defaults for the method.
}
Loading A Value: When you need to get the values, just grab it from NSUserDefaults.
#IBAction func loadButton(sender: UIButton) {
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
var money = defaults.valueForKey("money") as? String
dollarLabel.text! = money
}
To remove the stored data, all you need to do is call the removeObjectForKey function for each key previously set.
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.removeObjectForKey("money")
defaults.synchronize()
Helpful Source on NSUserDefaults:
NSUserDefulats Class Reference: Link here.
You can use NSUserDefaults for this.
Save Value
NSUserDefaults.standardUserDefaults().setInteger(money, forKey: "MoneyKey");
Retrieve Value
NSUserDefaults.standardUserDefaults().integerForKey("MoneyKey");
So can retrieve the value in viewDidLoad and load the data:
override func viewDidLoad()
{
super.viewDidLoad()
loadWebView()
var money = NSUserDefaults.standardUserDefaults().integerForKey("MoneyKey");
}
When you come to the view for the first time the value of money will be 0.
Remove Value
If you need to remove a value from NSUserdefaults, you can use:
NSUserDefaults.standardUserDefaults().removeObjectForKey("MoneyKey")