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:
Related
I have been trying to refactor my source code so that it would conform to the Coordinator Pattern. I have used UITabBarController as the parent viewController of my app which contains 4 viewControllers.
I have been following the tutorials on how to implement the Coordinator pattern for iOS apps, and I have created and set up the protocols and classes of the Coordinator. I have a button inside my viewController (child viewController of the TabbarViewController), however, on button click, coordinator is not pushing / navigating to the desired VC, and I see the coordinator is returning nil on the debug console while debugging through the breakpoint, and I could not figure it out how to resolve this issue.
MainCoordinator.swift:
class MainCoordinator: SubCoordinator {
var subCoordinators = [SubCoordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
print("Initialized.. .")
UIApplication.app().window?.rootViewController = self.navigationController
let vc = SplashViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
}
}
// testing using a simple Viewcontroller class, its background color is set to red, so if the
// navigation works, a blank red VC should appear. but not working so far
func testView() {
let vc = ViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
}
}
SubCoordinator.swift:
protocol SubCoordinator {
var subCoordinators: [SubCoordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
StoryBoarded.swift:
protocol StoryBoarded {
static func instantiate() -> Self
}
// I am using storyBoard, and `instantiate()` should instantiate and return the specified VC
// from the Storyboard with the specified VC id (?)
extension StoryBoarded where Self: UIViewController {
static func instantiate() -> Self {
let id = String(describing: self)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return storyboard.instantiateViewController(withIdentifier: id) as! Self
}
}
FirstViewController.Swift:
class FirstViewController: UIViewController, StoryBoarded {
#IBOutlet weak var button: UIButton!
var coordinator: MainCoordinator?
//MARK: - viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// If uncommented the below line, coordinator is not returning `nil`, but not navigating
anyways!
//coordinator = MainCoordinator(navigationController: UINavigationController())
}
#IBAction func onButtonTap(_ sender: Any) {
// So, basically I would expect the coordinator to navigate to the testView, but not
navigating
coordinator?.testView()
}
}
ViewController.swift:
// testView
class ViewController: UIViewController, StoryBoarded {
var coordinator: MainCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = .red
}
}
and
// TabbarController, set as the root VC after the splashVC is completed
class MainViewController: UITabBarController, StoryBoarded {
var coordinator: MainCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
let firstVC = UIStoryboard.firstViewController()
let secondVC = UIStoryboard.secondViewController()
let views: [UIViewController] = [firstVC, secondVC]
self.setViewControllers(views, animated: false)
self.navigationController?.navigationBar.isHidden = false
}
}
start() is being called, and splashVC appears and updates rootViewController with MainViewontroller on completion, But the navigation is not working at all on button click event.
Any feedback or help would highly be appreciated!
Since you're using the StoryBoarded protocol, you should follow the pattern and call instantiate() for initialization. Then, just set the coordinator.
class MainViewController: UITabBarController, StoryBoarded {
var coordinator: MainCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
let firstVC = FirstViewController.instantiate()
let secondVC = SecondViewController.instantiate()
firstVC.coordinator = self.coordinator
secondVC.coordinator = self.coordinator
let views: [UIViewController] = [firstVC, secondVC]
self.setViewControllers(views, animated: false)
self.navigationController?.navigationBar.isHidden = false
}
}
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 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)
}
}
I have been searching for how the delegate works and I tried to do it in my project. Unfortunately, the delegate method I implement does not get called ever. I am trying to do a slide-out navigation panel. so what I did is that I put two uicontainerviews, one is for slide-out navigation panel and the other for main view controller
enter image description here
The code is that
For main view controller
protocol MainViewControllerDelegate {
func toggleSideMenu()
}
class MainViewController: UIViewController {
var delegate: MainViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Slide Action
#IBAction func slideMenuTapped(_ sender: UIBarButtonItem){
delegate?.toggleSideMenu()
print("Slide Menu has been tapped")
}
}
For container view controller
class ContainerVC: UIViewController {
#IBOutlet weak var SideMenuConstraint: NSLayoutConstraint!
#IBOutlet weak var slideMenuContainer: UIView!
#IBOutlet weak var mainViewContainer: UIView!
var mainViewController: MainViewController?
var isSideMenuOpened = false
override func viewDidLoad() {
super.viewDidLoad()
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
}
}
extension ContainerVC: MainViewControllerDelegate{
func toggleSideMenu() {
print("It works")
if isSideMenuOpened{
isSideMenuOpened = false
SideMenuConstraint.constant = -260
mainViewContainer.layer.shadowOpacity = 0
} else {
isSideMenuOpened = true
SideMenuConstraint.constant = 0
mainViewContainer.layer.shadowOpacity = 0.59
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
extension UIStoryboard{
static func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
static func mainViewController() -> MainViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
}
}
Please let know what's wrong
I think the reason is that you embed your main view controller in navigation controller :
let navigationController = self.childViewControllers.last as! UINavigationController
let mainViewController = navigationController.topViewController as! MainViewController
mainViewController?.delegate = self
Here is where you got wrong:
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
this mainViewController is not the same as the child of the container view controller, so setting its delegate doesn't really do anything.
You need to first get the VC that is the child of the container view controller:
mainViewController = self.childViewControllers.last as! MainViewController
mainViewController.delegate = self
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;
}
}