How to send data back to previous screen IOS/Swift [duplicate] - ios

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 2 years ago.
I am creating a library in IOS/swift that:
takes a user to a scene --> performs a task --> return to the initial scene that called the first while passing a payload back to the user
I have figured out how to take users back to the previous scene that called it, but my issue is how to send a payload back with it using thee code snippet below:
func switchToPreviousPage(){
self.dismiss(animated: true, completion: nil)
}
How do I achieve this?

In your scenario you can use either :
Delegation Pattern
Notification/Observer
Lets discuss each one :
1. Delegation :
If you have idea about Protocol in Swift you can do it easily.
first create a protocol with the required function you want to implement :
protocol FirstControllerDelegate: AnyObject {
func sendData(data: String)
}
Suppose your firstPage is FirstViewController, it has a UILabel and we have to assign a String to it from our secondPage means SecondViewController. the Structure of your FirstViewController may be like this :
class FirstViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
#IBAction func gotoSecondPage() {
let secondVC = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
}
}
Now your FirstViewController has to confirm to this protocol and it will implement the sendData(data: ) method :
extension FirstViewController: FirstControllerDelegate {
func sendData(data: String) {
textLabel.text = data
}
}
Now as a feature of Protocol in iOS, Protocols can work as a Type(like Int, String). So just create a variable of type FirstControllerDelegate in your SecondViewController !
class SecondViewController: UIViewController {
weak var delegate: FirstControllerDelegate!
#IBAction func switchToPreviousPage() {
delegate.sendData(data: "Hello")
self.dismiss(animated: true, completion: nil)
}
}
You can now call the sendData(data:) function with the variable you created above !
At last you have to do oneThing just assign the delegate :
secondVC.delegate = self
It should be inside the gotoSecondPage() method !
2. Notification/Observer
With this, our basic idea is to send a Notification inside our app, and it can be observed by any where inside !
So our SecondViewController will send a Notification embedded with required data that we want to pass, and FirstViewController will receive the Notification and it will extract the data from the Notification !!
Each Notification has a specific name, which will differentiate it from other Notifications. we have to create the Name :
Notification.Name(rawValue: "com.app.notificationObserver")
Now the FirstViewController will be Observe to this specific notification :
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.changeLabelText(notifcation:)), name: Notification.Name("com.app.notificationObserver"), object: nil)
}
We have to define changeLabelText(notification:) method :
private func changeLabelTExt(notification: NSNotification) {
if let dataDict = notification.userInfo as NSDictionary? {
if let message = dataDict["data"] as? String {
self.textLabel.text = message
}
}
}
Finally, SecondViewController will trigger the Notification :
#IBAction func switchToPreviousPage() {
NotificationCenter.default.post(name: Notification.Name(rawValue: "com.app.notificationObserver"), object: ["data": "hello"])
self.dismiss(animated: true, completion: nil)
}
Thats All .....

Related

Get Data from UIViewController to Another UIViewController

