Access another view controller's property after an event - ios

I have 2 ViewControllers, the first one has an array that is filled by a fetch request once the ViewController appears. The second ViewController has a reference to that array. However, that is referencing an empty array which before it's being filled.
I want to get a reference once the array is filled. Here is a snippet.
class VC1: UIViewController {
let coreDataObjects = [NSManagedObject]() //At this moment, this array is empty
func viewWillAppear {
fetchRequestThatFillsTheArray() //Here, the array is filled
}
fetchRequestThatFillsTheArray() {
//calling NSFetchRequest
}
}
class VC2: UIViewController {
let vc1 = VC1()
vc1.coreDataObjects //Empty array
}
Thanks.

Here is the solution:
class VC1: UIViewController {
lazy var coreDataObjects = fetchRequestThatFillsTheArray()
func fetchRequestThatFillsTheArray() {
//calling NSFetchRequest
}
}
If you still want a solution on your original code, or you have issue to use fetchRequestThatFillsTheArray() in lazy . The you have to use NSNotification. Read Notification Programming Topics
--- UPDate for your comment----
You should change you code a little.
class VC1: UIViewController {
lazy var coreDataObjects:[NSManagedObject] = self.fetchRequestThatFillsTheArray()
func fetchRequestThatFillsTheArray() -> [NSManagedObject] {
//calling NSFetchRequest
}
}
However, since you used self.property in your fetchRequestThatFillsTheArray(), you can't use this solution.
You have to use NSNotification. That means you send a notification when fetchRequestThatFillsTheArray() finished. And register the notification in vc2. When vc2 get the notification, it does what it wants.

Related

One delegate Two classes

I am using a UISearchController with GoogleMap Autocomplete View controller to send data to my view controllers.
I have a Tabbar controller and I want to send the data two my ViewController A and B.
I have done all the necessary jobs but only one ViewController gets notified when user has used the UISearchController.
I have tried setting the delegate of each tab to nil when I move to the other tab, For example if I move from ViewController A to B
I will set the delegate of A to nil and then set the delegate of B to itself.
I am kinda new to swift so Can anyone help me understand why isn't this working?
I have tried debugging my code to see is my delegate is nil and it wasn't .
Here is how i set and unset the delegate
func setDelegate() {
print("MapViewController is not nil")
print(resultsViewController?.delegate)
resultsViewController?.delegate = self
print(resultsViewController?.delegate)
}
func unSetDelegate() {
print("MapViewController is nil")
resultsViewController?.delegate = nil
}
You need an observer pattern, if you need that one class instance notify to a number of other instances you need make an array of delegates (called observers) and register and deregister from that notifier instance class
Further info Wikipedia Observer Pattern
example code
This is the protocol that must implement any observer class
protocol GeotificationsManagerObserver : NSObjectProtocol{
func nearestGeotificationsHasChanged(pgeotifications:[Geotification])
}
Notifier class
class GeotificationsManager: NSObject {
/**...*//code
fileprivate var observers : [GeotificationsManagerObserver] = []
/**...*//code
}
Observers methods
extension GeotificationsManager
{
func addGeotificationsManagerObserver(observer:GeotificationsManagerObserver)
{
for currentObs in self.observers {
if(observer.isEqual(currentObs))
{
//we don't want add again
return
}
}
self.observers.append(observer)
}
func removeGeotificationsManagerObserver(observer:GeotificationsManagerObserver)
{
var observerIndex = -1
for (index,currObserver) in self.observers.enumerated() {
if(observer.isEqual(currObserver))
{
observerIndex = index
break
}
}
if(observerIndex != -1)
{
self.observers.remove(at: observerIndex)
}
}
//here make the notification to all observers in observers array
func nearestsGeotificationsHasChanged()
{
for currObserver in self.observers {
currObserver.nearestGeotificationsHasChanged(pgeotifications: self.getNearesGeotifications())
}
}
}
Important
You must remove the observer once you don't need being notified if not you will have memory issue
Example: You can add a UIViewController as Observer in viewDidAppear and can be removed in viewDidDisappear

Why is selector being sent to previous view controller?

