Calling an asynchronous closure from a delegate's source ViewController - swift

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.

Related

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
}
}

(re)-Pass data after click on backbutton

I'm trying to pass data from a SecondViewController to my FirstViewController when I click on my back button (UINaviagtionController).
For pass my data from FirstViewController to the SecondViewController I do this:
if segue.identifier == "showSecondVC" {
let vc = segue.destination as! SecondViewController
vc.rows = rows[pathForAVC]
vc.lap = lapTime[pathForAVC]
vc.indexPath = pathForAVC
}
But I have no idea how to pass data from SecondViewController to the FirstViewController and I really don't understand topics about it on Stack Overflow.
I want to transfer it when I click here:
Thanks.
You can use delegate pattern for that. You can grab the back button press event like this and update the data
override func viewWillDisappear(_ animated: Bool) {
if self.isMovingFromParentViewController {
self.delegate.updateData( data)
}
}
For more information on delegates you can go through this.
Actually things depend on your requirement, if you want data to be updated in first view controller as soon as it is updated in second view controller, you would need to call delegate as soon as the data is updated. But as in the question you have mentioned that you want it to be updated on back button only, above is the place to do it.
Another way would be to have Datasource as singleton so that it is available to all the view controllers and the changes are reflected in all view controllers. But create singleton if absolutely necessary, because these nasty guys hang around for entire time your application is running.
You should have a custom protocol such as:
public protocol SendDataDelegate: class {
func sendData(_ dataArray:[String])
}
Here I suppose you want to send a single array back to FirstViewController
Then make your first view controller to conform to the custom protocol, such as:
class FirstViewController: UIViewController, SendDataDelegate
In the second view controller, create a delegate a variable for that protocol, such as:
weak var delegate: SendDataDelegate?
and then you catch the back action and inside it you call your custom protocol function, such as:
self.delegate?.sendData(arrayToSend)
In the first viewController, in the prepare for segue function just set the delegate like
vc.delegate = self

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]
}
}
}
}

How to store objects in memory

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
}
}