Way to check data modification across many viewcontroller without using global variables? - swift

I have an app which contains several viewControllers. On the viewDidAppear() of the first VC I call a set of functions which populate some arrays with information pulled from a database and then reload table data for a tableView. The functions all work perfectly fine and the desired result is achieved every time. What I am concerned about is how often viewDidAppear() is called. I do not think (unless I am wrong) it is a good idea for the refreshing functions to be automatically called and reload all of the data every time the view appears. I cannot put it into the viewDidLoad() because the tableView is part of a tab bar and if there are some modifications done to the data in any of the other tabs, the viewDidLoad() will not be called when tabbing back over and it would need to reload at this point (as modifications were made). I thought to use a set of variables to check if any modifications were done to the data from any of the other viewControllers to then conditionally tell the VDA to run or not. Generally:
override func viewDidAppear(_ animated: Bool) {
if condition {
//run functions
} else{
//don't run functions
}
}
The issue with this is that the data can be modified from many different viewControllers which may not segue back to the one of interest for the viewDidAppear() (so using a prepareForSegue wouldn't work necessarily). What is the best way to 'check' if the data has been modified. Again, I figured a set of bool variables would work well, but I want to stay away from using too many global variables. Any ideas?

Notification Center
struct NotificationName {
static let MyNotificationName = "kMyNotificationName"
}
class First {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived), name: NotificationName.MyNotificationName, object: nil)
}
func notificationReceived() {
// Refresh table view here
}
}
class Second {
func postNotification() {
NotificationCenter.default.post(name: NotificationName.MyNotificationName, object: nil)
}
}
Once postNotification is called, the function notificationReceived in class First will be called.

Create a common global data store and let all the view controllers get their data from there. This is essentially a global singleton with some accompanying functions. I know you wanted to do this without global variables but I think you should consider this.
Create a class to contain the data. Also let it be able to reload the data.
class MyData {
static let shared = MyData()
var data : SomeDataType
func loadData() {
// Load the data
}
}
Register to receive the notification as follows:
static let dataChangedNotification = Notification.Name("DataChanged")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Establish a way for call activity to notify this class so it can update accordingly
NotificationCenter.default.addObserver(self, selector: #selector(handleDataChangedNotification(notification:)), name: "DataChanged", object: nil)
}
func handleDataChangedNotification(notification: NSNotification) {
// This ViewController was notified that data was changed
// Do something
}
func getDataToDisplay() {
let currentData = MyData.shared.data
// do something
}
// Any view controller would call this function if it changes the data
func sendDataChangeNotification() {
let obj = [String]() // make some obj to send. Pass whatever custom data you need to send
NotificationCenter.default.post(name: type(of: self).dataChangedNotification, object: obj)
}

Related

difference between function with #objc in front and function doesn't have #objc

In one of the view controller files in my project, there are two functions, one is called in viewdidload and another is called by Notification and observers. Those functions do exactly the same thing, and I was wondering if I get rid of one of the functions, especially the one without using #objc in front. (otherwise I get an error)
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
func displayItems() {
fetchLiveEvents { [weak self] in
self?.applySnapshot(animatingDifferences: true)
}
}
func configureNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(updateExistingItem), name: .updateExistingItem, object: nil)
}
#objc func updateExistingItem() {
fetchLiveEvents { [weak self] in
self.applySnapshot(animatingDifferences: true)
}
}
Since I'm using the notification canter, I cannot get rid of #objc in front of updateExistingItem function. However, the updateExistingItem and displayItems are doing exactly something, so I feel it's kinda redundant and I was thinking to get rid of displayItems function from the viewDidLoad and call updateExistingItem (probably change the name) in viewdidLoad instead.
Is there any convention in Swift programming that keeps both #objc and normal function when they are doing the same thing? or is it just a personal preference and doesn't matter to leave both of them?
viewDidLoad just call once when the screen is present if you go to another screen by pushing a viewcontroller or presenting a controller and comeback to this controller the viewDidLoad didn't triggered it will never called again until the next run / terminate the app and open again.
so your function is called by the notification to run again when this screen appear.
// just called once
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
// just called every time when you popped a viewController or tap on tab bar items using tabbar controller
override func viewWillAppear() {
super.viewDidLoad()
displayItems()
}
in your scenario may be you came back to this screen by present some other screen and do some functionality there and call the notification to be trigger on this screen so nothing will trigger if your present a screen by modal presentation style over full screen
That's why called the notification to start displaying item again
override func viewDidLoad() {
super.viewDidLoad()
configureNotifications()
displayItems()
}
// called once
func displayItems() {
fetchLiveEvents { [weak self] in
self?.applySnapshot(animatingDifferences: true)
}
}
func configureNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(updateExistingItem), name: .updateExistingItem, object: nil)
}
// called every time when you trigger notifcation
#objc func updateExistingItem() {
fetchLiveEvents { [weak self] in
self.applySnapshot(animatingDifferences: true)
}
}