I have an app with a data model class that declares a protocol, and two view controllers embedded in a navigation controller. The data model class is a shared instance. Both view controllers are delegates of the data model. The second view controller has a UITableView.
On start, calls to data model functions from the first view controller work as expected. When I segue from the first view controller to the second, calls to data model functions work as expected as well.
However, when I navigate back to the first view controller and a data model function is called, the app crashes with this error:
2017-04-03 09:48:12.623027 CoreDataTest[3207:1368182]
-[CoreDataTest.PracticesViewController noDupeWithResult:]: unrecognized selector sent to instance 0x15fe136e0
That PracticesViewController is the second view controller. I don't understand why a selector is being sent to what I am thinking of as the previous view controller. My expectation is that the selector should be sent to the first view controller that has just been navigated back to,
I am self-taught, so I presume there is something basic that I am missing, but I don't know what I don't know. Can someone explain why the crash is happening?
Code for the data model
import Foundation
import CoreData
import UIKit
#objc protocol PracticesDataDelegate {
#objc optional func practicesLoadError(headline:String,message:String)
#objc optional func practicesLoaded(practices:[NSManagedObject])
#objc optional func practiceStored()
#objc optional func practiceDeleted()
#objc optional func noDupe(result:String)
}
class PracticesDataModel {
static let sharedInstance = PracticesDataModel()
private init () {}
var delegate: PracticesDataDelegate?
var practices: [NSManagedObject] = []
// some code omitted . . .
/// Check for a duplicate exercise
func checkForDupe(title:String,ngroup:String,bowing:String,key:String){
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Practice")
let predicate = NSPredicate(format: "exTitle == %# AND notegroup == %# AND bowing == %# AND key == %#", title, ngroup, bowing, key)
fetchRequest.predicate = predicate
do {
practices = try managedContext.fetch(fetchRequest)
if practices.count == 0 {
self.delegate?.noDupe!(result:"none") // exception happens here
} else {
self.delegate?.noDupe!(result:"dupe")
}
} catch let error as NSError {
// to come
}
}
The top of the first view controller
import UIKit
class galamianSelection: UIViewController, ExcerciseDataModelDelegate, PracticesDataDelegate {
let exerciseModel = ExerciseDataModel.sharedInstance
let pModel = PracticesDataModel.sharedInstance
// some code omitted . . .
//// THE VIEW ////
override func viewDidLoad() {
super.viewDidLoad()
exerciseModel.delegate = self
pModel.delegate = self
exerciseModel.loadExercises()
}
//// RESPOND TO PRACTICE DATA MODEL ////
func practiceStored() {
print("exercise stored")
}
func noDupe(result: String) {
if result == "none" {
let d = Date()
pModel.storePractice(date: d, exTitle: theExerciseTitle.text!, notegroup: theNoteGroup.text!, bowing: theBowings.text!, rythem: theRhythms.text!, key: theKey.text!, notes: "")
} else {
print("dupe")
}
}
The top of the second view controller
import UIKit
class PracticesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, PracticesDataDelegate {
#IBOutlet weak var tableView: UITableView!
let pModel = PracticesDataModel.sharedInstance
// some code omitted . . .
//// THE VIEW ////
override func viewDidLoad() {
super.viewDidLoad()
pModel.delegate = self
pModel.getPractices()
}
delegate is properly set to self in both view controllers.
I am happy to provide more code is needed, but I suspect someone who knows can diagnose from what I've provided.
There are a few issues here.
You are trying to reuse a delegate but only setting it in viewDidLoad. viewDidLoad is only called when, as the name implies, the view initially loads. That means that if you have VC1 in a navigation controller, then you push to VC2, then pop back to VC1, viewDidLoad will not be called a second time. The view has already loaded. If you want a piece of code to be called every time a view controller comes back into focus, you should put it into viewWillAppear: or viewDidAppear:
You are force unwrapping an optional protocol method which you haven't actually implemented (a bug which you're seeing as a result of the first issue, but it's still an issue on its own). Change the force unwrap to an optional self.delegate?.noDupe?(result:"none")
You also declared your delegate like this var delegate: PracticesDataDelegate?. This makes your class retain its delegate and is generally not the right behavior. In your case it actually causes a retain cycle (until you change the delegate). You should change this declaration to weak var delegate: PracticesDataDelegate?
What appears to be happening is:
In PracticesViewController you set the model's delegate to self, as in pModel.delegate = self.
You navigate back to your first view controller. This means that PracticesViewController gets deallocated. But the model's delegate has not been changed, so it's still pointing to the memory location where the view controller used to be.
Later (but soon), your model tries to call a method on its delegate, but it can't because it was deallocated. This crashes your app.
You could reassign the value of the delegate, for example in viewDidAppear. Or you could have your model check whether its delegate implements a method before attempting to call it. That's a standard practice for optional protocol methods-- since they don't have to be implemented, you check first before calling them.
In general, don't use ! in Swift unless you want your code to crash there if something goes wrong.

In Swift, how do I access data in second ViewController from third ViewController? [duplicate]

Say I have multiple view controllers in my Swift app and I want to be able to pass data between them. If I'm several levels down in a view controller stack, how do I pass data to another view controller? Or between tabs in a tab bar view controller?
(Note, this question is a "ringer".) It gets asked so much that I decided to write a tutorial on the subject. See my answer below.
Your question is very broad. To suggest there is one simple catch-all solution to every scenario is a little naïve. So, let's go through some of these scenarios.
The most common scenario asked about on Stack Overflow in my experience is the simple passing information from one view controller to the next.
If we're using storyboard, our first view controller can override prepareForSegue, which is exactly what it's there for. A UIStoryboardSegue object is passed in when this method is called, and it contains a reference to our destination view controller. Here, we can set the values we want to pass.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegueID" {
if let destination = segue.destination as? SecondController {
destination.myInformation = self.myInformation
}
}
}
Alternatively, if we're not using storyboards, then we're loading our view controller from a nib. Our code is slightly simpler then.
func showNextController() {
let destination = SecondController(nibName: "SecondController", bundle: nil)
destination.myInformation = self.myInformation
show(destination, sender: self)
}
In both cases, myInformation is a property on each view controller holding whatever data needs to be passed from one view controller to the next. They obviously don't have to have the same name on each controller.
We might also want to share information between tabs in a UITabBarController.
In this case, it's actually potentially even simpler.
First, let's create a subclass of UITabBarController, and give it properties for whatever information we want to share between the various tabs:
class MyCustomTabController: UITabBarController {
var myInformation: [String: AnyObject]?
}
Now, if we're building our app from the storyboard, we simply change our tab bar controller's class from the default UITabBarController to MyCustomTabController. If we're not using a storyboard, we simply instantiate an instance of this custom class rather than the default UITabBarController class and add our view controller to this.
Now, all of our view controllers within the tab bar controller can access this property as such:
if let tbc = self.tabBarController as? MyCustomTabController {
// do something with tbc.myInformation
}
And by subclassing UINavigationController in the same way, we can take the same approach to share data across an entire navigation stack:
if let nc = self.navigationController as? MyCustomNavController {
// do something with nc.myInformation
}
There are several other scenarios. By no means does this answer cover all of them.
This question comes up all the time.
One suggestion is to create a data container singleton: An object that gets created once and only once in the life of your application, and persists for the life of your app.
This approach is well suited for a situation when you have global app data that needs to be available/modifiable across different classes in your app.
Other approaches like setting up one-way or 2-way links between view controllers are better suited to situations where you are passing information/messages directly between view controllers.
(See nhgrif's answer, below, for other alternatives.)
With a data container singleton, you add a property to your class that stores a reference to your singleton, and then use that property any time you need access.
You can set up your singleton so that it saves it's contents to disk so that your app state persists between launches.
I created a demo project on GitHub demonstrating how you can do this. Here is the link:
SwiftDataContainerSingleton project on GitHub
Here is the README from that project:
SwiftDataContainerSingleton
A demonstration of using a data container singleton to save application state and share it between objects.
The DataContainerSingleton class is the actual singleton.
It uses a static constant sharedDataContainer to save a reference to the singleton.
To access the singleton, use the syntax
DataContainerSingleton.sharedDataContainer
The sample project defines 3 properties in the data container:
var someString: String?
var someOtherString: String?
var someInt: Int?
To load the someInt property from the data container, you'd use code like this:
let theInt = DataContainerSingleton.sharedDataContainer.someInt
To save a value to someInt, you'd use the syntax:
DataContainerSingleton.sharedDataContainer.someInt = 3
The DataContainerSingleton's init method adds an observer for the UIApplicationDidEnterBackgroundNotification. That code looks like this:
goToBackgroundObserver = NSNotificationCenter.defaultCenter().addObserverForName(
UIApplicationDidEnterBackgroundNotification,
object: nil,
queue: nil)
{
(note: NSNotification!) -> Void in
let defaults = NSUserDefaults.standardUserDefaults()
//-----------------------------------------------------------------------------
//This code saves the singleton's properties to NSUserDefaults.
//edit this code to save your custom properties
defaults.setObject( self.someString, forKey: DefaultsKeys.someString)
defaults.setObject( self.someOtherString, forKey: DefaultsKeys.someOtherString)
defaults.setObject( self.someInt, forKey: DefaultsKeys.someInt)
//-----------------------------------------------------------------------------
//Tell NSUserDefaults to save to disk now.
defaults.synchronize()
}
In the observer code it saves the data container's properties to NSUserDefaults. You can also use NSCoding, Core Data, or various other methods for saving state data.
The DataContainerSingleton's init method also tries to load saved values for it's properties.
That portion of the init method looks like this:
let defaults = NSUserDefaults.standardUserDefaults()
//-----------------------------------------------------------------------------
//This code reads the singleton's properties from NSUserDefaults.
//edit this code to load your custom properties
someString = defaults.objectForKey(DefaultsKeys.someString) as! String?
someOtherString = defaults.objectForKey(DefaultsKeys.someOtherString) as! String?
someInt = defaults.objectForKey(DefaultsKeys.someInt) as! Int?
//-----------------------------------------------------------------------------
The keys for loading and saving values into NSUserDefaults are stored as string constants that are part of a struct DefaultsKeys, defined like this:
struct DefaultsKeys
{
static let someString = "someString"
static let someOtherString = "someOtherString"
static let someInt = "someInt"
}
You reference one of these constants like this:
DefaultsKeys.someInt
Using the data container singleton:
This sample application makes trival use of the data container singleton.
There are two view controllers. The first is a custom subclass of UIViewController ViewController, and the second one is a custom subclass of UIViewController SecondVC.
Both view controllers have a text field on them, and both load a value from the data container singlelton's someInt property into the text field in their viewWillAppear method, and both save the current value from the text field back into the `someInt' of the data container.
The code to load the value into the text field is in the viewWillAppear: method:
override func viewWillAppear(animated: Bool)
{
//Load the value "someInt" from our shared ata container singleton
let value = DataContainerSingleton.sharedDataContainer.someInt ?? 0
//Install the value into the text field.
textField.text = "\(value)"
}
The code to save the user-edited value back to the data container is in the view controllers' textFieldShouldEndEditing methods:
func textFieldShouldEndEditing(textField: UITextField) -> Bool
{
//Save the changed value back to our data container singleton
DataContainerSingleton.sharedDataContainer.someInt = textField.text!.toInt()
return true
}
You should load values into your user interface in viewWillAppear rather than viewDidLoad so that your UI updates each time the view controller is displayed.
Another alternative is to use the notification center (NSNotificationCenter) and post notifications. That is a very loose coupling. The sender of a notification doesn't need to know or care who's listening. It just posts a notification and forgets about it.
Notifications are good for one-to-many message passing, since there can be an arbitrary number of observers listening for a given message.
Swift 4
There are so many approaches for data passing in swift. Here I am adding some of the best approaches of it.
1) Using StoryBoard Segue
Storyboard segues are very much useful for passing data in between Source and Destination View Controllers and vice versa also.
// If you want to pass data from ViewControllerB to ViewControllerA while user tap on back button of ViewControllerB.
#IBAction func unWindSeague (_ sender : UIStoryboardSegue) {
if sender.source is ViewControllerB {
if let _ = sender.source as? ViewControllerB {
self.textLabel.text = "Came from B = B->A , B exited"
}
}
}
// If you want to send data from ViewControllerA to ViewControllerB
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is ViewControllerB {
if let vc = segue.destination as? ViewControllerB {
vc.dataStr = "Comming from A View Controller"
}
}
}
2) Using Delegate Methods
ViewControllerD
//Make the Delegate protocol in Child View Controller (Make the protocol in Class from You want to Send Data)
protocol SendDataFromDelegate {
func sendData(data : String)
}
import UIKit
class ViewControllerD: UIViewController {
#IBOutlet weak var textLabelD: UILabel!
var delegate : SendDataFromDelegate? //Create Delegate Variable for Registering it to pass the data
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
textLabelD.text = "Child View Controller"
}
#IBAction func btnDismissTapped (_ sender : UIButton) {
textLabelD.text = "Data Sent Successfully to View Controller C using Delegate Approach"
self.delegate?.sendData(data:textLabelD.text! )
_ = self.dismiss(animated: true, completion:nil)
}
}
ViewControllerC
import UIKit
class ViewControllerC: UIViewController , SendDataFromDelegate {
#IBOutlet weak var textLabelC: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnPushToViewControllerDTapped( _ sender : UIButton) {
if let vcD = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerD") as? ViewControllerD {
vcD.delegate = self // Registring Delegate (When View Conteoller D gets Dismiss It can call sendData method
// vcD.textLabelD.text = "This is Data Passing by Referenceing View Controller D Text Label." //Data Passing Between View Controllers using Data Passing
self.present(vcD, animated: true, completion: nil)
}
}
//This Method will called when when viewcontrollerD will dismiss. (You can also say it is a implementation of Protocol Method)
func sendData(data: String) {
self.textLabelC.text = data
}
}
Instead of creating a data controller singelton I would suggest to create a data controller instance and pass it around. To support dependency injection I would first create a DataController protocol:
protocol DataController {
var someInt : Int {get set}
var someString : String {get set}
}
Then I would create a SpecificDataController (or whatever name would currently be appropriate) class:
class SpecificDataController : DataController {
var someInt : Int = 5
var someString : String = "Hello data"
}
The ViewController class should then have a field to hold the dataController. Notice that the type of dataController is the protocol DataController. This way it's easy to switch out data controller implementations:
class ViewController : UIViewController {
var dataController : DataController?
...
}
In AppDelegate we can set the viewController's dataController:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let viewController = self.window?.rootViewController as? ViewController {
viewController.dataController = SpecificDataController()
}
return true
}
When we move to a different viewController we can pass the dataController on in:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
...
}
Now when we wish to switch out the data controller for a different task we can do this in the AppDelegate and do not have to change any other code that uses the data controller.
This is of course overkill if we simply want to pass around a single value. In this case it's best to go with nhgrif's answer.
With this approach we can separate view form the logic part.
As #nhgrif pointed out in his excellent answer, there are lots of different ways that VCs (view controllers) and other objects can communicate with each other.
The data singleton I outlined in my first answer is really more about sharing and saving global state than about communicating directly.
nhrif's answer lets you send information directly from the source to the destination VC. As I mentioned in reply, it's also possible to send messages back from the destination to the source.
In fact, you can set up an active one-way or 2-way channel between different view controllers. If the view controllers are linked via a storyboard segue, the time to set up the links is in the prepareFor Segue method.
I have a sample project on Github that uses a parent view controller to host 2 different table views as children. The child view controllers are linked using embed segues, and the parent view controller wires up 2-way links with each view controller in the prepareForSegue method.
You can find that project on github (link). I wrote it in Objective-C, however, and haven't converted it to Swift, so if you're not comfortable in Objective-C it might be a little hard to follow
SWIFT 3:
If you have a storyboard with identified segues use:
func prepare(for segue: UIStoryboardSegue, sender: Any?)
Although if you do everything programmatically including navigation between different UIViewControllers then use the method:
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
Note: to use the second way you need to make your UINavigationController, you are pushing UIViewControllers on, a delegate and it needs to conform to the protocol UINavigationControllerDelegate:
class MyNavigationController: UINavigationController, UINavigationControllerDelegate {
override func viewDidLoad() {
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
// do what ever you need before going to the next UIViewController or back
//this method will be always called when you are pushing or popping the ViewController
}
}
It depends when you want to get data.
If you want to get data whenever you want, can use a singleton pattern. The pattern class is active during the app runtime. Here is an example of the singleton pattern.
class AppSession: NSObject {
static let shared = SessionManager()
var username = "Duncan"
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(AppSession.shared.username)
}
}
If you want to get data after any action, can use NotificationCenter.
extension Notification.Name {
static let loggedOut = Notification.Name("loggedOut")
}
#IBAction func logoutAction(_ sender: Any) {
NotificationCenter.default.post(name: .loggedOut, object: nil)
}
NotificationCenter.default.addObserver(forName: .loggedOut, object: nil, queue: OperationQueue.main) { (notify) in
print("User logged out")
}
The way that I would do it would be instead of passing data between view controllers, I would just declare a variable globally. You can even do this with a function!
For example:
var a = "a"
func abc() {
print("abc")
}
class ViewController: UIViewController {
}

