How to store objects in memory - swift

I come from JavaScript and I'm used to fetching data through an API once and then using it throughout the app (as long as I don't need to do a page reload). For example, if I write var user = $.get('/get/user'), I can then use the user variable everywhere, even if I load a new view/page.
As I understand it, each View Controller is like a new page. Therefore, how can I get, for example, my user when the app starts and use it throughout the app without storing it in Core Data or somewhere similar?
Can I instantiate a class when the application starts that gets all the initial values and then stores them for use everywhere? If so, how?

You can follow the singleton pattern. This is an object that holds a static reference to itself and this instance is accessed using a class method.
class YourSingletonClass {
static let sharedInstance = YourSingletonClass()
func someMethod() -> Void {
}
}
// Other part of the app
YourSingletonClass.sharedInstance.someMethod()
The other approach is to create an object (instance of a class), populate the variables with the data you require to persist in memory and then pass this instance from View Controller to View Controller.
var myVar = YourClassForMemory()
myVar.value = 1
myVar.otherValue = 2
You can then create a property on other View Controllers of type YourClassForMemory, and set it with this object when you initialise the View Controller and pass the object around. This can be done in prepareForSegue, if using Storyboard segues, or as an init method, or just a public property on the class.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mySegue" {
var myVar = YourClassForMemory()
myVar.value = 1
myVar.otherValue = 2
(segue.destinationViewController as! SomeViewController).myVar = myVar
}
}

Related

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.

Calling an asynchronous closure from a delegate's source ViewController

I have the following setup :
Launch VC performs an asynchronous API request in a closure dataGatheringClosure
The dataGatheringClosure closure's completion handler passes the result to VC1 via delegation using protocol method setData.
Within setData, VC1 passes the result to VC2 using the following code:
if let vc2 = self.tabBarController?.viewControllers?[1] as? VC2Controller {
vc2.data = result
}
I'm able to transfer data from the Launch VC to VC2, but now I need to refresh the data by performing the dataGatheringClosure in Launch VC.
My question is: How do I access Launch VC's methods from VC2? And is this approach sound?
As a workaround, I copied the dataGatheringClosure closure code inside TabBar VC and had VC 2 call its self.tabBarController.dataGatheringClosure method. However, it is duplicative, and also I don't know if it's best practice to execute heavy API requests within a TabBarController.
The first step is to separate the data from the ViewControllers. Create a singleton class like below and use it to pass around the data you need.
class Data {
static let shared = Data()
// Declare any other data properties you need here...
var result = [String]()
private init() {}
func initialize() {
// Write code to initialize the data
refresh()
}
func refresh() {
// Write code to refresh the data
}
}
Initialize data at app startup or wherever required using the below code:
Data.shared.initialize()
You can now access data from anywhere within your app using the static variable Data.shared. To refresh data you can do the following:
Data.shared.refresh()
Hope this helps.

Passing data to a ViewController that is triggered by an external event

I've set up a URL scheme for my application that will open it from a web browser. When received by the application, it presents AViewController.
I also have a ViewController, BViewController that contains some data. Ideally I would like to be able to pass this data from BViewController, to AViewController if BViewController is open at the time that the URL scheme is activated.
Since the URL scheme triggers a function in AppDelegate, I have no opportunity to pass the data from B to A.
What would be the best way to pass the data along?
The only solutions I can think of so far are:
Setting and getting a global variable for each piece of data
When the URL scheme call is received in AppDelegate, extracting the data from B by accessing the object properties, and setting them in A
Neither of these solutions I'm fully happy with, certainly not the first.
Is there be a better way of solving this?
Thanks in advance.
If you have…
class Object {
var someData…
}
class BViewController {
var object: Object!
func updateObject() {
object.someData = …
}
}
class AViewController {
var object: Object!
}
Then in AppDelegate, something like this (not real code!)…
func handleURL() {
if let b = rootViewController as? BViewController {
let a = AViewController()
a.object = b.object
rootViewController = a
}
}

strange optional error in Swift

