I am using XLPagerTabStrip library and using the ButtonBarPagerTabStripViewController. This is what the hierarchy looks like:
The ButtonBarPagerTabStripViewController is embedded inside a navigationController and I have two different viewControllers that the ButtonBarPagerTabStripViewController contains. The problem is that I want to change how the navigationBar looks based one either ViewController1 or ViewController2.
For example, when the tab is one viewController1, I want to have a barButton "+". For viewController2, I want to have a barButton of report. However, I cannot make modifications to the navigationController bar from viewController1 and viewController2.
Is there a way?
ButtonBarPagerTabStripViewController class has a override function to handle this.
override func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
if progressPercentage == 1 {
// Add `+` button to navigation item
} else {
// Add `report` button to navigation item
}
}
Add condition as per your total number of view controllers on the screen.
Update
Please see the below code used in the demo project.
import UIKit
import XLPagerTabStrip
class MainTabBarViewController: ButtonBarPagerTabStripViewController {
var isFirstView: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
}
func getSegmentList() -> [UIViewController] {
let firstViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "FirstViewController")
firstViewController.title = "First"
let secondViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "SecondViewController")
secondViewController.title = "Second"
return [firstViewController, secondViewController]
}
func setUI() {
let addImage = UIImage(systemName: "plus")
let addBarButton = UIBarButtonItem(image: addImage, style: .plain, target: self, action: #selector(addButtonAction(_:)))
let reportImage = UIImage(systemName: "book")
let reportBarButton = UIBarButtonItem(image: reportImage, style: .plain, target: self, action: #selector(reportButtonAction(_:)))
navigationItem.rightBarButtonItem = isFirstView ? addBarButton : reportBarButton
}
#IBAction func addButtonAction(_ sender: UIBarButtonItem) {
print("Add button action")
// Add button action
}
#IBAction func reportButtonAction(_ sender: UIBarButtonItem) {
print("Report button action")
// Report button action
}
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
return getSegmentList()
}
override func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
if progressPercentage == 1 {
let viewController = viewController.viewControllers[toIndex] as? FirstViewController
isFirstView = viewController?.title == "First"
setUI()
}
}
}
Output:
Related
I have my main screen with only one button on it "Show next screen". When the second screen(VC) pops up it has 2 buttons (go back and toSelect button).
My goal is to when I show my second screen and select a button on it then go back to first. The button on my second screen will stay selected. How can I do that?
So basically I need to save my actions on the second screen so if I go back to it it will show everything I did.
What is the best way to do it?
Storyboard
The easiest way to achieve this using Delegate and protocol.
you should listen and save the changes of SecondViewController at FirstViewController using delegate methods.
And when you are presenting the secondViewController you will share the saved changes to secondViewController so that button can be selected on behalf of that information
Code -
class FirstViewController: UIViewController {
//secondViewController States
private var isButtonSelected = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func gotoSecondVCAction(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
guard let secondVC = storyBoard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondVC.isButtonSelected = isButtonSelected
secondVC.delegate = self
self.present(secondVC, animated: true, completion: nil)
}
}
extension FirstViewController: ButtonSelectionProtocol {
func button(isSelected: Bool) {
isButtonSelected = isSelected
}
}
and for secondViewController
protocol ButtonSelectionProtocol {
func button(isSelected:Bool)
}
class SecondViewController: UIViewController {
var isButtonSelected : Bool = false
var delegate:ButtonSelectionProtocol?
#IBOutlet weak var selectButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if isButtonSelected {
selectButton.tintColor = .red
selectButton.setTitle("Selected", for: .normal)
}else{
selectButton.tintColor = .green
selectButton.setTitle("Select Me", for: .normal)
}
}
#IBAction func gobackAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
#IBAction func selectAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
isButtonSelected.toggle()
delegate?.button(isSelected: isButtonSelected)
}
}
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 custom "Replace" segue that replaces the current viewcontroller with another. This works good. However the back button title always becomes "Back". The segue works like this:
ViewController A -> ViewController B
ViewController B then performs a replace segue to ViewController C. The stack is then:
ViewController A -> ViewController C
This is the code for ReplaceSegue.swift:
public class ReplaceSegue: UIStoryboardSegue
{
public var animated = true
public override func perform()
{
let navigationController: UINavigationController = sourceViewController.navigationController!
var controllerStack = navigationController.viewControllers
let index = controllerStack.indexOf(sourceViewController)
controllerStack[index!] = destinationViewController
navigationController.setViewControllers(controllerStack, animated: animated)
}
}
And for my ViewController:
func replaceAgendaViewController()
{
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let agendaViewController = mainStoryboard.instantiateViewControllerWithIdentifier("AgendaViewController") as! AgendaViewController
let segue = ReplaceSegue(identifier: replaceSegueId, source: self, destination: agendaViewController) { () -> Void in
}
segue.animated = false
segue.perform()
}
Have tried this code but I understand why it does not work as it sets the navigationItem on the ViewController that is replaced:
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
I also do not want to set the back button title previous to performing the segue as the ViewController contains other segues that may not want that back button title. Any suggestions?
It was quite easy to fix, I just added the code to the prepareForSegue:sender: in ViewController A:
public override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
switch (segue.identifier!) {
case agendaSegueId:
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
...
}
}
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