I have created a navigation barButton in tabBarController and I want to call delegate method on this button.
/// Delegate
protocol DataExportDelegate {
func generateCSVFile()
}
class TabbarController: UITabBarController {
var exportDelegate: DataExportDelegate?
override func viewDidLoad() {
super.viewDidLoad()
///**barbutton**
let barButtonItem = UIBarButtonItem(
image: UIImage(named: "icon"),
style: UIBarButtonItem.Style.plain,
target: self, action: #selector(onClick(_:)
)
)
self.navigationItem.rightBarButtonItem = barButtonItem
}
#objc func onClick(_ sender: UIBarButtonItem) {
exportDelegate?.generateCSVFile()
}
}
/// ViewController
class viewController: UIViewController, DataExportDelegate {
var tabbar_Controller: TabbarController = TabbarController()
override func viewDidLoad() {
super.viewDidLoad()
tabbar_Controller.exportDelegate = self
}
func generateCSVFile() {
print("Delegate called")
}
}
This
var tabbar_Controller: TabbarController = TabbarController()
creates a new instance other than the presented 1 , so replace
tabbar_Controller.exportDelegate = self
With
(self.tabBarController as! TabbarController).exportDelegate = self
Related
My scenario, I am having Two UIBarButton with action method, Here, whenever I am clicking Done and Cancel button I am moving to another ViewController. Once user clicked the Done barbutton I need to set some flag value and validate it another ViewController for button clicked or not clicked.
My ViewController One
let barButtonItem = UIBarButtonItem(image: UIImage(named: "backImgs"),
style: .plain,
target: self,
action: #selector(menuButtonTapped))
self.navigationItem.rightBarButtonItem = barButtonItem
#objc fileprivate func menuButtonTapped() { // here I need to set flag value }
My ViewController Two
class ViewControllertwo: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Here need to validate flag values to button clicked or not
}
}
I partially understand your question , in here you need to go with tag concept, for e.g
override func viewDidLoad() {
super.viewDidLoad()
let barButtonItem = UIBarButtonItem(image: UIImage(named: "backImgs"),
style: .plain,
target: self,
action: #selector(menuButtonTapped(_:)))
barButtonItem.tag = 20
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(menuButtonTapped(_:)))
cancelButton.tag = 10
self.navigationItem.rightBarButtonItem = barButtonItem
self.navigationItem.leftBarButtonItem = cancelButton
}
handle your target function is like
#objc fileprivate func menuButtonTapped(_ sender: UIBarButtonItem) {
// if you dont want the tag concept, use title property for check which button tapped //print("get Tapped button title == \(sender.title)")
//if sender.tag == 20{
// clicked for another VC button, add your segue code here
// }else{
// pressed cancel button
// }
let vcTwo = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllertwo") as! ViewControllertwo
vcTwo.getSelectedTag = sender.tag
self.navigationController?.pushViewController(vcTwo, animated: true)
}
on your VC2 create the one global Int for get the tag where its comes from,
** ViewControllertwo**
class ViewControllertwo : UIViewController {
var getSelectedTag = 0
override func viewDidLoad() {
super.viewDidLoad()
if getSelectedTag == 20 {
//pressed menu Tapped
}
}
}
It's hard to tell from your question, but assuming you are presenting ViewControllerOne from ViewControllerTwo, you're going to want to use the delegate pattern for this. This is similar to the way you use table/collection views, and you are essentially telling ViewControllerTwo to be ViewControllerOne's delegate so that it can react to the buttons being pressed.
Start by creating a protocol that defines the messages that ViewControllerOne can send to its delegate:
protocol ViewControllerOneDelegate: AnyObject {
func viewControllerOneDidTapDone(_ viewController: ViewControllerOne)
func viewControllerOneDidTapCancel(_ viewController: ViewControllerOne)
}
Then extend ViewControllerTwo to implement your protocol:
extension ViewControllerTwo: ViewControllerOneDelegate {
func viewControllerOneDidTapDone(_ viewController: ViewControllerOne) {
// Set your flag or do whatever you need to do on 'Done'.
// Then dismiss viewController.
}
func viewControllerOneDidTapCancel(_ viewController: ViewControllerOne) {
// Dismiss viewController
}
}
In ViewControllerOne, keep the delegate as a weak property and call the delegate methods on button press:
class ViewControllerOne: UIViewController {
weak var delegate: ViewControllerOneDelegate?
#objc private func donePressed() {
delegate?.viewControllerOneDidTapDone(self)
}
#objc private func cancelPressed() {
delegate?.viewControllerOneDidTapCancel(self)
}
}
Lastly, somewhere in ViewControllerTwo, you need to set yourself as ViewControllerOne's delegate. This will likely be when creating ViewControllerOne:
class ViewControllerTwo: UIViewController {
...
private func presentViewControllerOne() {
let viewControllerOne = ViewControllerOne(nibName:nil, bundle: nil)
viewControllerOne.delegate = self
// Present or push viewControllerOne
}
...
}
As I understand your question the solution is -
ViewContorllerOne
class ViewControllerOne : UIViewController {
var isMenubuttonTapped : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
self.actionToPushOnViewControllerTwo()
NotificationCenter.default.addObserver(self, selector: #selector(actioFire), name: NSNotification.Name.init("MenuButtonTapped"), object: nil)
}
//Call from any where in viewControllerOne
func actionToPushOnViewControllerTwo() {
let viewControllerTwo : ViewControllerTwo = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerTwo") as! ViewControllerTwo
self.present(viewControllerTwo, animated: true, completion: nil)
}
#objc func actioFire(_ notification: Notification) {
print(notification.userInfo!["isMenuButtonTapped"] as Any)
if let isMenuButtonTapped = notification.userInfo!["isMenuButtonTapped"] as? Bool {
self.isMenubuttonTapped = isMenuButtonTapped
}
}
}
ViewControllerTwo
class ViewControllerTwo : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let barButtonItem = UIBarButtonItem(image: UIImage(named: "backImgs"),
style: .plain,
target: self,
action: #selector(menuButtonTapped))
self.navigationItem.rightBarButtonItem = barButtonItem
}
#objc fileprivate func menuButtonTapped() {
// here I need to set flag value
self.dismiss(animated: true) {
NotificationCenter.default.post(name: Notification.Name("MenuButtonTapped"),
object: nil,
userInfo:["isMenuButtonTapped": true])
}
}
}
Another Easy Solution is
self.dismiss(animated: true) {
if let tabController = self.presentingViewController as? UITabBarController {
if let navController = tabController.selectedViewController as? UINavigationController {
if let secondTab = navController.viewControllers.first as? HomeViewController {
secondTab.tfData = "YES"
}
} else {
if let secondTab = tabController.selectedViewController as? HomeViewController {
secondTab.tfData = "YES"
}
}
}
}
I'm eliminating the storyboard from my app completely. How do I present the VC that is linked to the second tab of the TabBarController.
Setup: mainVC --- myTabBar -- tab1 - navCntrl - VC1
tab2 - navCntrl - VC2
When using a segues I used the following code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == myTabBar) {
let tabVC = segue.destination as? UITabBarController {
tabVC.selectedIndex = myTabBarIndex ==> 1 to reach VC2
}
// other other stuff
}
To eliminating the segues I rewrote the above but although I set the selectedIndex VC2 is not presented. Any suggestions?
func vc2Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 1 // ==>> Index set but can not reach VC2
present(tabVC, animated: true, completion: nil)
}
The full code of my test system:
class MyTabBar: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create Tab 1
let navCtrlTab1 = UINavigationController(rootViewController: VC1())
let tabOne = navCtrlTab1
let tabOneBarItem = UITabBarItem(title: "", image: StyleKit.imageOfIconTabRecent, selectedImage: StyleKit.imageOfIconTabRecentRev)
tabOne.tabBarItem = tabOneBarItem
// Create Tab 2
let navCtrlTab2 = UINavigationController(rootViewController: VC2())
let tabTwo = navCtrlTab2
let tabTwoBarItem = UITabBarItem(title: "", image: StyleKit.imageOfIconTabNote, selectedImage: StyleKit.imageOfIconTabNoteRev)
tabTwo.tabBarItem = tabTwoBarItem
self.viewControllers = [tabOne, tabTwo]
}
}
class mainVC: UIViewController {
let btn0: UIButton = {
let button = UIButton()
button.setBackgroundImage(StyleKit.imageOfBtnBlue(btnText: "VC1"), for: UIControlState.normal)
button.addTarget(self, action:#selector(vc1Btn), for: .touchUpInside)
return button
}()
let btn1: UIButton = {
let button = UIButton()
button.setBackgroundImage(StyleKit.imageOfBtnBlue(btnText: "VC2"), for: UIControlState.normal)
button.addTarget(self, action:#selector(vc2Btn), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(btn0)
self.view.addSubview(btn1)
addConstraintsWithFormat("H:|-100-[v0]", views: btn0)
addConstraintsWithFormat("H:|-100-[v0]", views: btn1)
addConstraintsWithFormat("V:|-300-[v0]-20-[v1]", views: btn0, btn1)
}
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
func vc1Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 0 // ==>> this is working
present(tabVC, animated: true, completion: nil)
}
func vc2Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 1 // ==>> Index set but can not reach VC2
present(tabVC, animated: true, completion: nil)
}
}
class VC1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "VC1"
print ("VC1")
}
}
class VC2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "VC2"
print ("VC2")
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = mainVC()
return true
}
}
I have a method called selectionDidFinish(controller:) in a delegate to dismiss the viewController the delegate presented. The presented controller, which adopts my Dismissable protocol, has a UIBarButtonItem with an action attached to it that should call the selectionDidFinish(controller:) method but it's giving me the "Argument of '#selector' does not 'refer' to an initializer or method" error.
The error is in this presented UIViewController:
class FormulaInfoViewController: UIViewController, Dismissable {
weak var dismissalDelegate: DismissalDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Xcode doesn't like this selector
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(dismissalDelegate?.selectionDidFinish(self)))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I've confirmed that the dismissalDelegate is getting set properly to FormulasTableViewController so I can't understand why it can't see dismissalDelegate?.selectionDidFinish(self)).
The relevant code of my presenting UIViewController is:
class FormulasTableViewController: UITableViewController, DismissalDelegate {
let formulas: [CalculationFormula] = [
CalculationFormula.epley,
CalculationFormula.baechle,
CalculationFormula.brzychi,
CalculationFormula.lander,
CalculationFormula.lombardi,
CalculationFormula.mayhewEtAl,
CalculationFormula.oConnerEtAl]
override func viewDidLoad() {
super.viewDidLoad()
let liftInfoImage = UIImage(named: "info_icon")
let liftInfoButton = UIBarButtonItem(image: liftInfoImage, style: .Plain, target: self, action: #selector(self.segueToFormulaInfo(_:)))
self.navigationItem.rightBarButtonItem = liftInfoButton
}
func selectionDidFinish(controller: UIViewController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nav = segue.destinationViewController as! UINavigationController
let vc = nav.topViewController as! Dismissable
vc.dismissalDelegate = self
}
func segueToFormulaInfo(sender: UIButton) {
performSegueWithIdentifier("segueToFormulaInfo", sender: self)
}
}
I've done all kinds of research on how to use #selector and I thought this post had all the answers, but it doesn't.
I've tried this Dismissable protocol with and without exposing it to #objc:
#objc protocol Dismissable: class {
weak var dismissalDelegate: DismissalDelegate? {
get set }
}
And I've also tried it with my DismissalDelegate protocol:
#objc protocol DismissalDelegate : class {
func selectionDidFinish(controller: UIViewController)
}
extension DismissalDelegate where Self: UIViewController {
func selectionDidFinish(viewController: UIViewController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
I can't expose my protocol extension to #objc - is that why this doesn't work? Is my #selector really the problem here? Does it have something to do with my protocols?
EDIT: Final fix
Based on the accepted answer, I added a function to do the dismissal:
func dismiss() {
dismissalDelegate?.selectionDidFinish(self)
}
and then called the selector like this:
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(dismiss))
You can't have the action parameter specify an arbitrary expression. It needs to invoke a specific selector - a class and function in that class.
You will need to create a function in your class that invokes the delegate method:
class FormulaInfoViewController: UIViewController, Dismissable {
weak var dismissalDelegate: DismissalDelegate?
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(FormulaInfoViewController.selectionDidFinish)
}
#objc func selectionDidFinish() {
self.dismissalDelegate?.selectionDidFinish(self)
}
}
Create a UIBarButtonItem which name is nextVc on my navigationBar, and set its action by nextVc.action = #selector(self.gotoVC4), but it does not work.
my code is below:
class ViewController3: UIViewController {
#IBOutlet weak var nextVc: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
nextVc.action = #selector(self.gotoVC4)
}
func gotoVC4() -> Void {
print("go to vc4")
let vc4 = ViewController4()
self.navigationController!.pushViewController(vc4, animated: true)
}
}
and the image of storyboard is here:
Since you have a storyboard, simply ctrl-drag from the bar button to the "View Controller 4" scene to create a segue. No code needed.
You can omit self in selector expression
Method should be dynamic or #objc
override func viewDidLoad() {
super.viewDidLoad()
nextVc.action = #selector(gotoVC4)
}
dynamic func gotoVC4() -> Void {
print("go to vc4")
let vc4 = ViewController4()
self.navigationController!.pushViewController(vc4, animated: true)
}
So I have a button in [2] and it pushes to [3] through the Navigation Controller so I can go back to [2] with the "Back" button in the toolbar. This all works fine.
In [4] I have a button too and I want it to go to [3]. But it should also go through the navigation controller so that when I press "Back" I can return to [2] again.
So actually I want the button on [4] to go like [ 1 ][ 2 ][ 3 ] so that I can return to [2] from [3]
#IBAction func showKaart(sender: AnyObject) {
performSegueWithIdentifier("menuToKaart", sender: sender)
}
If I understand your question, I believe try it.
Mind your root view controller is vc[2], ok?
You push from vc[2] > vc[3], next press back and return to vc[2], that's ok!
You push from vc[4] > vc[3], next press back and return to vc[4], but you need back to vc[2]?
For this logic you can create your custom behavior to vc[3]
For your vc[3] I do this to control behavior.
class ViewController3:UIViewController{
var backToRoot:Bool = false;
override func viewDidLoad() {
super.viewDidLoad();
self.hideAndAddNewBackButton();
}
private func hideAndAddNewBackButton(){
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "back:")
self.navigationItem.leftBarButtonItem = newBackButton;
}
func back(sender: UIBarButtonItem) {
if backToRoot{
self.navigationController?.popToRootViewControllerAnimated(true);
}else{
self.navigationController?.popViewControllerAnimated(true)
}
}
func needBackToRoot(){
backToRoot = true;
}
}
Now in vc[4] I think you can modify the back button behavior of your vc[3]
I mind two way to do with your vc[4]
First using performSegue.
class ViewController4PerfomSegue:UIViewController{
#IBAction func pressButtonToPushViewController3(sender:AnyObject?){
self.performSegueWithIdentifier("showViewController3", sender: nil);
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let viewController3 = segue.destinationViewController as? ViewController3{
viewController3.needBackToRoot();
}
}
}
Second with push manual in your navigation view controller
class ViewController4Push:UIViewController{
#IBAction func pressButtonToPushViewController3(sender:AnyObject?){
let viewController3 = ViewController3(nibName: "ViewController3", bundle: nil);
viewController3.needBackToRoot();
self.navigationController?.pushViewController(viewController3, animated: true);
}
}
Edit: new way to instantiate from storyboard
class ViewController4Push:UIViewController{
#IBAction func pressButtonToPushViewController3(sender:AnyObject?){
if let viewController3 = storyboard!.instantiateViewControllerWithIdentifier("ViewController3") as? ViewController3{
viewController3.needBackToRoot();
self.navigationController?.pushViewController(viewController3, animated: true);
}
}
}
Edit: removing when new view was presented
class ViewController4Push:UIViewController, UINavigationControllerDelegate{
#IBAction func pressButtonToPushViewController3(sender:AnyObject?){
if let viewController3 = storyboard!.instantiateViewControllerWithIdentifier("ViewController3") as? ViewController3{
viewController3.needBackToRoot();
self.navigationController?.delegate = self;
self.navigationController?.pushViewController(viewController3, animated: true);
}
}
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
self.navigationController?.delegate = nil;
self.dismissViewControllerAnimated(false, completion: nil);
//or self.removeFromParentViewController();
}
}
I hope help you, if I understand the problem
Edit: after talk in chat with Sinan we resolve it with simple way, just force back present the root element of application
class ViewCustomController:UIViewController{
override func viewDidLoad() {
super.viewDidLoad();
self.hideAndAddNewBackButton();
}
private func hideAndAddNewBackButton(){
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "back:")
self.navigationItem.leftBarButtonItem = newBackButton;
}
func back(sender: UIBarButtonItem) {
if let viewControllerRoot = storyboard!.instantiateViewControllerWithIdentifier("ViewControllerRoot") as? ViewControllerRoot{
self.navigationController?.pushViewController(viewController2, animated: true);
}
}
}
[4] needs to be embedded in a navigation controller. After that, change you #IBAction to use pushViewController:Animated: on the navigationController instead of performSegueWithIdentifier