Swift 3 - Add BarButtonItem To NavigationBar On Each ViewController Without Repeating Code - ios

I have a BarButtonItem I want to add to every single ViewController in my app. How can I do this without having to copy/paste the button creation code and the action function into each ViewController? I would prefer reusability instead of having the same code in every ViewController.
Button Code:
// Settings button
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "icon-settings"), for: .normal)
btn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
btn.addTarget(self, action: #selector(showSettings(sender:)), for: .touchUpInside)
let settingsBtn = UIBarButtonItem(customView: btn)
self.navigationItem.setRightBarButton(settingsBtn, animated: false)
The Action:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "SettingsVC") as! SettingsViewController
self.navigationController?.show(vc, sender: self)
I've tried adding this code to a separate Utility class, but you can't perform the segue from it since there is no way to access the button's parent ViewController from the action function if it is declared in the Utility class. I also tried subclassing UINavigationController and assigning that subclass to the NavigationController in my Storyboard, but it didn't work.

You can do this by adding a BaseViewController and inserting the code there. After that, on your rootviewController of your navigationController, you just extend from this BaseViewController. Doing that, and calling super in that function it will always having that code available and you don't need to repeat in every viewcontroller.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Settings button
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "icon-settings"), for: .normal)
btn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
btn.addTarget(self, action: #selector(showSettings(sender:)), for: .touchUpInside)
let settingsBtn = UIBarButtonItem(customView: btn)
self.navigationItem.setRightBarButton(settingsBtn, animated: false)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
And then in your view controller you just extend BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Button should already appear here
}
Note that ViewController should be your root view controller.

Related

How to push a ViewController from a UIButton that is embedded in Tab Bar Controller

In want to have a button floating next to my Tab Bar. When pressed, this button will open a View that can be Navigated (so a View Controller embedded in a Navigation Controller(?)).
In UITabBarController {
ViewDidLoad() {
super.viewDidLoad()
//I have my 5 tab bar items set up programatically here.
//The middle tab bar item is disabled because the button is on top of it
setupMiddleButton()
}
My setupMiddleButton function
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 52, height: 52))
//..various styling and alignment values...
view.addSubview(menuButton)
menuButton.addTarget(self, action: #selector(menuButtonAction(sender:)), for: .touchUpInside)
finally I've tried to add the code to push the view controller, however I receive nil when tapping the button.
#objc private func menuButtonAction(sender: UIButton) {
let createController = CreateViewController()
let nav3 = UINavigationController(rootViewController: createController)
nav3.navigationController!.pushViewController(createController, animated: true)
I think that you may be passing the wrong parameter to the menuButtonAction function, try to change your code like this:
func setupMiddleButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 52, height: 52))
//..various styling and alignment values...
view.addSubview(menuButton)
menuButton.addTarget(self, action: #selector(menuButtonAction(_:)), for: .touchUpInside)
}
...
#objc private func menuButtonAction(_ sender: UIButton) {
...
}
So I figured it out. Turns out it was much simpler than I thought, which makes me think I was explaining myself poorly in the question. Anyway, here's the code that worked for me.
let myViewController = ViewController()
let nav = UINavigationController(rootViewController: myViewController)
self.present(nav, animated:true, completion: nil)

Custom Tab Bar To Navigate Between View Controller

I have a tab bar which i have set up using storyboard. In the tab bar, i have a custom button in the middle which looks like:
It was set up using:
class TabBarViewController: UITabBarController {
let button = UIButton.init(type: .custom)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
button.setImage(UIImage(named: "assetIcon"), for: .normal)
button.backgroundColor = .blue
button.layer.cornerRadius = 35
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.insertSubview(button, aboveSubview: self.tabBar)
self.view.layoutIfNeeded()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let distance = ((self.view.bounds.height)/100)*11
// safe place to set the frame of button manually
button.frame = CGRect.init(x: self.tabBar.center.x - 32, y: self.view.bounds.height - distance, width: 70, height: 70)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc func buttonAction(){
// no actions here yet as I dont know what to put
}
}
As seen above, i created an action for the button to navigate to a specific view like how other tab bar button should act. However, I am not sure how to navigate or rather should i say, I dont know what i should be putting in the custom tab bar button
I'm guessing you want to display a view controller using the button.
Instantiate and display the View Controller using the following:
let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewControllerIdentifier")
self.present(controller, animated: true, completion: nil)
When the button is tapped, I believe you need to be using the selectedViewController property to specify which view to display. Here's the documentation on the Tab Bar Controller: https://developer.apple.com/documentation/uikit/uitabbarcontroller

