I have the following protocols:
protocol MyDelegate: class {
func closeViewController()
}
protocol MyProtocol: class {
weak var delegate: SomeClass? {get set}
}
And the following class:
class SomeClass: MyDelegate {
var myViewController: UIViewController
init(myViewController: UIViewController){
self.myViewController = myViewController
self.myViewController.delegate = self
}
func closeViewController() {
myViewController.dismiss(animated: true, completion: nil)
}
}
The idea here is that SomeClass takes a view controller and sets itself as the view controller's delegate.
The View controller is defined like so:
class SomeViewController: UIViewController, MyProtocol {
weak var delegate: SomeClass?
...
#IBAction func close(_ sender: Any) {
delegate?.closeViewController()
}
...
}
where the close function is mapped to a close button in storyboard.
I initialize both SomeClass and my view controller inside another view controller.
var someViewController = // initialized here
var someClass = SomeClass(myViewController: someViewController)
self.present(someViewController, animated: true, completion: nil)
However, when I press the close button, nothing happens at all. The view controller does not dismiss.
On the other hand, if I change the close() function in my ViewController to be:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Then it dismisses itself as expected, showing that the function is correctly mapped to the button.
How do I go about dismissing my view controller from another class?
You have declared delegate property as weak and there isn't any strong reference of SomeClass object. Object should be nil by the time close button callback and closeViewController() is never called.
If I may suggest that you've made this much more complicated than it needs to be. I would ditch the protocol altogether and just implement a simple delegate pattern.
class SomeViewController {
func close() {
print("did close")
}
}
class SomeObject {
weak var delegate: SomeViewController?
func close() {
delegate?.close()
}
}
let viewController = SomeViewController()
let object = SomeObject()
object.delegate = viewController
object.close() // prints "did close"
Related
According to this link , I wanted to pass some data from a B viewController to its parent, A viewController, on back press! here is my code :
in my B viewController, I've added this code -
extension Bcontroller: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
(viewController as? Acontroller)?.number = numberInB
(viewController as? Acontroller)?.myBoolean = boolInB
}
}
and here is my code in A controller :
override func viewWillAppear(_ animated: Bool) {
if number != -1 {
print(myBoolean)
}
}
when I open B controller, navigationController (willShow) is called, and when I press back button, viewWillAppear is called first, and then navigationController(willShow) in B controller is called! so my data is not set, and number will be always -1 . how can I set these variable?
Please find the below steps to implement delegate.
Step 1:- Initialise the protocol in view controller B.
ViewcontrollerB.m
protocol ViewControllerDelegate
{
func didUpdateViewController(_ number: NSNumber, myBoolean: Bool);
}
Step 2:-
initalise object inside viewcontrollerB
var delegate:ViewControllerDelegate?
Step 3:-
Now call this delegate from back function.Here I am considering back is the function to pop viewcontroller.
func Back()
{
delegate?.didUpdateViewController(numberInB!, myBoolean: boolInB!)
}
Step 4:-
Inherit the protocol in viewcontrollerA.
class ViewControllerA: UIViewController,ViewControllerDelegate
Step 5:-
Now Set the delegate in viewcontrollerA.
ViewcontrollerA.m
override func viewDidLoad() {
super.viewDidLoad()
let obj = ViewControllerB()//Initialize it as per your code
obj.delegate = self;
// Do any additional setup after loading the view, typically from a nib.
}
Final Step:-
override the delegate method.
func didUpdateViewController(_ number: NSNumber, myBoolean: Bool) {
print(number,myBoolean)
}
Let me know if it worked
Do call it in viewWillDisappear of controller B:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
let navigationController: UINavigationController = self.navigationController!
let controllers: [Acontroller] = navigationController.viewControllers.filter({ $0 is Acontroller }) as! [Acontroller]
if let viewController: Acontroller = controllers.first {
viewController.number = numberInB
viewController.myBoolean = boolInB
}
}
If the controller is in stack the values will be assigned to it.
If it is a small data then use UserDefaults:
This is how you store data:
UserDefaults.standard.set(1, forKey: "Key")
And this is how you get it back:
UserDefaults.standard.integer(forKey: "Key")
Or you can use a struct with a static variable:
struct ShareValue {
static var UserNum: Int = 0
}
Create a delegate which other view controllers can implement and call the delegate method in viewWillDisappear when isMovingFromParentViewController is true
public protocol YourViewControllerDelegate: class {
func didGoBack(viewController: YourViewController)
}
public class YourViewController: UIViewController {
public weak var delegate: YourViewControllerDelegate?
public override func viewWillDisappear(_ animated: Bool) {
if isMovingFromParentViewController {
delegate?.didGoBack(viewController: self)
}
}
}
In your other view controller:
extension YourOtherViewController: YourViewControllerDelegate {
public func didGoBack(viewController: YourViewController) {
// Do something e.g. set your variables
}
}
How can I call the playAgain() function which is defined in ViewController, from DisplayScoreController ?
ViewController.swift
class ViewController: UIViewController {
func playAgain() {
print("Play Again")
}
}
DisplayScoreController.swift
class DisplayScoreController: UIViewController {
#IBAction func playAgain(_ sender: Any) {
dismiss(animated: true, completion: nil)
// I want to call playAgain() in ViewController
}
You can do this by passing a closure to the DisplayScoreController that dismisses that controller and calls playAgain().
In the example below, I set up that closure in prepare(for:sender:). If you aren't launching DisplayScoreController with a segue, you could assign this closure just after you instantiate the DisplayScoreController and before you present it.
In my example, I trigger the calling of the closure when the user has pressed the Done button in DisplayScoreController. You could put the call to self.goPlayAgain?() anywhere you want to trigger that action.
class ViewController: UIViewController {
func playAgain() {
print("Play Again")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showScore" {
if let dvc = segue.destination as? DisplayScoreController {
// assign closure to goPlayAgain property
// of destination view controller to dismiss
// the destination and call playAgain()
dvc.goPlayAgain = {
self.dismiss(animated: true, completion: nil)
self.playAgain()
}
}
}
}
}
class DisplayScoreController: UIViewController {
// property to hold closure which dismisses this
// view controller and calls playAgain() in
// ViewController
var goPlayAgain: (() -> ())?
// Time to return to ViewController and call playAgain()
#IBAction func done(_ sender: UIButton) {
self.goPlayAgain?()
}
}
You don't have to call it a delegate, but that's the idea. You need a pointer to the object that has the playAgain() function. Defining a protocol and adding an instance var to your DisplayScoreController that points to an object that conforms to that protocol enables DisplayScoreController to send the playAgain() message to some other object.
You can call the object fred if you want to:
protocol PlayAgainProtocol {
func playAgain()
}
class DisplayScoreController: UIViewController {
weak var fred: PlayAgainProtocol?
#IBAction func playAgain(_ sender: Any) {
dismiss(animated: true, completion: nil)
fred?.playAgain()
}
}
The variable is named fred rather than delegate. Does that mean you're not using delegates? No, not really, but you aren't using the term delegate...
I have two UIViewController, when I click a button, it goes from the first view controller to the second one. And before that, I animated a UIView to move to another place. After dismissing the second View Controller, I want to move the UIView in the first view controller back to where it originally was. However, when I call a function from the second View Controller to animate the UIview in the first view controller after dismissing the second one, It could not get the UIView's properties, and cannot do anything with it. I think because the first UIViewController is not loaded yet. Is that the problem? And How should I solve this?
There are two solutions you can either use swift closures
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.callbackClosure = { [weak self] in
print("Do your stuff")
}
self.navigationController?.pushViewController(secondController, animated: true)
}
}
//----------------------------
class SecondController: UIViewController {
var callbackClosure: ((Void) -> Void)?
override func viewWillDisappear(_ animated: Bool) {
callbackClosure?()
}
}
or you can use protocols
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.delegate = self
self.navigationController?.pushViewController(secondController, animated: true)
}
}
extension ViewController : ViewControllerSecDelegate {
func didBackButtonPressed(){
print("Do your stuff")
}
}
//--------------------------
protocol SecondControllerDelegate : NSObjectProtocol {
func didBackButtonPressed()
}
class SecondController: UIViewController {
weak var delegate: SecondControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
delegate?.didBackButtonPressed()
}
}
You can try to use a closure. Something like this:
class FirstViewController: UIViewController {
#IBOutlet weak var nextControllerButton: UIButton!
private let animatableView: UIView = UIView()
private func methodsForSomeAnimation() {
/*
perform some animation with 'animatableView'
*/
}
#IBAction func nextControllerButtonAction() {
// you can choose any other way to initialize controller :)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let secondController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondController.callbackClosure = { [weak self] in
self?.methodsForSomeAnimation()
}
present(secondController, animated: true, completion: nil)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var dismissButton: UIButton!
var callbackClosure: ((Void) -> Void)?
#IBAction func dismissButtonAction() {
callbackClosure?()
dismiss(animated: true, completion: nil)
/*
or you call 'callbackClosure' in dismiss completion
dismiss(animated: true) { [weak self] in
self?.callbackClosure?()
}
*/
}
}
When you present your second view controller you can pass an instance of the first view controller.
The second VC could hold an instance of the first VC like such:
weak var firstViewController: NameOfController?
then when your presenting the second VC make sure you set the value so it's not nil like so:
firstViewController = self
After you've done this you'll be able to access that viewControllers functions.
iOS 11.x Swift 4.0
In calling VC you put this code ...
private struct Constants {
static let ScannerViewController = "Scan VC"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Constants.ScannerViewController {
let svc = destination as? ScannerViewController
svc?.firstViewController = self
}
}
Where you have named the segue in my case "Scan VC", this is what it looks like in Xcode panel.
Now in scan VC we got this just under the class declaration
weak var firstViewController: HiddingViewController?
Now later in your code, when your ready to return I simply set my concerned variables in my firstViewController like this ...
self.firstViewController?.globalUUID = code
Which I have setup in the HiddingViewController like this ...
var globalUUID: String? {
didSet {
startScanning()
}
}
So basically when I close the scanning VC I set the variable globalUUID which in term starts the scanning method here.
When you are saying it could not get the UIView's properties it's because you put it as private ? Why you don't replace your UIView in the first controller when it disappears before to go to your secondViewController. I think it's a case where you have to clean up your view controller state before to go further to your second view controller.
Check IOS lifecycle methods : viewWillDisappear or viewDidDisappear through Apple documentation and just do your animation in one of these methods.
Very simple solution actually... Just put your animation in the viewDidAppear method. This method is called every time the view loads.
class firstViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// insert animation here to run when FirstViewController appears...
}
}
I know the same question is asked many times. I read most of the answers from stack overflow and tried. But it did not help my problem.
I have two view controllers
protocol UpdateDataDelegate {
func loadData()
}
viewcontroller2 {
var delegate: UpdateDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
fun saveData() {
self.delegate?.loadData()
}
}
viewcontroller1 : UpdateDataDelegate {
var vc2 = viewcontroller2()
override func viewDidLoad() {
super.viewDidLoad()
vc2.delegate = self
}
func loadData() {
}
}
But function loadData() from viewcontroller1 is not called.
Since I don't have the complete code before me I can only assume that the delegate is not assumed properly.
If the delegate is not initialised properly it cannot pass value to the other viewController.
You can check delegate is properly initialised by:
if let delegate = delegate{
//Do your works here
}else{
print("The delegate is nil")
}
if the delegate is nil is printed in console, then the problem might be in the way the delegate was initialised
This might be because you are setting the delegate and opening an another instance of the viewController which was not assigned the delegate value.
In the code you provided I see that you are setting the delegate as
var vc2 = viewcontroller2()
vc2.delegate = self
But I cannot see the code that you used to move to the viewController2. Now we have to present this assigned viewController. Instead of using segue to move to the viewcontroller2 present this vc using the code below
present(vc2, animated: true, completion: nil)
You should place this according to your code logic.(where your segue is triggered)
Situation 2:
If you are using segue to move to the viewController2 then the delegate should be assigned in the prepareforSegue method as below
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc2 = segue.destination as? ViewController2{
vc2.delegate = self
}
}
let me know how it goes.
A simple playground for what you are trying to do, even if I have not clear what you are trying to achieve:
import UIKit
protocol UpdateDataDelegate: class {
func loadData()
}
class ViewController2: UIViewController {
weak var delegate: UpdateDataDelegate?
func saveData() {
self.delegate?.loadData()
}
}
class ViewController1: UIViewController {
}
extension ViewController1: UpdateDataDelegate {
func loadData() {
print("loadData called")
}
}
let viewController1 = ViewController1()
let viewController2 = ViewController2()
viewController2.delegate = viewController1
viewController2.saveData()
Few notes:
classes should be upper case. So, ViewController1 instead viewcontroller1
delegates should be weak otherwise you create reference cycles
class should be used for UpdateDataDelegate protocol otherwise compiler will complain since weak cannot be applied to class and class-bound protocol types
prefer extension to conform to protocols. It makes the code easy to read
The only thing I see missing in your code is call to saveData() of ViewController2 that will in turn call loadData() of ViewController1.
So just add:
override func viewDidLoad()
{
super.viewDidLoad()
vc2.delegate = self
vc2.saveData() //Add this line to your code
}
You are good to go now :)
Edit:
protocol UpdateDataDelegate
{
func loadData()
}
class ViewController2: UIViewController
{
var delegate: UpdateDataDelegate?
func saveData()
{
self.delegate?.loadData()
}
}
class ViewController1: UIViewController, UpdateDataDelegate
{
var vc2 = ViewController2()
override func viewDidLoad()
{
super.viewDidLoad()
vc2.delegate = self
vc2.saveData()
}
func loadData()
{
print("Done")
}
}
I have used the above code and it is working fine for me. How are you executing it? I have used storyboard and used ViewController1 as the Initial View Controller.
I assume that you need to load data when your delegate has been set up. In this case you can use magic didSet:
weak var delegate: UpdateDataDelegate? {
didSet {
self.saveData()
}
}
So right after setting the delegate the needed method will be called.
I have two controllers and i need call up function the first controller to second controller:
In second controller I have created protocol and init delegate in class:
protocol testProtocol {
func testDelegate() // this function the first controllers
}
class SecondViewController: UIViewController {
var delegate: testProtocol?
....
}
#IBAction func testDelegateClicked(sender : AnyObject) {
delegate?.testDelegate()
}
First Controller
class ViewController: UIViewController, testProtocol {
var secondController: SecondViewController = SecondViewController()
override func viewDidLoad() {
super.viewDidLoad()
secondController.delegate = self
}
func testDelegate() {
println("Hello delegate")
}</pre>
But function not getting called
I am going to make an assumption you are using storyboards. If I am correct, then your issue is that your secondController, created in your First Controller, is not the actual one you are presenting. You will need to set secondController in your prepareForSegue:
Second Controller
Unchanged
First Controller
class ViewController: UIViewController, testProtocol {
// you will want to add the ? since this variable is now optional (i.e. can be nil)
var secondController: SecondViewController? // don't assign it a value yet
// ...
// implementation of the protocol
func testDelegate() {
println("Hello delegate")
}
// your prepare for segue
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
// get the controller that storyboard has instantiated and set it's delegate
secondController = segue!.destinationViewController as? SecondViewController
secondController!.delegate = self;
}
}