I have been trying to create a protocol and delegate within my custom UIView subclass, but when I call it on View Controller class the button is not responding to the method. I have read almost all the answers on StackOverflow, none of them solve my issue. Here is the procedure I have followed:
UIView Subclass (View A)
Create the require delegate:
protocol LoginDelegates {
func loginButtonPressed(sender: AnyObject)
}
Within Subclass
class LoginViewController: UIViewController {
#IBOutlet weak var loginButton: UIButton!
var delegate: LoginDelegates? // Initite the delegate variable
//Login Button Action
#IBAction func loginButtonPressed(_ sender: UIButton) {
delegate?.loginButtonPressed(sender: loginButton)
}
}
View Controller Class (View B)
Call the delegate within the class:
class AccessViewController: UIViewController, LoginDelegates
In here I tried multiple approaches I have found on SO and around web. but none of them seems to work for me, the button still doesn't respond.
Approach 1:
override func viewDidLoad() {
super.viewDidLoad()
//Assign the delegate in current view
let LoginView = LoginViewController()
LoginView.delegate = self
}
Approach 2:
with this approach, I get the following error doesn't contain a view controller with identifier 'LoginViewController', which make sense cause the view is subclass and not within the storyboard.
override func viewDidLoad() {
super.viewDidLoad()
//Assign the delegate in current view
if let loginView = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController {
loginView.delegate = self
}
}
and of course, call the method in the current view.
func loginButtonPressed(sender: AnyObject) {
performSegue(withIdentifier: "toDashboard", sender: self)
}
I also try to create a swift file and just put all the protocols separate from both views and just call them when needed. and At this point, I have no other option. Thank you for help in advance.
Update your LoginViewController like so:
class LoginViewController: UIViewController {
static weak var shared: LoginViewController?
#IBOutlet weak var loginButton: UIButton!
weak var delegate: LoginDelegates? // Initite the delegate variable
//Login Button Action
#IBAction func loginButtonPressed(_ sender: UIButton) {
delegate?.loginButtonPressed(sender: loginButton)
}
override func viewDidLoad() {
super.viewDidLoad()
// All your viewDidLoad stuff
LoginViewController.shared = self
}
}
Update your AccessViewController like so:
class AccessViewController: UIViewController, LoginDelegates {
override func viewDidLoad() {
super.viewDidLoad()
// All your viewDidLoad stuff
LoginViewController.shared?.delegate = self
}
func loginButtonPressed(sender: AnyObject) {
performSegue(withIdentifier: "toDashboard", sender: self)
}
}
The problem you have caused because you don't have access to your active instance of LoginViewController. In both your approaches you create a new instance of LoginViewController but you need to access existing instance. To make this you have to create static variable which will keeps for you a reference to the active instance of the LoginViewController.
Also please remember that in most cases you have to mark delegate variable as weak to avoid memory leaks.
Because of you load and present view of your LoginViewController directly in AccessViewController view your LoginViewController is not the firstResponder. Because of this the action of your button is not called while pressing.
You can make some trick in your AccessViewController class
class AccessViewController: UIViewController, LoginDelegates {
override func viewDidLoad() {
super.viewDidLoad()
// All your viewDidLoad stuff
let loginVC = LoginViewController()
loginVC.loginButton.addTarget(self, action: #selector(self.loginButtonPressed), for: .touchUpInside)
//add your LoginViewController view as subview to your AccessController view.
}
#objc func loginButtonPressed() {
performSegue(withIdentifier: "toDashboard", sender: self)
}
}
Related
I want to load the function checkStatus() (which is part of my ViewController1) from my ViewController 2 before the Navigation Controller pops back to the ViewController1.
Unfortunately, when calling the function, the app crashes as soon as it loads and I am really frustrated becaue I do not know what I did wrong.
The ViewControllers are embeded in a Navigation Controller.
Code in ViewController1:
func checkStatus(){
/* setting Label texts (Outlets) to a specific value but it is
irrelevant as the compiler does not even get to
this point. The program crashes as soon as the function is called (tried it with prints).*/
Code in ViewController2:
#IBAction func didTapBack(_ sender: UIButton){
// first the function is animating something inside the VC2
ViewController1().checkStatus() // function gets called here
self.navigationController?.popToRootViewController(animated: false)
}
I am grateful for any kind of help.
You can use Delegate Pattern to call a function in your case.
ViewController code:
import UIKit
class ViewController: UIViewController, SecondViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func gotoSecondVC(_ sender: UIButton) {
let secondVC = self.storyboard?.instantiateViewController(identifier: "SecondViewController") as! SecondViewController
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
}
func checkStatus() {
print("\(#function) called...")
}
}
SecondViewController code:
import UIKit
protocol SecondViewControllerDelegate: class {
func checkStatus()
}
class SecondViewController: UIViewController {
weak var delegate: SecondViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func backButtonTapped(_ sender: UIButton) {
delegate?.checkStatus()
self.navigationController?.popViewController(animated: true)
}
}
Refernce image Im new to protocols, I tried the following implementation to achieve the protocols, but im getting nil value for the protocol object
import UIKit
//MARK: step 1 Add Protocol here.
protocol MyDelegate: class {
func changeBackgroundColor(_ color: UIColor?)
}
class ViewController: UIViewController {
//MARK: step 2 Create a delegate property here.
weak var delegate: MyDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//MARK: step 3 Add the delegate method call here.
delegate?.changeBackgroundColor(UIColor.red)
}
}
Here I am getting delegate value nil and protocol not getting called.
here is the implementation
import UIKit
class HomeViewController: UIViewController, MyDelegate {
func changeBackgroundColor1(_ color: UIColor?) {
self.view.backgroundColor = color
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
callVC()
}
func callVC() {
let vc = ViewController()
vc.delegate = self
}
func changeBackgroundColor(_ color: UIColor?) {
self.view.backgroundColor = color
}
In short:
You have forgotten to set the delegate to the object that conforms the protocol.
Detailed Answer:
Protocol is an agreement between two objects so that you know a delegate will have some specified functions ready to be called. Let's explain with an example:
Suppose that you have two view controllers called A and B. You have defined your protocol in global (as you did in your code). Then you create a property called delegate in A which will hold a weak reference of any object conforms the protocol. In this case, this held object reference is B.
So you need to have a property in A like below:
weak var delegate: MyDelegate?
Then determine this delegate to be the reference of what you need. In our example, it's B. So you need to set it in B as below.
// Somewhere you have the reference of the object or where you initialize it.
instanceOfA.delegate = self
Finally you conform the protocol in B like:
extension B: MyProtocol {
func changeBackgroundColor(_ color: UIColor?) {
// some implementation goes here
}
}
There you are. Now, you can make sure that you have delegate object and protocol methods are getting called if you have completed steps above correctly.
The problem lies in your callVC method:
func callVC()
{
let vc = ViewController()
vc.delegate = self
}
You create an instance of your ViewController, but you are not doing anything with it. No methods, aside from initializer, will be called on that view controller because it's not a part of active navigation stack. Also, since it's just a local variable (not retained anywhere) it will be deallocated immediately after leaving the method scope.
What you need to do is to present the view controller somehow - either with a navigation controller, or as a child to the current view controller
func callVC()
{
let vc = ViewController()
vc.delegate = self
addChildViewController(vc)
view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
}
or if you're using navigation controller
func callVC()
{
let vc = ViewController()
vc.delegate = self
navigationController?.pushViewController(vc, animated: true)
}
Hi Smart i think the problem is when you set the delegate
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
callVC()
}
func callVC() {
let vc = ViewController()
vc.delegate = self
}
Because you set the delegate for the class ViewController, but you don't present the viewController at all; you should set the delegate before presenting the ViewController, one way to do it is to present the ViewController and in function callVc add this
func callVC() {
let vc = ViewController()
vc.delegate = self
//Present the viewController with this
present(vc, animated: true, completion: nil)
}
Your optional delegate variable is nil. You need to set it first from the UIViewController you are segueing from. For example.
class OtherViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.performSegue(withIdentifier: "addHereTheSegueIdFromStoryBoard", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addHereTheSegueIdFromStoryBoard" {
guard let vc = segue.destination as? ViewController else {return}
vc.delegate = self
}
}
}
import UIKit
class ViewController: UIViewController {
weak var delegate: MyDelegate?
override func viewDidLoad() {
super.viewDidLoad()
if delegate != nil {
delegate?.changeBackgroundColor(UIColor.red)
}
}
}
Call like
class SecondViewController: UIViewController(), MyDelegate {
func callVC() {
let vc = ViewController()
vc.delegate = self
}
func changeBackgroundColor(_ color: UIColor?) {
//Code here
}
}
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 a UIPageViewcontroller containing three ViewControllers. One Viewcontroller is my ProfileViewcontroller. I have a button in my ProfileViewController, which should tell the UIPageViewCongtroller when pressed, to switch to the next Viewcontroller.
It's my first time implementing a delegate, but I just can't figure out why its not working.
UIPageViewController class:
class PageViewController: UIPageViewController, ProfileViewControllerDelegate {
private(set) lazy var orderedViewControllers: [UIViewController] = {
// The view controllers will be shown in this order
return [self.newColoredViewController("Search"),
self.newColoredViewController("Menu"),
self.newColoredViewController("Profile"),
self.newColoredViewController("Offer")]
}()
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if orderedViewControllers.count >= 2 {
scrollToViewController(orderedViewControllers[1])
}
}
// MARK: ProfileViewControllerDelegate
func profileViewControllerDidTouchOffer(viewController:ProfileViewController, sender: AnyObject) {
scrollToNextViewController()
print("I'm pressing the offer Button")
}
ProfileViewController class:
protocol ProfileViewControllerDelegate : class {
func profileViewControllerDidTouchOffer(controller: ProfileViewController, sender: AnyObject)
}
class ProfileViewController: UIViewController {
weak var delegate: ProfileViewControllerDelegate?
#IBOutlet var profileImageView: SpringImageView!
#IBOutlet var offerButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func offerButtonTouchUpInside(sender: AnyObject) {
delegate?.profileViewControllerDidTouchOffer(self, sender: sender)
}
Answer
I updated the PageViewController class by changing the way I add the ViewControllers in orderderedViewControllers:
private(set) lazy var orderedViewControllers: [UIViewController] = {
// The view controllers will be shown in this order
let profileVC = UIStoryboard(name: "Main", bundle: nil) .instantiateViewControllerWithIdentifier("ProfileViewController") as! ProfileViewController
profileVC.delegate = self
return [self.newColoredViewController("Search"),
self.newColoredViewController("Menu"),
profileVC,
self.newColoredViewController("Offer")]
}()
Looks like you're using the InterfaceBuilder so I'm not sure how it's done there (probably in prepareForSegue), but a typical pattern is something like this:
let vc = ProfileViewController()
vc.delegate = self // Or whatever you want to act as the delegate
// Now show the view controller.
The key is that before using the view controller, you make sure it's delegate property is set to the thing that is the delegate and conforms to the protocol. Typically the calling controller would be the delegate.
EDIT: If you're using the UIPageViewController, then you should add the delegate to the viewController before passing it to setViewControllers https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPageViewControllerClassReferenceClassRef/#//apple_ref/occ/instm/UIPageViewController/setViewControllers:direction:animated:completion:
I've stucked on a simple concept(I guess), basically I have two ViewControllers on Storyboard also I have 2 classes, ViewController and ViewController2:
I Have a Label whit a default value (0), and when I click on button I want to change the value for this variable to 10, and then I click on the button "Show" and I print this variable, I'm successfully changing the Label and printing the new Value.
The real problem is when I want to get the new variable value from another view, even after I change the value if I try to print the variable on second view the variable always return de default value(0)
ViewController
import UIKit
class ViewController: UIViewController {
var variable = "0"
#IBOutlet var defaultLabel: UILabel!
#IBOutlet var label1Label: UILabel!
#IBAction func setValue(sender: AnyObject) {
setValue()
}
#IBAction func getValue(sender: AnyObject) {
getValue()
}
override func viewDidLoad() {
super.viewDidLoad()
}
func setValue(){
variable = "10"
defaultLabel.text = variable
}
func getValue(){
print(variable)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
ViewController2
import UIKit
class ViewController2: UIViewController {
#IBOutlet var label2Label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func show(sender: AnyObject) {
print(ViewController().getValue())
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I've found this post:
Access variable in different class - Swift
And I really think this is the way that I will find my solution but I really don't understand how to call the variable on ViewController2.
Thanks.
#IBAction func show(sender: AnyObject) {
print(ViewController().getValue())
}
ViewController() - this is class constructor and each time you call ViewController() it return a new instance/object of ViewController class, with default values of course.
If you show ViewController2 from ViewController you can create a property/variable variable2 like variable in ViewController and set value before display, but after ViewController2 is created. If you use segues you can put this code in ViewController class:
// Put this code in ViewController class
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController2 = segue.destinationViewController as? ViewController2
if viewController2 != nil {
// you can't set the value for label at this time
// because the viewcontroller and all its UI controls aren't loaded
// but you can set a non UI variable
viewController2?.variable2 = self.variable
}
}
After that you can put one line of code in viewDidLoad method from ViewController2 class:
// Put this code in ViewController2 class
var variable2 = "0"
#IBOutlet var label2Label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.label2Label.text = variable2
}
Use Delegates!
Here's an example where ViewController1 is the delegate for ViewController2:
Define a protocol:
protocol VariableManager {
func getValue() -> Int
}
Then, in ViewController1, modify the getValue method so that ViewController1 conforms to the protocol:
class ViewController1: VariableManager {
func getValue() -> String {
return variable
}
}
Now define a variable in ViewController2 named delegate:
class ViewController2 {
var delegate: VariableManager?
}
In your prepareForSegue method in ViewController1 :
override func prepareForSegue(segue: UIStoryboardSegue) {
if let identifier = segue.identifier {
switch identifier {
case "MySegueIdentifier":
let destination = segue.destinationViewController as! 'ViewController2'
destination.delegate = self
default:
break
}
}
}
Now in ViewController2, change the show method:
#IBAction func show(sender: AnyObject) {
if let delegate = delegate {
let variable = delegate.getValue()
print(variable)
}
Delegation is a very common, and very important pattern. I suggest you read up on it: https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
Trying to instantiate another instant of ViewController1 inside ViewController2 is not good practice.