Add UIBarButtomItem as Child of UITabBarController

I have a UIViewController with a UITableView embedded in a UINavigationController, that starts a UITabBarController. The navigation bar is displayed including the back button and I can change the title of the navigation item programmatically from the UITabBarController.
But I can't add a UIBarButtonItem to the child controllers of the UITabBarController. Neither in the storyboard, nor programmatically.
I also tried to embed the child controller in a UINavigationController, but that just added a second navigation bar. How do I add UIBarButtonItems from a child of a UITabBarController?
In your storyboard, your view controller is inside navigation controller. And your child view controller is INSIDE tab bar controller of your view controller. You're not able to add bar button because you're not able to access navigationController property (which is nil in your child controller case.)
Debug this by accessing navigationController and tabBarController. In your case it should be tabBarController?.navigationController most probably.
EDIT
This is how I maintain navigation bar in code:
final class MyVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar(withTitle: "MyVC")
}
}
extension UIViewController {
func setupNavigationBar(withTitle title: String? = nil) {
let backButton = UIBarButtonItem(image: #imageLiteral(resourceName: back), style: .plain, target: self, action: #selector(popVC(animated:)))
backButton.tintColor = .white
navigationItem.leftBarButtonItem = backButton
navigationItem.title = title
}
#objc
func popVC(animated: Bool = true) {
navigationController?.popViewController(animated: true)
}
}
Try the following code for set the left and right BarButton programatically.You can add the given code in viewDidload your child TabBarController.
let cancelBtn = UIButton(type: .custom)
cancelBtn.frame = CGRect(x: 0, y: 0, width: 40, height: 15)
cancelBtn.setTitle("Cancel", for: .normal)
cancelBtn.setTitleColor(UIColor.init(red: 240/255, green: 36/255, blue: 70/255, alpha: 1.0), for: .normal)
cancelBtn.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: cancelBtn)
let resetBtn = UIButton(type: .custom)
resetBtn.setTitle("Reset", for: .normal)
resetBtn.setTitleColor(UIColor.init(red: 240/255, green: 36/255, blue: 70/255, alpha: 1.0), for: .normal)
resetBtn.addTarget(self, action: #selector(resetTapped), for: .touchUpInside)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: resetBtn)
and also add the following codes to make it sure the property of navigation bar is true..
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
// Hide the navigation bar on the this view controller
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool)
{
super.viewWillDisappear(animated)
// Show the navigation bar on other view controllers
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
I have one suggestion for you. Please just remove the Show Segue Between the Person List ViewController to the Person Detail ViewController. and then you embed In Navigation to Person Detail ViewController.
And on the didselect tableview method set the 'Person Detail ViewController` as RootViewController Then You can add the NavigationBar Item in the childViewController also.

Back UIBarButtonItem appearance remove text and change image

I've watched a lot of questions like this and didn't find an answer for my question.
That how i do now:
APPDELEGATE (didFinishLaunchingWithOptions)
// Text
let barButtonItem = UIBarButtonItem.appearance()
barButtonItem.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: UIControlState.normal)
barButtonItem.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: UIControlState.highlighted)
// Image
let backImage = UIImage(named: "arrow_left"
UINavigationBar.appearance().backIndicatorImage = backImage
UINavigationBar.appearance().backIndicatorTransitionMaskImage = backImage
And this almost fit to what i need, but the screen title shifted to the right as there is an invisible back button text. And it definetly is (the root controller's title has 9 characters length):
The question is: How to change image, hide text and keep standart back action for every appearance of back button in ios 9.0 ?
There are three ways to do what you want.
I recommend: Create your own Navigation Class by extending and UINavigationController and override backbuttonItem (UIBarButtonItem) property to customise it according to your requirement. And use the same Navigation Controller class in your project.
Create a custom backBarButton by extending UIBarButtonItem and manually set the same as a back button of default Navigation Controller class, in all view controller.
Hide default navigation bar from root controller and create your own navigation bar using UIView and UIButton in all view controllers. (I always use this choice, that makes customization of navigation bar very easy for me. And I can set my view according to my requirement)
Here is how you can add Custom button for your navigation bar
let btnleft : UIButton = UIButton(frame: CGRect(x:0, y:0, width:35, height:35))
btnleft.contentMode = .center
btnleft.setImage(Set_Local_Image("arrow_left"), for: .normal)
btnleft.addTarget(self, action: #selector(YOUR_ACTION), for: .touchDown)
let backBarButon: UIBarButtonItem = UIBarButtonItem(customView: btnleft)
self.navigationItem.setLeftBarButtonItems([menuBarButon], animated:false)
instead of "arrow_left" You can use any image you want
For Default back action you can create function(YOUR_ACTION) and use in selector of back button
navController.popViewController(animated: true)
I can suggest you 2 options. Both require BaseViewController class as a superclass of all your view controllers.
If you are ok with native back button image, just want to remove back button text you can use this subclass:
class BaseViewController: UIViewController {
var navigationTitle: String = ""
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !navigationTitle.isEmpty {
navigationItem.title = navigationTitle
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationTitle = navigationItem.title ?? ""
navigationItem.title = ""
}
}
If you want to use your custom icon for back button, you should create UIBarButtonItem with your image, add target, selector, handle action of the button. Sample BaseViewController class below:
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let backImage = UIImage(named: "arrow_left")
navigationItem.hidesBackButton = true
guard let navigationController = navigationController else {
return
}
if navigationController.viewControllers.count > 1 {
// we have to set back button only when we have at least 1 controller to go back
navigationItem.leftBarButtonItem = UIBarButtonItem(image: backImage, style: .plain, target: self, action: #selector(backBarButtonAction(sender:)))
}
}
// MARK: Actions
func backBarButtonAction(sender: UIBarButtonItem) {
navigationController?.popViewController(animated: true)
}
}
According to https://stackoverflow.com/a/16831482/5790492 there is a way to do this without appearance.
Swift 3.0
extension UIViewController {
func setupCustomBackButton() {
if let controllersCount = navigationController?.viewControllers.count, controllersCount > 1 {
let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: 12, height: 20))
backButton.setBackgroundImage(UIImage(named: "arrow_left"), for: .normal)
backButton.contentMode = .left
let backButtonItem = UIBarButtonItem(customView: backButton)
backButton.addTarget(self, action: #selector(self.popCurrentViewController), for: .touchUpInside)
navigationItem.leftBarButtonItem = backButtonItem
navigationItem.hidesBackButton = true
}
}
func popCurrentViewController() {
navigationController?.popViewController(animated: true)
}
}
UINavigationBar.appearance().setBackgroundImage(UIImage(), for:
UIBarPosition.any, barMetrics: UIBarMetrics.default)
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().tintColor = UIColor.white
UINavigationBar.appearance().barTintColor = UIColor.main
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().clipsToBounds = false
UINavigationBar.appearance().backgroundColor = UIColor.main
UINavigationBar.appearance().titleTextAttributes = [NSFontAttributeName :
(UIFont(name: "Helvetica", size: 18))!, NSForegroundColorAttributeName:
UIColor.white]
Try this code and make changes accordingly to set image, color and other properties
You should watch Mark Moeykens' youtube series on this. He is IMHO one of the best YouTube presenters for UI Design and implementation in Swift.
The play list is https://www.youtube.com/playlist?list=PLHDMmeIMXj8WyvlX5uFmppVn2Pm0bXVr7
Create an extension for UINavigationItem
extension UINavigationItem {
func backBarButtonItem() -> UINavigationItem {
return UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
}
}

Creating a segue programmatically without a storyboard

Trying to create a segue for my register button which is going to send a user to a page that will allow them to register in my database.
Usually, I make a button and a target for my button which performs some action through a function.
Can we make this function segue PROGRAMMATICALLY mind you I haven't used storyboards at all?
Code Snippet:
lazy var registerButton: UIButton = {
let rButton = UIButton(type: .system)
rButton.frame = CGRect(x: 210, y: 600, width: 100, height: 50)
rButton.setTitle("REGISTER", for: .normal)
rButton.backgroundColor = UIColor.white
rButton.translatesAutoresizingMaskIntoConstraints = false
rButton.setTitleColor(UIColor.black, for: .normal)
rButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
rButton.layer.cornerRadius = 20
rButton.layer.borderWidth = 2
rButton.addTarget(self, action: #selector(regSegue), for: .touchUpInside)
return rButton
}()
// will segue for me
func regSegue(){
}
I assume you meant a ViewController by the word page. So initialize your ViewController inside your func regSegue(){...} and present it using present(_:animated:completion:) method.
Example:
func regSegue(){
let page = PageViewController()
present(page, animated: true, completion: nil)
}
You can achieve this easily without a storyboard.First, you need to initialize the viewcontroller which you need to show.Then present the view controller.
let vc: MyViewController = MyViewController()
self.presentViewController(vc, animated: true, completion: nil)

Resources