Suppose I have a storyboard like so:
Is it possible for me to get a flag or a boolean data from A back to B? I initially thought of using delegation but most of the tutorials about it talks about sending data between UIViewControllers that are part of 1 NavigationController. In my case, the UIViewController I need to get data is outside of the navigation controller. Is there a way for me to send data from A to B despite not being embedded in the same NavigationController?
If you don't want to use delegate between the classes . One possible way is to create separated file , saved in class and fetch required data any where in navigation .
Useful class for your case would be create singleton class FlowEngine . Use getter / setter method for saving and fetching of data. Code is attached for your reference .
class FlowEngine : NSObject{
private let static shared = FlowEngine()
private var data : String
private init(){
}
func savedData(text : String){
data = text
}
func fetchSavedData() -> String{
return data // add checsk for nil values
}
}
Delegation doesn't require the ViewControllers to be in same navigation stack. You can use the same for your case. However, if you choose to go with NotificationCenter, just remember to remove the observer when appropriate.
Other answers seem to accomplish your requirements but for the sake of completeness you could try to use KVC and KVO for modifying values in A and receiving its changes in B (or any other place)
You could see a detailed explanation of how to use them in here.
You have several ways to go, depending on your needs :
Delegation
Declare a protocol in A, and make B conform to it. Set the delegate of A to B. This could be cumbersome if the navigation stack has too many level, as you would need to pass the reference of B to each ViewController between A & B
Notification / KVO
B subscribe to a notification sent by A, no reference needed, thread safe. Don't forget to unsubscribe when done.
Proxy class
Use a proxy singleton class, that will hold your data. A will write to it, and B will read it in viewWillAppear.
UserDefaults
Same concept as a Proxy Class, but the data will persist during your app life cycle and even after killing the app. It's appropriate if you want to change a flag or a setting for your user, not if you have a lot of data to hold.
Cocoa Touch uses the target-action mechanism for communication between a control and another object. More here... If you would like to use it with UIControl objects like buttons, then you can set it in Interface Builder by sending an action to the FirstResponder object.
Target-Action will start searching a VC which responds to a given method from the current first responder and then will move to the next responder and will terminate a search in a current UIWindow. Once a controller which responds to a method signature is found, the search is terminated.
class AViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func configure(with dictionary: Dictionary<String, Any>) {
print(dictionary)
}
}
class BViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let a = self.targetViewController(forAction: #selector(ViewController.configure(with:)), sender: self) as? ViewController
a?.configure(with: ["firstName": "Alex", "lastName": "Toto"])
}
}
if your A viewController is not huge, In B viewController do this :
class B : UIViewController {
var a : A! = nil
func viewDidLoad() {
super.viewDidLoad()
a = storyboard?.instantiateViewController(withIdentifier: "StoryBoard ID") as? A
if a.booleanValue == true {
// use your booleanValue
a = nil // deallocate after using your value.
}
}
}
Update (better solution)
We've had to edit a few things to the functionality which presented me with the opportunity to refactor this. I used the NSNotification way, which was way cleaner than using closures.
ViewControllerB
override func viewDidLoad() {
super.viewDidLoad()
//Observe for notification from "myIdentifier"
NotificationCenter.default.addObserver(self, selector: #selector(self.processNotification(notification:)), name: Notification.Name("myIdentifier"), object: nil)
}
//function that gets called when notification is received
//the #objc annotation is required!
#objc func processNotification(notification: Notification) {
//Do something
}
ViewControllerA
#IBAction func didTapButton(_ sender: Any) {
//Process something
// ...
//
//Post a notification to those observing "myIdentifier"
NotificationCenter.default.post(name: Notification.Name("myIdentifier"), object: nil)
self.dismiss(animated: true, completion: nil)
}
Old (but working) solution
This might be an unpopular solution but I managed to solve this with callbacks. I was looking into another possible solution which was commented NSNotification but since someone from the team already had experience with using callbacks in this manner, we decided to ultimately use that.
How we made it work:
ViewControllerB is given the actual code implementation through prepare(for segue: UIStoryboardSegue, sender: Any?) while ViewControllerC (This is the middle UIViewController in the picture) has a callback property and ViewControllerA contains the value to pass when it's about to be dismissed.
ViewControllerB
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "secondSegue" {
let nvc: NavigationController = segue.destination as! NavigationController
let vc = nvc.viewControllers[0] as! ViewControllerC
vc.completion = { hasAgreed in
//Do Something
}
}
}
ViewControllerC
class ViewControllerC: UIViewController {
var completion: ((Bool) -> ())?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "thirdSegue" {
let nvc: NavigationController = segue.destination as! NavigationController
let vc = nvc.viewControllers[1] as! ViewControllerA
vc.middleController = self
}
}
ViewControllerA
class ViewControllerC: UIViewController {
var middleController: ViewControllerC?
#IBAction func didTapButton(_ sender: Any) {
self.dismiss(animated: true, completion: {
middleController?.completion(true)
})
}
}
With this, we got the data we needed from the diagram picture above.
Your best bet is to make use of NotificationCenter to achieve this.
Post notification like this:
NotificationCenter.default.post(name: Notification.Name("NotificationName"), object: nil, userInfo: ["somekey":"somevalue"])
Observe it like this:
NotificationCenter.default.addObserver(self, selector: #selector(self.dataReceived(notification:)), name: Notification.Name("NotificationName"), object: nil)
Use the following method:
#objc func dataReceived(notification: Notification) {}

Pushing notifications from one Viewcontroller to another in Swift?

I want to push a notification from one(1st) ViewController to (2nd)Another ViewController while this notification takes a function of the first ViewController and prints the result of the function. How can I do that? Please help, I searched all the internet with no result.
Try this
Post Notification: (First ViewController)
NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: nil, userInfo: ["key":"value"])
Get Notification: (Second ViewController)
NotificationCenter.default.addObserver(self, selector: #selector(self.ReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)
Method to handle received Notification:
#objc func ReceivedNotification(notification: Notification){
//Take Action on Notification
}
You can easily make use of protocols to pass the data between controller same as observer
Step 1 Create a protocol stub
/// Protcol
#objc protocol showViews : class {
/// An optional func inside protocol that will
/// be used to pass the required Data
/// For now, I am just passing a Bool Value
#objc optional func showToggleMenu(needToShow:Bool)
}
Source ViewController - Controller from which data is to be passed
/// Source Controller
class mainHomeVC: UIViewController
{
/// Create a referencing object for the protocol
var delegate : showViews?
/// To pass data just call the delegate and its function
#IBAction func showToggleMenu(_ sender: UIButton)
{
/// Pass Data
delegate?.showToggleMenu!(needToShow: true)
}
}
Destination Controller Controller in which Data was required to be passed
/// Destination Controller
class homeVC: UIViewController
{
/// Create a reference of class from which data is to be fetched
private lazy var mainHomeVCObjet: mainHomeVC =
{
// Instantiate View Controller
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "mainHomeVC") as! mainHomeVC
viewController.delegate = self
// Add View Controller as Child View Controller
self.addChildViewController(viewController)
return viewController
}()
}
//MARK: Protocol Extension
extension homeVC : showViews
{
//MARK: Show Side Menu ?
func showToggleMenu(needToShow: Bool)
{
if (needToShow)
{
print("time to show a toggle Menu")
}
}
}

