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
Related
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)
This is the situation:
The root view controller of a UINavigationController has a search bar. When a user searches something, they can tap on a cell that pushes a new view controller onto the navigation controller with some info. When this happens, the intended behavior is for the user to be able to go back and still see the keyboard open with the most recent search query populated within the search bar.
However, there is also a situation in which the user may tap on a tab bar that should pop all the view controllers off the navigation controller except the root controller. In that case, I would like to resign the search bar textField's first responder before the root controller is presented. If I try to do this in viewWillAppear(_:), resignFirstResponder() fails.
Is this possible? I've tried using delegation and notifications but nothing seems to work.
As #Sweeper said, endEditing worked for me, but resignFirstResponder didn't. I executed in viewWillAppear:
import PlaygroundSupport
import UIKit
class ViewController: UIViewController {
let button = UIButton()
let searchBar = UISearchBar()
override func viewDidLoad() {
super.viewDidLoad()
button.setTitle("Button", for: .normal)
button.tag = 1
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.backgroundColor = .black
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
self.navigationItem.titleView = searchBar
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
button.widthAnchor.constraint(equalToConstant: 200),
button.heightAnchor.constraint(equalToConstant: 100),
])
}
#objc func buttonPressed(sender: UIButton) {
if case let tag = sender.tag, tag == 1 {
let secondVC = SecondVC()
self.navigationController?.pushViewController(secondVC, animated: true)
}
}
}
class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.view.endEditing(true)
}
}
let navVC = UINavigationController(rootViewController: ViewController())
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = navVC
I've a three screens for navigate. But in one these, I can't put a UIBarButtoItem. This screen, is for create a register, and I wants create a button to "Save", that simple. When I did, I select the Bar Button Item, the XCode do not leave me drop on the bar. And programatically, also not works.
I tried this: (Not happens)
var btSalvar : UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
btSalvar = UIBarButtonItem(title: "Salver", style: .plain, target: self, action: nil)
self.navigationItem.rightBarButtonItem = btSalvar
// Do any additional setup after loading the view.
}
And in storyboard: (Note: The Button "Item" not keep fixed on the bar)
You have many options to do that, One of them is:
You have to create a super view controller and add navigation button code in it. I have added a back button for a demo:
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
/**
To add the left back button on navigation.
*/
var addLeftBarMenuButtonEnabled: Bool? {
didSet {
if addLeftBarMenuButtonEnabled! {
let leftBarBtn = UIButton()
leftBarBtn.setImage(UIImage(named: "backIcon"), for: .normal)
leftBarBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
leftBarBtn.addTarget(self, action: #selector(actionBackButton), for: .touchUpInside)
self.navigationItem.leftBarButtonItem = UIBarButtonItem.init(customView: leftBarBtn)
} else {
self.navigationItem.setHidesBackButton(true, animated: true)
}
}
}
///This is action method for back button
#objc func actionBackButton() {
self.view.endEditing(true)
self.navigationController?.popViewController(animated: true)
}
}
Now you need to use the back button in your view controller which super view controller is MainViewController:
class ViewController: MainViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.addLeftBarMenuButtonEnabled = true
}
}
You can add navigation button like that and use where you want. If you want to use it in every view controller then you have to add 'self.addLeftBarMenuButtonEnabled = true' in main view controller like that
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.addLeftBarMenuButtonEnabled = true
}
}
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)
}
}
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.