Okay, so basically I am following a tutorial on udemy on how to create a chat with Backendless and Firebase. However, I prefer not to use Backendless, because I don't want to rely on 2 providers - so I want to stick to Firebase only. Therefore, I am currently converting my code to Firebase.
I have a view controller that displays a unique page for each UID - from a database that I have. The UID is stored as a String, and is assigned upon a segue from another table view controller (this works fine). After that, I fetch the data that I want from the user, with the UID. I have a "Start Chat" button that is supposed to create a new chat.
In this tutorial, the tutor has set a protocol (delegate) that is triggering another function from another view controller. This is what it looks like:
protocol ChooseUserDelegate {
func createChatroom(withUser: String)
}
var delegate: ChooseUserDelegate!
and in my chat #IBAction, I have this code:
#IBAction func StartChat(sender: AnyObject) {
let userID = uid
if let theId = userID as? String {
delegate.createChat(String(theId))
}
}
(The code above is all in the same VC.).
In another view controller, where the createChat() function is stored, is the following code:
class AnotherVC UIViewController, UITableViewDelegate, UITableViewDataSource, ChooseUserDelegate{
func createChat(withUser: String) {
print(withUser)
}
}
The problem is that I can't get to call createChat(), because of an optional error (unwrapping) on the delegate.createChat(String(theId)).
Edit: Even with a "" input, I get an error. I am really confused now. Is it something wrong with my delegate?
The only part of your code that is optional is delegate (because you correctly unwrapped userID). Therefore, the error must be due to delegate being nil. Make sure that you set delegate before calling StartChat().
The line var delegate: ChooseUserDelegate! does not initialize a delegate. When you write ChooseUserDelegate! you are only defining the type of the delegate variable. It is automatically set to nil. To initialize a new instance of ChooseUserDelegate you would need to write something like:
var delegate: ChooseUserDelegate! = ChooseUserDelegate()
There are a few other ways you could clean up your code. Method names should be llamaCase, not CamelCase, so you should rename StartChat() to startChat() (be sure to reconnect in interface builder). The body of that method has three different names for the same variable, uid. See how simple it could be:
#IBAction func startChat(sender: AnyObject) {
if let uid = uid as? String {
delegate.createChat(uid)
}
}
if let theId = userID {
delegate.createChat(String(theId))
}

Passing Data with Delegates

Im having a hard time understanding data passing with delegates, I have looked but I get confused by the answers as I'm usually trying to do the opposite of what needs to be done for mine.
What I am trying to do is pass a PFObject from my main viewController (a UITableView) to a PopOverViewController.
I have done this successfully, but I need to pass the object. What would be the best way to pass from the mainController (SOITableViewController) to the popover (DetailPopViewController)?
Where should the protocol go? Where should the Delegate method be placed, etc.
Thank you!
PrepareForSegue, NSUserDefault and Singleton
You have a few possible options to pass your data to other views depending how you want that data to be handled, I will explain each for you and you can choose which one best fit your need.
prepareForSegue: Method
I recommend this method if you want to hold your data for 1 segue transition, it's a good cause to pass this again to another view afterward you need to create another prepareForSegue within the new view. here is an example on how to do this:
First, you create 2 variables in both views, 1 to send (currentViewController.swift) and 1 to receive (toViewyourGoingController.swift).
currentViewController.swift var dataToSend: AnyObject?
ViewYourGoingController.swift var dataToReceive: AnyObject?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Check your segue, this way you can transfer different data to different view. also make sure the identifier match your segue.
if segue.identifier == "toViewYourGoing" {
//Initial your second view data control
let ExchangeViewData = segue.destinationViewController as! toViewyourGoingController
//Send your data with segue
ExchangeViewData.dataToReceive = dataToSend
}
}
NSUserDefault
Now this method is good if you want to keep your data live as long as the app is installed, once the app is removed this will reset automatically. You also have the option to update the value of the key if you wish, here is how you do NSUserDefault:
I always like to register my NSUserDeafult to default setting, a lot of people just continue with the second step without registering.
Register NSUserDefault in AppDelgate.swift
NSUserDefaults.standardUserDefaults().registerDefaults(["valueName": AnyObject])
Set Value to your NSUserDefault, this depends on what type of data you're storing, should match the one with your registration if you did register. (Example of Boolean data type below)
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "valueName") //Bool Data Type
Make sure you synchronize once you set the value to the NSUserDefault, this way it will update instantly, otherwise it will update when it get a chance.
NSUserDefaults.standardUserDefaults().synchronize()
Receive Value: this will receive boolean value since we set boolean and register boolean.
let Variable: Bool! = NSUserDefaults.standardUserDefaults().boolForKey("valueName")
Singleton
Now singleton is basically a global variable that you can use them in any views, but some developers experience some bugs and difficulties, use it at your own risk, I recommend this method when you're definite that you will use that data a lot (STILL RISKY), but this method is like goddess of data handling :).
Create a NSObject subclass and call it DataManager.swift (I call it data manager cause it handle data.) as following:
import UIKit
class DataManager: NSObject {
//Store Data Globally
static var someData: Boo! //This Boolean, you can choose whatever you want.
}
the static is what keep your data live.
Now you can store and receive someData from anywhere like you handle any data type like this.
//Store
DataManager.someData = true
//Receive
print(DataManager.someData)
Challenges:
You can also use
Keychain
Sergey Kargopolov will walk you through how to use a third party to use swift keychain. Otherwise, you can take even harder challenge and create one yourself :P .
Key-Value Data in iCloud
The best way to do this would be to pass it over when you do the prepare for segue method. So to do this make a variable in your detailPopViewController. In this case your pop over segue in storyboard will have the segue identifier detailView. Also tblSearchResults is your tableView outlet (you can name it whatever you want). Is that what you were looking for?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailView" {
if let detailViewController = segue.destinationViewController as? PopOverViewController{
if let indexPath = self.tblSearchResults.indexPathForCell(sender as! SOITTableViewController) {
detailViewController.objectInSecondVC = objectFromFirstVC[indexPath.row]
}
}
}
}