How do you share data between view controllers and other objects in Swift?

Say I have multiple view controllers in my Swift app and I want to be able to pass data between them. If I'm several levels down in a view controller stack, how do I pass data to another view controller? Or between tabs in a tab bar view controller?
(Note, this question is a "ringer".) It gets asked so much that I decided to write a tutorial on the subject. See my answer below.
Your question is very broad. To suggest there is one simple catch-all solution to every scenario is a little naïve. So, let's go through some of these scenarios.
The most common scenario asked about on Stack Overflow in my experience is the simple passing information from one view controller to the next.
If we're using storyboard, our first view controller can override prepareForSegue, which is exactly what it's there for. A UIStoryboardSegue object is passed in when this method is called, and it contains a reference to our destination view controller. Here, we can set the values we want to pass.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegueID" {
if let destination = segue.destination as? SecondController {
destination.myInformation = self.myInformation
}
}
}
Alternatively, if we're not using storyboards, then we're loading our view controller from a nib. Our code is slightly simpler then.
func showNextController() {
let destination = SecondController(nibName: "SecondController", bundle: nil)
destination.myInformation = self.myInformation
show(destination, sender: self)
}
In both cases, myInformation is a property on each view controller holding whatever data needs to be passed from one view controller to the next. They obviously don't have to have the same name on each controller.
We might also want to share information between tabs in a UITabBarController.
In this case, it's actually potentially even simpler.
First, let's create a subclass of UITabBarController, and give it properties for whatever information we want to share between the various tabs:
class MyCustomTabController: UITabBarController {
var myInformation: [String: AnyObject]?
}
Now, if we're building our app from the storyboard, we simply change our tab bar controller's class from the default UITabBarController to MyCustomTabController. If we're not using a storyboard, we simply instantiate an instance of this custom class rather than the default UITabBarController class and add our view controller to this.
Now, all of our view controllers within the tab bar controller can access this property as such:
if let tbc = self.tabBarController as? MyCustomTabController {
// do something with tbc.myInformation
}
And by subclassing UINavigationController in the same way, we can take the same approach to share data across an entire navigation stack:
if let nc = self.navigationController as? MyCustomNavController {
// do something with nc.myInformation
}
There are several other scenarios. By no means does this answer cover all of them.
This question comes up all the time.
One suggestion is to create a data container singleton: An object that gets created once and only once in the life of your application, and persists for the life of your app.
This approach is well suited for a situation when you have global app data that needs to be available/modifiable across different classes in your app.
Other approaches like setting up one-way or 2-way links between view controllers are better suited to situations where you are passing information/messages directly between view controllers.
(See nhgrif's answer, below, for other alternatives.)
With a data container singleton, you add a property to your class that stores a reference to your singleton, and then use that property any time you need access.
You can set up your singleton so that it saves it's contents to disk so that your app state persists between launches.
I created a demo project on GitHub demonstrating how you can do this. Here is the link:
SwiftDataContainerSingleton project on GitHub
Here is the README from that project:
SwiftDataContainerSingleton
A demonstration of using a data container singleton to save application state and share it between objects.
The DataContainerSingleton class is the actual singleton.
It uses a static constant sharedDataContainer to save a reference to the singleton.
To access the singleton, use the syntax
DataContainerSingleton.sharedDataContainer
The sample project defines 3 properties in the data container:
var someString: String?
var someOtherString: String?
var someInt: Int?
To load the someInt property from the data container, you'd use code like this:
let theInt = DataContainerSingleton.sharedDataContainer.someInt
To save a value to someInt, you'd use the syntax:
DataContainerSingleton.sharedDataContainer.someInt = 3
The DataContainerSingleton's init method adds an observer for the UIApplicationDidEnterBackgroundNotification. That code looks like this:
goToBackgroundObserver = NSNotificationCenter.defaultCenter().addObserverForName(
UIApplicationDidEnterBackgroundNotification,
object: nil,
queue: nil)
{
(note: NSNotification!) -> Void in
let defaults = NSUserDefaults.standardUserDefaults()
//-----------------------------------------------------------------------------
//This code saves the singleton's properties to NSUserDefaults.
//edit this code to save your custom properties
defaults.setObject( self.someString, forKey: DefaultsKeys.someString)
defaults.setObject( self.someOtherString, forKey: DefaultsKeys.someOtherString)
defaults.setObject( self.someInt, forKey: DefaultsKeys.someInt)
//-----------------------------------------------------------------------------
//Tell NSUserDefaults to save to disk now.
defaults.synchronize()
}
In the observer code it saves the data container's properties to NSUserDefaults. You can also use NSCoding, Core Data, or various other methods for saving state data.
The DataContainerSingleton's init method also tries to load saved values for it's properties.
That portion of the init method looks like this:
let defaults = NSUserDefaults.standardUserDefaults()
//-----------------------------------------------------------------------------
//This code reads the singleton's properties from NSUserDefaults.
//edit this code to load your custom properties
someString = defaults.objectForKey(DefaultsKeys.someString) as! String?
someOtherString = defaults.objectForKey(DefaultsKeys.someOtherString) as! String?
someInt = defaults.objectForKey(DefaultsKeys.someInt) as! Int?
//-----------------------------------------------------------------------------
The keys for loading and saving values into NSUserDefaults are stored as string constants that are part of a struct DefaultsKeys, defined like this:
struct DefaultsKeys
{
static let someString = "someString"
static let someOtherString = "someOtherString"
static let someInt = "someInt"
}
You reference one of these constants like this:
DefaultsKeys.someInt
Using the data container singleton:
This sample application makes trival use of the data container singleton.
There are two view controllers. The first is a custom subclass of UIViewController ViewController, and the second one is a custom subclass of UIViewController SecondVC.
Both view controllers have a text field on them, and both load a value from the data container singlelton's someInt property into the text field in their viewWillAppear method, and both save the current value from the text field back into the `someInt' of the data container.
The code to load the value into the text field is in the viewWillAppear: method:
override func viewWillAppear(animated: Bool)
{
//Load the value "someInt" from our shared ata container singleton
let value = DataContainerSingleton.sharedDataContainer.someInt ?? 0
//Install the value into the text field.
textField.text = "\(value)"
}
The code to save the user-edited value back to the data container is in the view controllers' textFieldShouldEndEditing methods:
func textFieldShouldEndEditing(textField: UITextField) -> Bool
{
//Save the changed value back to our data container singleton
DataContainerSingleton.sharedDataContainer.someInt = textField.text!.toInt()
return true
}
You should load values into your user interface in viewWillAppear rather than viewDidLoad so that your UI updates each time the view controller is displayed.
Another alternative is to use the notification center (NSNotificationCenter) and post notifications. That is a very loose coupling. The sender of a notification doesn't need to know or care who's listening. It just posts a notification and forgets about it.
Notifications are good for one-to-many message passing, since there can be an arbitrary number of observers listening for a given message.
Swift 4
There are so many approaches for data passing in swift. Here I am adding some of the best approaches of it.
1) Using StoryBoard Segue
Storyboard segues are very much useful for passing data in between Source and Destination View Controllers and vice versa also.
// If you want to pass data from ViewControllerB to ViewControllerA while user tap on back button of ViewControllerB.
#IBAction func unWindSeague (_ sender : UIStoryboardSegue) {
if sender.source is ViewControllerB {
if let _ = sender.source as? ViewControllerB {
self.textLabel.text = "Came from B = B->A , B exited"
}
}
}
// If you want to send data from ViewControllerA to ViewControllerB
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is ViewControllerB {
if let vc = segue.destination as? ViewControllerB {
vc.dataStr = "Comming from A View Controller"
}
}
}
2) Using Delegate Methods
ViewControllerD
//Make the Delegate protocol in Child View Controller (Make the protocol in Class from You want to Send Data)
protocol SendDataFromDelegate {
func sendData(data : String)
}
import UIKit
class ViewControllerD: UIViewController {
#IBOutlet weak var textLabelD: UILabel!
var delegate : SendDataFromDelegate? //Create Delegate Variable for Registering it to pass the data
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
textLabelD.text = "Child View Controller"
}
#IBAction func btnDismissTapped (_ sender : UIButton) {
textLabelD.text = "Data Sent Successfully to View Controller C using Delegate Approach"
self.delegate?.sendData(data:textLabelD.text! )
_ = self.dismiss(animated: true, completion:nil)
}
}
ViewControllerC
import UIKit
class ViewControllerC: UIViewController , SendDataFromDelegate {
#IBOutlet weak var textLabelC: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnPushToViewControllerDTapped( _ sender : UIButton) {
if let vcD = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerD") as? ViewControllerD {
vcD.delegate = self // Registring Delegate (When View Conteoller D gets Dismiss It can call sendData method
// vcD.textLabelD.text = "This is Data Passing by Referenceing View Controller D Text Label." //Data Passing Between View Controllers using Data Passing
self.present(vcD, animated: true, completion: nil)
}
}
//This Method will called when when viewcontrollerD will dismiss. (You can also say it is a implementation of Protocol Method)
func sendData(data: String) {
self.textLabelC.text = data
}
}
Instead of creating a data controller singelton I would suggest to create a data controller instance and pass it around. To support dependency injection I would first create a DataController protocol:
protocol DataController {
var someInt : Int {get set}
var someString : String {get set}
}
Then I would create a SpecificDataController (or whatever name would currently be appropriate) class:
class SpecificDataController : DataController {
var someInt : Int = 5
var someString : String = "Hello data"
}
The ViewController class should then have a field to hold the dataController. Notice that the type of dataController is the protocol DataController. This way it's easy to switch out data controller implementations:
class ViewController : UIViewController {
var dataController : DataController?
...
}
In AppDelegate we can set the viewController's dataController:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let viewController = self.window?.rootViewController as? ViewController {
viewController.dataController = SpecificDataController()
}
return true
}
When we move to a different viewController we can pass the dataController on in:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
...
}
Now when we wish to switch out the data controller for a different task we can do this in the AppDelegate and do not have to change any other code that uses the data controller.
This is of course overkill if we simply want to pass around a single value. In this case it's best to go with nhgrif's answer.
With this approach we can separate view form the logic part.
As #nhgrif pointed out in his excellent answer, there are lots of different ways that VCs (view controllers) and other objects can communicate with each other.
The data singleton I outlined in my first answer is really more about sharing and saving global state than about communicating directly.
nhrif's answer lets you send information directly from the source to the destination VC. As I mentioned in reply, it's also possible to send messages back from the destination to the source.
In fact, you can set up an active one-way or 2-way channel between different view controllers. If the view controllers are linked via a storyboard segue, the time to set up the links is in the prepareFor Segue method.
I have a sample project on Github that uses a parent view controller to host 2 different table views as children. The child view controllers are linked using embed segues, and the parent view controller wires up 2-way links with each view controller in the prepareForSegue method.
You can find that project on github (link). I wrote it in Objective-C, however, and haven't converted it to Swift, so if you're not comfortable in Objective-C it might be a little hard to follow
SWIFT 3:
If you have a storyboard with identified segues use:
func prepare(for segue: UIStoryboardSegue, sender: Any?)
Although if you do everything programmatically including navigation between different UIViewControllers then use the method:
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
Note: to use the second way you need to make your UINavigationController, you are pushing UIViewControllers on, a delegate and it needs to conform to the protocol UINavigationControllerDelegate:
class MyNavigationController: UINavigationController, UINavigationControllerDelegate {
override func viewDidLoad() {
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
// do what ever you need before going to the next UIViewController or back
//this method will be always called when you are pushing or popping the ViewController
}
}
It depends when you want to get data.
If you want to get data whenever you want, can use a singleton pattern. The pattern class is active during the app runtime. Here is an example of the singleton pattern.
class AppSession: NSObject {
static let shared = SessionManager()
var username = "Duncan"
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(AppSession.shared.username)
}
}
If you want to get data after any action, can use NotificationCenter.
extension Notification.Name {
static let loggedOut = Notification.Name("loggedOut")
}
#IBAction func logoutAction(_ sender: Any) {
NotificationCenter.default.post(name: .loggedOut, object: nil)
}
NotificationCenter.default.addObserver(forName: .loggedOut, object: nil, queue: OperationQueue.main) { (notify) in
print("User logged out")
}
The way that I would do it would be instead of passing data between view controllers, I would just declare a variable globally. You can even do this with a function!
For example:
var a = "a"
func abc() {
print("abc")
}
class ViewController: UIViewController {
}