Observers don't get called using NotificationCenter Swift 4.0

I have a two ViewControllers: ViewController and SecondViewController.
I added an observer to this two ViewControllers. In ViewController I also defined an IBAction to post the notification. I handle the notification via a closure in both ViewControllers. But only the closure in the ViewController gets called. The closure (and even the whole code) in the SecondViewController does not get called (I checked with debugger). The closure only contains a print-statement.
Here is my Code
//ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nc = NotificationCenter.default
nc.addObserver(forName: Notification.Name(rawValue:"MyNotification"), object: nil, queue: nil) { (notification) in
print("I'm the : \(type(of: self))")
}
}
#IBAction func sendNotification(_ sender: UIButton) {
let nc = NotificationCenter.default
nc.post(name: Notification.Name(rawValue:"MyNotification"), object: nil, userInfo: ["message":"Hello there!", "Date:":Date()])
}
}
The ScondViewController
//SecondViewController
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nc = NotificationCenter.default
nc.addObserver(forName: Notification.Name(rawValue:"MyNotification"), object: nil, queue: nil) { (notification) in
print("I'm the: \(type(of: self))")
}
}
}
The closure in ViewController gets called but the closure in SecondViewController does not. Maybe the reason is that SecondViewController does not get initialized before I post the notification. But how would a solution look like?
Any help is appreciated.
If this is one-to-one relationship you can also use the Delegate pattern instead. Although you are right, the notification observer is not yet called because you do not initialise before the post. So there shouldn't be a reason for a function to be called in the SecondViewController.
So if you only need that to update a variable value, you can do that somewhere else in a Variables class where both ViewControllers have access.

How to push user to ViewController from non UIView class [duplicate]

