I have 3 UIViewControllers say:
v1ViewController
v2ViewController
v3ViewController.
I have pushed controllers as v2 on v1 and v3 on v2.
Now I want to bring some value back to v1ViewController from v3ViewController using delegates.
On v3Viewcontroller I wrote it:
for vc in self.navigationController!.viewControllers{
if vc is v1ViewController{
delegate?.returnFilteredImage(imageView.image!)
self.navigationController?.popToViewController(vc, animated: true)
}
}
How can I use delegates because in v1ViewController I haven't create object of v3Viewcontroller; consequently I cannot connect delegate to self.
So how can i do that.
1. Using delgates
create a protocol and implement in your firstviewcontroller
protocol My {
func returnFilteredImage(image: UIImage)
}
class FirstViewController: UIViewcontroller, My {
...
func returnFilteredImage(image: UIImage) {
}
}
and in your thirdViewController create a property and assign FirstViewController delegate to this.
class ThirdViewController: UIViewController {
var delegate: My?
...
override func viewDidLoad() {
super.viewDidLoad()
for vc in self.navigationController!.viewControllers{
if vc is FirstViewController {
let vc1 = vc as! FirstViewController
self.delegate = vc1
self.delegate?.returnFilteredImage(imageView.image!)
self.navigationController?.popToViewController(vc, animated: true)
}
}
}
}
2. Using local notifications
check here
You can do this using local notifications
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//add observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.didgetImage(_:)), name: "receiveImageNotification", object: nil)
}
func didgetImage(notification: NSNotification) {
if let image = notification.userInfo?["image"] as? UIImage {
// do something with your image
}
}
}
and from third view controller, first notification
let imageDataDict:[String: UIImage] = ["image": image]
NSNotificationCenter.defaultCenter().postNotificationName("receiveImageNotification", object: self, userInfo: imageDataDict)
Hope this helps :)
Related
I am using Spotify SDK. I want to change labels in some view controllers when a user changes his/her player state. Here is my scene delegate:
var playerViewController = MatchViewController()
func playerStateDidChange(_ playerState: SPTAppRemotePlayerState) {
playerViewController.stateChanged(playerState)
}
A view controller:
func stateChanged(_ playerState: SPTAppRemotePlayerState) {
// aLabel.text = playerState.track.name
}
The problem is labels or other outlets are nil when the state is changed because the view controllers are not loaded at that time. How can I fix that? (I tried isViewLoaded)
If you have a more than a few places to update according to a change that occurs at one place use observers. Here's how,
Post notification in SceneDelegate like this:
func playerStateDidChange(_ playerState: SPTAppRemotePlayerState) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "stateChanged"), object: nil, userInfo: ["playerState": playerState])
}
Observe in ViewControllers like this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(stateChanged), name: NSNotification.Name("stateChanged"), object: nil)
}
#objc func stateChanged(_ notification: Notification) {
if let playerState = notification.userInfo?["playerState"] as? SPTAppRemotePlayerState {
print(playerState)
}
}
}
I have an observer that I register in one class as so:
class ViewControllerA: UIViewController {
//create shared class reference
var sharedClassReference_A = SharedClass()
//initialize Notification Observer and store observer reference in sharedClass
override func viewDidLoad() {
super.viewDidLoad()
var observerHandler: Any? = nil
observerHandler = NotificationCenter.default.addObserver(self, selector: #selector(ViewControllerA.appDidTerminate(_:)), name: .UIApplicationWillTerminate, object: nil)
self.sharedClassReference_A.sharedHandler = observerHandler
}
//perform some action when a notification is received
#objc func appDidTerminate(_ notification: NSNotification) {
//perform some action
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueA_X" {
let destinationController = segue.destination as! ViewControllerX
destinationController.sharedClassReference_X = self.sharedClassReference_A
}
}
}
I store a reference to the observer in a shared class:
class SharedClass {
var sharedHandler: Any? = nil
}
I attempt to remove the observer once I reach a different view controller as so:
class ViewControllerX: UIViewController {
var sharedClassReference_X = SharedClass()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Attempt to remove observer registered in ViewControllerA
if let observerHandler = self.sharedClassReference_X.sharedHandler {
NotificationCenter.default.removeObserver(observerHandler)
}
}
}
I know that removing the observer using this approach is failing, because the observer is getting called after ViewControllerX is deallocated.
My question is: How can I successfully initialize an observer in one class (ViewControllerA) and be able to remove it later in a different class (ViewControllerX)?
I think it's better to follow the general guidelines of setting the observers inside viewDidLoad/viewWillAppear and removing them inside deinit / viewDidDisappear respectively according to your case , as this
NotificationCenter.default.addObserver(self, selector: #selector(ViewControllerA.appDidTerminate(_:)), name: .UIApplicationWillTerminate, object: nil)
returns void
//
class ViewControllerX: UIViewController {
var aRef:ViewControllerA!
}
in prepareForSegue
destinationController.aRef = self
Then use
NotificationCenter.default.removeObserver(aRef, name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
I have a mainTabBarController and inside it a mainViewController
I have the mainTabBarController's instance in the mainViewController
The problem is that when I add a notification call for the hello() function in the mainTabBarC, then it gets called twice
mainTabBarController:
class MainTabBarController : UITabBarController {
// Main Code
override func viewDidLoad() {
print("viewDidLoad")
NotificationCenter.default.addObserver(self, selector: #selector(hello), name: "sayHello", object: nil)
}
#objc func hello(){
print("Hello")
}
}
mainViewController:
class MainViewController: UITableViewController {
// Classes
let mainTabBarController = MainTabBarController()
}
And in AppDelegate I wanna call the hello function whenever app becomes active
func applicationDidBecomeActive(_ application: UIApplication) {
NotificationCenter.default.post(name: "sayHello", object: nil)
}
Now the problem is, that I have the mainTabBarC, and inside it I have the mainViewController which contains the mainTabBarC too..
And the hello() function will be called 2x times
How can I call a MainTabBarController function from MainViewController without creating a whole new instance?
Your MainViewController already holds a reference to MainTabBarController:
You could use it in two different ways:
// Option 1
if let tabBarController = tabBarController {
// do something with your tabBarController
}
// Option 2
guard let tabBarController = tabBarController else { return }
// do something with your tabBarController
I'm not sure if I'm doing it right, but the correct code in MainViewController might be:
var mainTabBarController: MainTabBarController!
override func viewDidLoad() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
mainTabBarController = storyboard.instantiateViewController(withIdentifier: "MainTabBarController") as! MainTabBarController
}
I am developing an application with swift 3.0. Where what I want to do is, from the "MainMapVC" class, which is the view where you have a map with a date slider (see the attached image). I want to move the slider and send that slider position (1,2 or 3) to LeftSideViewController which is the side view (the legend) updating the content depending on the selected date.
View of MainMapVC:
View of MainMapVC with Legend:
Well, and I've come to the point where I have to pass a value between the two view controllers. But problem is that I get the error "fatal error: unexpectedly found nil while unwrapping an optional value". Basically I have a "nil" delegate.
But do not find where the error is, because the definition of the delegate is like "var delegate: MainMapVCDelegate!" And I call it "delegate.moveSliderDates (datePos: Int (roundedValue))" in the "MainMapVC" class.
Does anyone know where I failed in the statement of the delegate?Thanks :)
I attach the code of the two classes so that you see the whole code.
Class MainMapVC (first way):
import UIKit
protocol MainMapVCDelegate: class {
func moveSliderDates(datePos: Int)
}
class MainMapVC: UIViewController, UISearchBarDelegate, CLLocationManagerDelegate, GMSMapViewDelegate {
//MARK: VARIABLES
weak var delegate: MainMapVCDelegate? = nil
let step: Float = 1
#IBAction func moveSliderDates(_ sender: UISlider) {
let roundedValue = round(sender.value / step) * step
sender.value = roundedValue
delegate?.moveSliderDates(datePos: Int(roundedValue))
}
}
The delegate value inside the moveSliderDates function is "nil":
delegate?.moveSliderDates(datePos: Int(roundedValue))
Class LeftSideViewController (first way):
import UIKit
class LeftSideViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, customCellDelegate, MainMapVCDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MainMapVC" {
let secondViewController = segue.destination as! MainMapVC
secondViewController.delegate = self
}
}
func moveSliderDates(datePos: Int){
print(datePos)
print("/////////////")
tableSideLeft.reloadData()
}
not enter inside this function because the delegate of "MainVC" is "nil":
Class MainMapVC (second way):
let step: Float = 1
#IBAction func moveSliderDates(_ sender: UISlider) {
let roundedValue = round(sender.value / step) * step
sender.value = roundedValue
let data:[String: Int] = ["data": Int(roundedValue)]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo: data)
}
Class LeftSideViewController (second way):
func listnerFunction(_ notification: NSNotification) {
if let data = notification.userInfo?["data"] as? String {
print(data)
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(listnerFunction(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)
}
Never goes into the function listnerFunction
You get the error because you defined your delegate as force unwrapped noy-nil version by this code var delegate: LeftSideDelegate!
Instead, you need to change it like this. You should not create strong reference cycle for delegate.
weak var delegate: LeftSideDelegate? = nil
Then for all your delegate calles, do the wrapped version delegate call
delegate?.changeZindexDelivery()
Other than that, change your line protocol LeftSideDelegate { into protocol LeftSideDelegate : class {
Passing data between view controllers using delegate
First, in the class where you want to pass the data to another view controller, declare protocol in this way
protocol SampleDelegate: class {
func delegateFunctionCall(data: String)
}
Then, create delegate variable as optional with type weak var. Call delegate method with you want to pass data or trigger action
class SecondViewController: UIViewController {
weak var delegate: SampleDelegate? = nil
#IBAction func sendTextBackButton(sender: AnyObject) {
delegate?.delegateFunctionCall(data: textField.text!)
}
}
Finally in your view controller that you want to receive action or data, implement the protocol. When you are initiating the second view controller, set it's delegate variable to be the current view controller
class FirstViewController: UIViewController, SampleDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destination as! SecondViewController
secondViewController.delegate = self
}
}
func delegateFunctionCall(data: String) {
label.text = data
}
}
Passing data between view controllers using notification
In the destination view controller, register a handler function that is ready to be called. You can add this registration code in view did load
NotificationCenter.default.addObserver(self, selector: #selector(listnerFunction(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)
func listnerFunction(_ notification: NSNotification) {
if let data = notification.userInfo?["data"] as? String {
// do something with your data
}
}
Then in another view controller, if you want to pass data, simply call this
let data:[String: String] = ["data": "YourData"]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo: data)
when a user receives a push notification and taps the notification, he/she will be brought into my app, where I want a certain view controller to appear. Therefore I use the notification center.
My question is, where do I need to perform the loading of the view controller so it will be shown and pushed on the navigation stack when the user enters the app?
func processReceivedRemoteNotification(userInfo:[NSObject:AnyObject]) {
let notification = userInfo as! Dictionary<String, AnyObject>
let json = JSON(notification)
// Get information from payload
let dispatchType:String = json["dispatch"]["dispatchType"].stringValue
switch dispatchType {
case "alert":
self.notificationCenter.postNotificationName("ALERT_RECEIVED", object: nil, userInfo: userInfo as [NSObject:AnyObject])
break
default:
break
}
}
View Controller to be loaded
class AlertViewController: UIViewController {
let notificationCenter: NSNotificationCenter = NSNotificationCenter.defaultCenter()
override func viewWillAppear(animated: Bool) {
self.notificationCenter.addObserver(self, selector: "alertMessageReceived:", name: "ALERT_RECEIVED", object: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func alertMessageReceived(notification: NSNotification) {
let userInfo = notification.userInfo as! Dictionary<String, AnyObject>
print(userInfo)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc1: AlertViewController = storyboard.instantiateViewControllerWithIdentifier("example1") as! AlertViewController
self.navigationController?.pushViewController(vc1, animated: true)
}
I don't know your app architecture, but from the given context I can see that you have a navigationController. You should not add as observer AlertViewController in this case. Instead move this code to another view controller, which is already pushed to navigationController. Another option is to subclass UINavigationController and observe "ALERT_RECEIVED" notification in it.