Passing Data Indirectly to an Unrelated NSViewController

I am new to macOS Development and I am working on a project for macOS using Xcode 10 and Swift 4.2.
The project contains mainly 3 view controllers.
Firstly, ViewController (the main welcome screen which separates the other two) has two buttons to call the other two respectively.
Secondly, MakeEntry View Controller creates an array of strings data variable using a form type structure comprised of text views and save button etc. which in the end just saves all input data into an array of strings data variable called carrierArray
Thirdly, there is a split view controller for displaying two children view controller namely EntryList and EntryDetail
EntryList (the left pane) contains a Table View to display titles of entries and EntryDetail (the right pane) will contain the description of the title entry (somewhat like the default notes app of macOS)
I want to achieve a simple functionality of being able to access or read that Array of strings variable called carrierArray which is created when the MakeEntry view controller saves it into a global variable defined within its own class file But I want to access that array of strings anywhere and anytime later.
I cannot use delegates and protocols, closures, segues or storyboard identifiers to carry that data because I am not navigating to the Split View Controller straightaway and also because I want to store that data
to manipulate it further before displaying it in the right pane of split view controller (EntryDetail) .
I am unable to figure out whether how it might be possible to achieve this functionality using NSUserDefaults or CoreData.
Therefore I tried using the Notification Centre after storing that array of Strings in a Dictionary namely notifDictionary containing a key called carryData to be stored as the data object of notification centre And with some research and some trials and errors but without any luck all resulting in failure to get that data in the split view controller left pane class file namely (EntryDetail).
Code Snippets are as below, thanks a lot in advance for the kind help.
In MakeEntry View controller:
notifDictionary = ["carryData": carrierArray]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "dataCarrier") , object: nil, userInfo: notifDictionary)
In EntryList View Controller:
(Tried using both types of selector methods one at a time and even using them together but all without luck! Please Help!)
The Variable datumDict and datumArray and nothing but copy receivers for carrierArray and notifDictionary
var datumDict: [String:[String]] = [:]
var datumArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.getThatDict(_:)), name: NSNotification.Name(rawValue: "dataCarrier") , object: nil)
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "dataCarrier") , object: nil, queue: nil, using: catchNotification)
//datumArray = datumDict["carryData"]!
}
#objc func onNotification(notification:Notification)
{
print(notification.userInfo!)
}
func catchNotification(notification: Notification) -> Void
{
let theDict = notification.object as! NSDictionary
datumDict = theDict as! [String: [String]]
guard let theData = notification.userInfo!["carryData"] as? [String:[String]] else { return }
datumDict = theData
}
#objc func getThatDict(_ notification: NSNotification)
{
print(notification.userInfo ?? "")
if let dict = notification.userInfo as NSDictionary?
{
if let thatDict = dict["carryData"] as? [String: [String]]
{
datumDict = thatDict
}
}
}
With the caveat that "globals and singletons should be avoided," it sounds like they are a good fit for what you're trying to do. As you get more comfortable in Cocoa you can move into more sophisticated means of accomplishing this (dependency injection). Look into this as you get more comfortable with Cocoa.
Create a simple singleton type:
// AppState.swift
class AppState {
// Basic singleton setup:
static let shared = AppState()
private init() {} // private prevents instantiating it elsewhere
// Shared state:
var carrierArray: [String] = []
}
Access it from your view controllers:
// YourViewController.swift:
#IBAction func doSomething(_ sender: Any) {
AppState.shared.carrierArray = ...
}
If you need to update the other view controllers when this shared state changes, notifications are a good tool for that. You could do this with a didSet on carrierArray, or simply trigger the notification manually.

Update tableview instead of entire reload when navigating back to tableview View Controller