This question already has answers here:
How to launch a ViewController from a Non ViewController class?
(7 answers)
Closed 5 years ago.
I would like to know how can I push user back to specific ViewController from regular swift class without being non UIView Class
Example
class nonUI {
function Push() {
//How to send user back to specific VC here?
}
}
This is a generic method you can use with in the class or outside the class for push if required else it will pop if the instance of view controller is in the stack:
func pushIfRequired(className:AnyClass) {
if (UIViewController.self != className) {
print("Your pushed class must be child of UIViewController")
return
}
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var isPopDone = false
let mainNavigation = UIApplication.shared.delegate?.window??.rootViewController as? UINavigationController
let viewControllers = mainNavigation!.viewControllers
for vc in viewControllers {
if (type(of: vc) == className) {
mainNavigation?.popToViewController(vc, animated: true)
isPopDone = true
break
}
}
if isPopDone == false{
let instanceSignUp = storyboard.instantiateViewController(withIdentifier: NSStringFromClass(className)) // Identifier must be same name as class
mainNavigation?.pushViewController(instanceSignUp, animated: true)
}
}
USES
pushIfRequired(className: SignupVC.self)
You could also utilise the NotificationCenter to achieve a loosely coupled way to "request a view controller"; if you will.
For example, create a custom UINavigationController that observes for the custom Notification and upon receiving one, looks for the requested UIViewController and pops back to it.
class MyNavigationController : UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name("RequestViewController"), object: nil, queue: OperationQueue.main) { [unowned self] (note) in
guard let targetType = note.object as? UIViewController.Type else {
print("Please provide the type of the VC to display as an `object` for the notification")
return
}
// Find the first VC of the requested type
if let targetVC = self.viewControllers.first(where: { $0.isMember(of: targetType) }) {
self.popToViewController(targetVC, animated: true)
}
else {
// do what needs to be done? Maybe instantiate a new object and push it?
}
}
}
}
Then in the object you want to go back to a specific ViewController, post the notification.
#IBAction func showViewController(_ sender: Any) {
NotificationCenter.default.post(Notification(name: NSNotification.Name("RequestViewController"), object: ViewController2.self))
}
Now, it's also fairly easy to adopt this method for other presentation-styles.
Instead of using the NotificationCenter, you could also muster up a Mediator to achieve the loose coupling.
You can't. UIViewController and its subclass only can handle navigate between screen.
In your case, need pass link (variable) to navigation controller in custom class.
Like:
class nonUI {
var navigationController: UINavigationController?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
function Push() {
//How to send user back to specific VC here?
navigationController?.popViewController(animated: true)
}
}

fatal error on calling function from another class

I'm having trouble on calling the function from a different class. I have this:
class ViewController: UIViewController {
func addToOrder(orderNumber:String) {
orderCount.text = orderNumber
}
}
Now in my other class:
class TableViewController: UITableViewController {
func addToOrder(button: UIButton) {
ViewController().addToOrder("100")
//I also tried
var menu = ViewController()
menu.addToOrder("100")
}
}
I'm getting error on this line
orderCount.text = orderNumber
with this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
You can use NSNotificationCenter for that.
Follow this step:
first of all add this in your first viewController where you want to update text:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshTable:", name: "refresh", object: nil)
}
Which will add an observer when your load your first view and also add this helper method which will call when you come back to this view:
func refreshTable(notification: NSNotification) {
let orderDetail = NSUserDefaults.standardUserDefaults().integerForKey("order") //this will read your integer which you will save on second view.
orderCount.text = "\(orderDetail)"
}
In your next view add this code when you are coming back to previous view.
#IBAction func goBack(sender: AnyObject) {
//store your int here
NSUserDefaults.standardUserDefaults().setInteger(100, forKey: "order")
//send notification to first view.
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
Hope this will help.
It's orderCount (which probably is a UILabel) which is nil at initialization-time. If this is a IBOutlet created in a storyboard, you will need to store your text as a separate property, and set the text of your Label to this property in your ´viewDidLoad´ method

Resources