Howto pass NSManagedObject via protocol/delegate?

I have a managed object defined:
#objc (Game)
class Game: NSManagedObject {
#NSManaged var player1: Player
#NSManaged var player2: Player
#NSManaged var totalScore: NSNumber
#NSManaged var gameDate: NSDate
}
I initialize it from ViewControllerA, then I give it to ViewControllerB using the delegate pattern. The protocol looks like this:
protocol gameProtocol {
func gameFunction(input: Game)
}
ViewControllerB signs up for the protocol:
class ViewControllerB: UIViewController, gameProtocol {...}
and implements this function to conform:
func gameFunction(input: Game) {
let currentGame = input
}
Then ViewControllerA can send a Game object to VCB as follows:
var gameDelegate: gameProtocol!
gameDelegate.gameFunction(myInitializedAndSavedGameObject)
This all works, but I need a class level variable inside ViewControllerB so that other code can be written to depend on the game. This, of course, does not work:
var currentGame = Game()
func gameFunction(input: Game) {
currentGame = input
}
I don't know the right words for it, but I think I want an initialized, empty Game object. I suppose I could write a convenience init that makes a temporary Game, but that doesn't seem like a good idea.
My current workaround is to have an NSManagedObjectID() and then recreate the object from the ID. But this is a lot of repeated code to get at an object that is central to what this ViewController is designed to work with.
So you want to push your NSManagedObject to your second View Controller?- You dont need an delegate, you could send your object as instance variable, as Wain already said.
For Example (in your MainViewController)
class MainViewControntroller: UIViewController {
var currentGameObject:YOURNSMANAGEDOBJECT!
func viewDidLoad() {
// load your Object
var fetchRequest = NSFetchRequest(entityName: "Game")
.....
games = context.executeFetchRequest(fetchRequest, error: nil) as [YOURNSMANAGEDOBJECT]
if(games.count > 0) {
// set to any game object (here is only the last)
currentGameObject = games.last?
}
}
}
// Initialize your Second View Controller (for example when using segues)
if(segue.identifier == "yourIdentifierInStoryboard") {
var yourNextViewController = (segue.destinationViewController as yourNextViewControllerClass)
yourNextViewController.currentGameObject = currentGameObject
So you are able to use your NSManagedObject in your SecondViewController - if you want to push it back, you could use a delegate.
'lazy var currentGame = Game()' seems to be what I want. By making the var lazy, the designated initializer is never incorrectly called. I am certain that the first thing to touch the var will be the gameFunction method, so my other code will compile and it won't crash at runtime. Alternative suggestions welcome.

Resources