I have a NavigationBar at the top of a TableView. It looks nice opening/closing the search.
However, if I click on a button in a cell and get directed to another page (with segue); and then use Back button to unwind, it seems like bugged.
()
So it looks like it is pressed and opened but it shouldn't have. It should be looked like the top picture instead (just UIBarButtonItem - search button)
I couldn't figure out the issue creating this problem.
Please note that < Back is created automatically and I didn't write any code to create it. Is there something I am doing wrong?
Update: Added some snippets...
First, created a different class for handling the search
class SearchBarViewController: UIViewController, UISearchBarDelegate {
var searchBar : UISearchBar?
var searchBarWrapper : UIView?
var searchBarButtonItem : UIBarButtonItem?
func constructSearchBar()
{
searchBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "showSearchBar")
self.navigationItem.rightBarButtonItem = searchBarButtonItem
}
func showSearchBar() {
// styling & configuration
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
UIView.animateWithDuration(0.2, animations: {
self.searchBar?.resignFirstResponder()
self.searchBarWrapper?.alpha = 0
}, completion: { (success) -> Void in
self.searchBar = nil
self.searchBarWrapper = nil
self.navigationItem.rightBarButtonItem = self.searchBarButtonItem
})
}
}
And my ViewController:
class ViewController: SearchBarViewController {
override func viewDidLoad() {
super.viewDidLoad()
constructSearchBar()
}
}
Regarding to emrys57's answer, I tried adding viewWillAppear() in my ViewController but I couldn't make it work, as my cancel looks a little different:
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
// Here, I couldn't figure out what to put because
// my searchBarCancelButtonClicked() needs searchBar and
// forces me to use (!) but then it says, it's optional..
}
The answer is...
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
navigationItem.titleView = nil
constructSearchBar()
}
You have not posted code, so it's not entirely clear what's gone wrong. Using UISearchBar, I think you must be handling the buttons separately yourself, as opposed to using UISearchController. I think that you may not be clearing away the search bar when coming back from the second VC. This code clears out the search bar in viewWillAppear:
class ViewController: UIViewController {
var cancelButton: UIBarButtonItem?
var searchButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
cancelButton = UIBarButtonItem(barButtonSystemItem: .Cancel, target: self, action: Selector("searchCancelPressed:"))
searchButton = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: Selector("searchPressed:"))
}
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
searchCancelPressed(nil)
}
func searchPressed(sender: AnyObject) {
navigationItem.titleView = UISearchBar()
navigationItem.rightBarButtonItem = cancelButton
}
func searchCancelPressed(sender: AnyObject?) {
navigationItem.titleView = nil
navigationItem.rightBarButtonItem = searchButton
}
}
and that is working nicely for me when I do a push from a button to the second VC and then hit back.
Following the edit to the original question, this code seems to work, although it may not be the most elegant way of constructing the answer:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationItem.titleView = nil
searchBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "showSearchBar")
navigationItem.rightBarButtonItem = searchBarButtonItem
}
The function constructSearchBar no longer needs to be called in viewDidLoad, and can be deleted.
Related
I'm wondering what's the best way to achieve the following. My application when launches goes to the following tableview:
When you select a category, a segue to another tableview is made and looks like this:
What I want to do, is eventually have the 'Basket' barButtonItem in the first view to update with the total price of the basket amount. I also want the button to be visible from the entire navigation controller cycle.
Is there a way that I can have the Basket button show on every stage of the navigationcontroller process?
So for example I'd like to have the button show on the second table view.
Yes you can achieve it by use of take class of UINavigationController like below
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// MARK: Private Functions
private func addRightBarButtonTo(viewController: UIViewController){
barButtonItem = UIBarButtonItem(title: "Basket", style: .plain, target: self, action: #selector(CustomNavigationController.dismiss(_:)))
viewController.navigationItem.rightBarButtonItem = barButtonItem
}
// MARK: UINavigationController Delegate
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
self.addRightBarButtonTo(viewController)
}
#objc func dismiss(sender: Any){
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
Use CustomNavigationController as rootView Controller of the Window.
Second Way
Take extension of UIViewController
extension UIViewController {
func addRightButtonItem() {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Basket", style: .done, target: self, action: #selector(barButtonMethod(_:)))
}
#objc func barButtonMethod(_ sender: UIBarButtonItem) {
// Your code
}
}
and call below method in viewWillAppear of each and every viewController
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.addRightButtonItem()
}
I want to change the appearance of all Back buttons in the app by setting the text to "Back" and removing the arrow (even just removing the arrow would be fine). I'm trying to find a way to do it globally for all view controllers in the app while also keeping the functionality (I don't want to create a new instance of a UIBarButtonItem and having to set the selector).
I created a custom UINavigationController and tried to set the back button title there with navigationItem.backBarButtonItem?.title = "Back", but it didn't work. Any suggestions how to do it properly?
Just create CustomNavigationController then at
override open func pushViewController(_ viewController: UIViewController, animated: Bool) do controlClearBackTitle method
then use CustomNavigationController to fix it all in your app
import UIKit
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override open func pushViewController(_ viewController: UIViewController, animated: Bool) {
controlClearBackTitle()
super.pushViewController(viewController, animated: animated)
}
override open func show(_ vc: UIViewController, sender: Any?) {
controlClearBackTitle()
super.show(vc, sender: sender)
}
func controlClearBackTitle() {
self.navigationBar.backIndicatorImage = UIImage()
self.navigationBar.backIndicatorTransitionMaskImage = UIImage()
topViewController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "AnyTitle", style: .plain, target: nil, action: nil)
}
}
I think something like this should do it. Try putting it in your custom NavigationController.
let backImage = UIImage(named: "backImage")
self.navigationController?.navigationBar.backIndicatorImage = backImage
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
EDIT
Sorry totally forgot about title. This should do it:
self.navigationController?.navigationBar.backItem?.title = "New title"
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'm using 2 different bar tint colors at UINavigationBar in different views. I'n changing color with that method in both views:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.barTintColor = COLOR
}
When I tap on back button color is not changed smoothly (you can see blink on last second).
But everything is okay if just swipe view back instead of tapping on back button.
How to make smooth transition in both situations?
To achieve this kind of animation you should use UIViewControllerTransitionCoordinator as Apple documentation say it is :
An object that adopts the UIViewControllerTransitionCoordinator protocol provides support for animations associated with a view controller transition.(...)
So every UIViewController has own transitionController. To get this you should call in the UIViewControllerClass :
self.transitionCoordinator()
From documentation:
Returns the active transition coordinator object.
So to get the result that you want you should implement animateAlongsideTransition method in viewController transitionCoordinatior. Animation works when you click backButton and swipe to back.
Example :
First Controller :
class ViewControllerA: UIViewController {
override func loadView() {
super.loadView()
title = "A"
view.backgroundColor = .white
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "NEXT", style: .plain, target: self, action: #selector(self.showController))
setColors()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
animate()
}
func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
private func animate() {
guard let coordinator = self.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: {
[weak self] context in
self?.setColors()
}, completion: nil)
}
private func setColors() {
navigationController?.navigationBar.tintColor = .black
navigationController?.navigationBar.barTintColor = .red
}
}
Second Controller:
class ViewControllerB : UIViewController {
override func loadView() {
super.loadView()
title = "B"
view.backgroundColor = .white
setColors()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
animate()
}
override func willMove(toParentViewController parent: UIViewController?) { // tricky part in iOS 10
navigationController?.navigationBar.barTintColor = .red //previous color
super.willMove(toParentViewController: parent)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.navigationBar.barTintColor = .blue
}
private func animate() {
guard let coordinator = self.transitionCoordinator else {
return
}
coordinator.animate(alongsideTransition: {
[weak self] context in
self?.setColors()
}, completion: nil)
}
private func setColors(){
navigationController?.navigationBar.tintColor = .black
navigationController?.navigationBar.barTintColor = .blue
}
}
UPDATE iOS 10
In the iOS 10 the tricky part is to add the willMoveTo(parentViewController parent: UIViewController?) in the second ViewController. And set the navigationBar tintColor to the color value of previous controller. Also, in viewDidAppear method in second ViewControler set the navigationBar.tintColor to the color from second viewController.
Check out my example project on github
I've coded final solution that looks most comfortable to use (don't need to use a lot of overrides in own view controllers). It works perfectly at iOS 10 and easy adoptable for own purposes.
GitHub
You can check GitHub Gist for full class code and more detailed guide, I won't post full code here because Stackoverflow is not intended for storing a lot of code.
Usage
Download Swift file for GitHub. To make it work just use ColorableNavigationController instead of UINavigationController and adopt needed child view controllers to NavigationBarColorable protocol.
Example:
class ViewControllerA: UIViewController, NavigationBarColorable {
public var navigationBarTintColor: UIColor? { return UIColor.blue }
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Push", style: .plain, target: self, action: #selector(self.showController))
}
func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
}
class ViewControllerB: UIViewController, NavigationBarColorable {
public var navigationBarTintColor: UIColor? { return UIColor.red }
}
let navigationController = ColorableNavigationController(rootViewController: ViewControllerA())
This worked for me:
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
navigationController?.navigationBar.barTintColor = previous view controller's navigation bar color
}
I am just wondering. For the same purpose I use UINavigationControllerDelegate. In navigationController(_:willShow:) I start the animation using transitionCoordinator?.animate(alongsideTransition:completion:). It works great when pushing new controllers, however pop doesn't.
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let dst = viewController as! ViewController
guard animated else {
navigationController.navigationBar.barTintColor = dst.navigationBarColor
navigationController.navigationBar.tintColor = dst.tintColor
navigationController.navigationBar.barStyle = dst.barStyle
return
}
navigationController.transitionCoordinator?.animate(alongsideTransition: { context in
navigationController.navigationBar.barTintColor = dst.navigationBarColor
navigationController.navigationBar.tintColor = dst.tintColor
navigationController.navigationBar.barStyle = dst.barStyle
}, completion: { context in
if context.isCancelled {
let source = context.viewController(forKey: UITransitionContextViewControllerKey.from) as! ViewController
navigationController.navigationBar.barTintColor = source.navigationBarColor
navigationController.navigationBar.tintColor = source.tintColor
navigationController.navigationBar.barStyle = source.barStyle
}
})
Do you see any reason why it should work with pushes but not pops?
I seem to be unable to correctly set up the back button of the navigationController programmatically that shows when a previous view uses
self.navigationController?.pushViewController(newView, animated: true)
I hide all of the views from the previous view in it's viewDidDisappear using a loop and in the new view presented in the viewDidAppear I attempt to set the action of the back button in various ways; however, while I can succeed in manipulating the back button that is automatically shown such as hiding it or changing it's image I am unable to set up it's action.
Any insight would be appreciated as none of the answers I have found seem to work correctly. Also this is done without any use of the storyboard
if let img = UIImage(named: "backButton") {
self.navigationController?.navigationBar.backIndicatorImage = img
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = img
print("IMAGE")
}
topItem.backBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Rewind, target: self,
action:#selector(self.backButtonAction(_:)))
In your case add a custom button on the Navigation.
class YourViewController: UIViewController {
//Navigation Items.
//left bar button item.
private var leftBarButtonItem : UIBarButtonItem!
//left button.
private var navigationLeftButton : UIButton!
//Your other variable/object declaration.
func viewDidLoad() {
super.viewDidLoad()
self.leftBarButtonItem = UIBarButtonItem()
}
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.setNavigationBackButton()
}
private func setNavigationBackButton() {
if(self.navigationLeftButton == nil) {
self.navigationLeftButton = UIButton(type: UIButtonType.System)
}
//Styling your navigationLeftButton goes here...
self.navigationLeftButton.addTarget(self, action: Selector("backButtonTapped"), forControlEvents: UIControlEvents.TouchUpInside)
self.leftBarButtonItem.customView = self.navigationLeftButton
self.navigationItem.leftBarButtonItem = self.leftBarButtonItem
}
func backButtonTapped(AnyObject:sender) {
// Here add your custom functionalities.
// Note, this will not pop to previous viewcontroller,
}
}