I have a home UIViewController that contains a UITableView. On this view controller I display all games for the current user by reading the data from firebase in ViewWillAppear. From this view controller a user can press a button to start a new game and this button takes them to the next view controller to select settings, this then updates the data in firebase and adds a new child. Once they navigate back to the home view controller is there anyway to just update the data with the new child added instead of loading all the games for the table view again as I am currently doing?
This is my current code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef = Database.database().reference().child("games").child(currentUserID)
self.games = []
gamesRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let game = child as! DataSnapshot
self.games.append(game)
self.tableView.reloadData()
}
})
}
}
I think you can use observeSingleEvent and .childAdded
You can do the loading of all the data in viewDidLoad and of single child in viewWillAppear since viewDidLoad will be called once initially
Since both methods will be called initially, so we can have a bool flag so we can control which code runs initially and which does not , since viewWillAppear is called after viewDidLoad so we change the value of this flag in viewWillAppear method and then control the execution of code inside viewWillAppear using this flag
class SomeVC: UIViewController {
var flag = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if flag {
//do your work here
}else {
flag = true
}
}
}
Edited:
Another solution can be that you dont do anything in viewDidLoad and do the work only in viewWillAppear since in this particular scenario data in both calls are related (fetching the data from Firebase)
class SomeVC: UIViewController {
var flag = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if flag {
//fetch only one child
}else {
//fetch all the data initially
flag = true
}
}
}

Simple data transfer within viewcontroller without transition

So i am new to app development and i am trying to set up a very simple delegation/protocol pattern. I have been searching and trying different tutorials but can't seem to find anything that works and am getting in such a muddle. Please can somebody help. I will break i down so that its really clear as to what i need -
I have two view controllers, 'DetailedVC' and 'SelectionsVC'.
DetailedVC has a variable called -
var sendingData = (choice: "", choiceValue:0.0)
and
UIbutton buttonSelectTapped
SelectionsVC has a variable called -
var recievedData = (choice: "", choiceValue:0.0)
And all i want to do is send the data from the variable 'sendingData' in DetailedVC when the button (buttonSelectTapped) is tapped to the SelectionsVC and store it in the variable 'recievedData'. I do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
Then when the user views that controller 'SelectionsVC' at whatever stage, the data will be called in the viewDidLoad when loading that controller.
Use NSUserDefault to pass data between viewcontroller if you do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
DetailedVC Code
func viewDidLoad() {
NSUserDefaults.standardUserDefaults().removeObjectForKey("selectedData")
}
func didTapButtonSelectTapped() {
NSUserDefaults.standardUserDefaults().setDouble(sendingData , forKey: "selectedData")
}
SelectionsVC code
func viewDidLoad() {
if(NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")) {
recievedData = NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")
}
}
But as your question title describe their is no use of protocol/delegate in above code.
Passing Data on transition from viewcontroller :
DetailedVC Code
func didTapButtonSelectTapped() {
let vc = SelectionsVC()
vc.recievedData = sendingData
self.presentViewController(vc, animated: true, completion: nil)
}

Automatically Reload TableViewController On Rewind

I am working on an app where it starts out at a tableViewController which loads and displays data stored in a Realm database. I have it so I can create a new entry in my Realm database in a separate scene and the save button unwind segues back to the initial tableView.
I current have it so the tableViewController will reload the tableView on pull down (something I Learned here, second answer down) but I would be better if the tableView would reload its self automatically upon unwind, displaying all the data in my database, including my new entry. Could someone please direct me to a tutorial that will teach me how this is done.
Additional info: My app is embedded in a navigation controller. The save button is located the bottom tool bar.
You can use NSNotification for that.
First of all in your tableViewController add this in your viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshTable:", name: "refresh", object: nil)
}
And this will call one method from your class:
func refreshTable(notification: NSNotification) {
println("Received Notification")
tableView.reloadData() //reload your tableview here
}
So add this method too.
Now in your next view controller where you add new data into data base add this in you unWindSegue function:
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
Hope it will help
Try reloading your tabledata in viewWillAppear in initial (tableview)controller.
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
Or call again the function through which you are loading your data from Realm like
override func viewWillAppear(animated: Bool) {
getMyData() //or whatever your function name is
}
If you are using storyboard unwind segue, try using
func unwindSegue(segue:UIStoryboardSegue) {
if segue.identifier == "identifier" {
getData()
self.tableView.reloaddata()
}
}