I've created a navigationBar in my view which is suppose to hold 2 buttons left and right. The problem is whatever i do it wont show the buttons/views. How am i suppose to do this? here is my code so far.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont(name: "Aerovias Brasil NF", size: 30)!, NSForegroundColorAttributeName: UIColor.whiteColor()]
navTitleView = UIView(frame: CGRectMake(0, 0, 100, 44))
titleLabel.text = "SHOPOP"
navTitleView?.addSubview(titleLabel)
self.navigationItem.titleView?.addSubview(navTitleView!)
navigationHeight = UIApplication.sharedApplication().statusBarFrame.height+self.navigationController!.navigationBar.bounds.height
searchBar = UISearchBar(frame: CGRectZero)
searchBarWrapper = UINavigationBar(frame: CGRectMake(0, 100, self.navigationController!.navigationBar.bounds.size.width, self.navigationHeight!))
var buttonSearchBar:UIBarButtonItem = UIBarButtonItem(customView: searchBar!)
var cancelButton:UIBarButtonItem = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
searchBarWrapper?.topItem?.leftBarButtonItem = buttonSearchBar
searchBarWrapper?.topItem?.rightBarButtonItem = cancelButton
self.navigationController?.view.addSubview(searchBarWrapper!)
}
#IBAction func showSearchBar(sender: UIBarButtonItem) {
searchBar?.becomeFirstResponder()
UIView.animateWithDuration(0.25, animations: {
// Optional chaining may return nil
self.searchBarWrapper!.center = CGPointMake(self.navigationController!.view.center.x, self.navigationHeight!/2)
// return
}, completion: {
finished in
println("Basket doors opened!")
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func hideSearchBar(sender: UIBarButtonItem) {
UIView.animateWithDuration(0.25, animations: {
// Optional chaining may return nil
self.searchBarWrapper!.center = CGPointMake(self.navigationController!.view.center.x, -self.navigationHeight!/2)
self.searchBar?.resignFirstResponder()
// return
}, completion: {
finished in
println("Basket doors opened!")
})
Based on the changes to your question you should look into the UISearchController class. A cursory glance at the documentation leads me to believe that after you configure it properly you can push it onto the view controller stack and it will behave like you want.
Old answer:
While I'm not sure about wrapping a UISearchBar inside of a UIBarButtonItem I can say with certainty that this is the wrong way to go about adding a UINavigationBar to a UINavigationController:
self.navigationController?.view.addSubview(searchBarWrapper!)
The right way to go about providing items for the navigation bar is to override UIViewController's navigationItem property and return a customized UINavigationItem instance. This way when you push and pop view controllers off of the UINavigationController's stack the navigation bar will be updated automatically.
This is an example from a UIViewController subclass in one of my projects.
private var customNavigationItem = UINavigationItem(title:nil);
override var navigationItem:UINavigationItem
{
get
{
customNavigationItem.title = self.title;
customNavigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.Cancel,
target:self, action:"dismissForm:");
customNavigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem:.Save,
target:self, action:"saveForm:");
return customNavigationItem;
}
}
Doing this will ensure that all of the "free" functionality that UINavigationController provides works as intended. Messing with UINavigationController's view is generally a bad idea.
Related
In my Swift project, I added a sublayer to UINavigationController.
But after adding this GAGradientLayer, I can't see the navigation title text or back button.
The weird thing is that in the view hierarchy, the CAGradientLayer(which was added as sublayer) is behind the title and button.
I tried to reload navigationController layer with LayoutIfNeeded, setNeedsLayout or setNeedsDisplay but nothing worked.
And I also just tried to change the navigation title but it doesn't work.
(Actually The text of navigation title is loaded on the view controller behind, so I don't want to change this on this VC.)
So, How can I show my navigation title text and button with CAGradientlayer above?
Here's the screenshot
Here's the codes needed
import UIKit
import SnapKit
class BulletinBoardViewController: UIViewController {
// ...
var backgroundGradientLayer: CAGradientLayer?
let bulletinBoardView = BulletinBoardView()
// MARK: - Lifecycles
override func viewDidLoad() {
super.viewDidLoad()
setBulletinBoardView()
setCells()
}
override func viewWillAppear(_ animated: Bool) {
setupBackgroundLayer()
}
override func viewWillDisappear(_ animated: Bool) {
self.backgroundGradientLayer?.removeFromSuperlayer()
}
// MARK: - Helpers
func setupBackgroundLayer() {
DispatchQueue.main.async {
if let backgroundGradientLayer = self.backgroundGradientLayer {
backgroundGradientLayer.frame = CGRect(x: 0, y: -59, width: 500, height: 103)
self.navigationController?.navigationBar.layer.addSublayer(backgroundGradientLayer)
}
}
}
func setBulletinBoardView() {
self.view.addSubview(bulletinBoardView)
bulletinBoardView.snp.makeConstraints { make in
make.right.left.top.equalTo(self.view.safeAreaLayoutGuide)
make.bottom.equalTo(self.view)
}
}
// ...
}
The origin navigation controller setting is below
class MainPageViewController: UIViewController {
// ...
func setupNav() {
navigationController?.navigationBar.tintColor = .black
navigationItem.rightBarButtonItem = listButton
navigationItem.leftBarButtonItem = settingButton
let backBarButtonItem = UIBarButtonItem(title: "",
style: .plain,
target: self,
action: nil)
self.navigationItem.backBarButtonItem = backBarButtonItem
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .systemGray3
appearance.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: AppFontName.bold, size: 20)!]
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance =
navigationController?.navigationBar.standardAppearance
}
// ...
}
Basically my issue is that I'm trying to create a drawer since iOS/Swift doesn't seem to have a native object for this. For reasons I can't recall, I decided to go with a navigation controller for this, thinking I could just override the back button(s) and their actions to turn this nav bar into a drawer. While I've changed the image/text of the back button successfully to look like a "burger" (drawer icon), I can't figure out how to successfully stop the button from taking us back, and instead have it just open/close my drawer.
Any advice appreciated, including suggestions to take an entirely different approach to creating this.
The following is the controller for the page which has the back button i'm trying to override. You can see in viewDidLoad() I call prepareDrawerPage(), which is a helper I have in another file. This definition can also be seen below.
class CreateListingController: UIViewController {
let DRAWER_OPEN = CGFloat(0)
var DRAWER_CLOSED: CGFloat = 0 // -1 * width of drawer
#IBOutlet var navItem: UINavigationItem!
#IBOutlet var drawerView: UIView!
#IBOutlet var drawerViewLead: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
DRAWER_CLOSED = UIScreen.main.bounds.width * -1 * drawerViewLead.multiplier
drawerViewLead.constant = DRAWER_CLOSED
prepareDrawerPage(controller: self, title: "PBX - Create a Listing")
}
#IBAction func flipMenu(_ sender: UIBarButtonItem) {
if (drawerViewLead.constant == DRAWER_OPEN){
drawerViewLead.constant = DRAWER_CLOSED
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
})
} else {
drawerViewLead.constant = DRAWER_OPEN
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
})
}
}
}
prepareDrawerPage():
func prepareDrawerPage(controller: UIViewController, title: String) {
let blank = UIImage()
controller.navigationController?.navigationBar.backIndicatorImage = blank
controller.navigationController?.navigationBar.backIndicatorTransitionMaskImage = blank
controller.title = title
}
The above approach works fine on the home page, where we haven't used the navigation bar yet. However, once we click to the create listing page, the button still takes us back home when clicked despite having linked an action between the BarButtonItem (back button) and CreateListingController (flipMenu function).
You can add a custom button on the navigation bar.
override func viewDidLoad() {
...
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(image: YourImage, style: .plain, target: self, action: #selector(back(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
...
}
#objc func back(sender: UIBarButtonItem) {
// Perform your custom actions
// ...
// Go back to the previous ViewController
// self.navigationController?.popViewController(animated: true)
}
In my app, I have a toolbar with UIBarButtonItems.
In most circumstances, the UIBarButtonItems are set via storyboard, and look as follows:
In a special case, I have to replace one UIBarButtonItem programmatically. This is done with the following code:
let rotatingButton = UIButton(type: .custom)
rotatingButton.setImage(UIImage(named: "LocalizationInUseNoFix"), for: .normal)
rotatingButton.addTarget(self, action: #selector(localizationButtonTapped), for: .touchUpInside)
rotatingButton.rotateStart()
let barButtonItem = UIBarButtonItem(customView: rotatingButton)
leftBarButtonItems![2] = barButtonItem
When the rotatingButton is displayed in the toolbar, it placed at a different position. It is shifted to the right, as you can see here:
How can I achieve to place both UIBarButtonItems at the same position?
EDIT:
By now I realized that the horizontal shift of the programmatically created UIBarButtonItem is not always the same, without any changes to the code: Sometimes it is shifted left, and not right:
EDIT 2:
I found a workaround:
If I set a width constrain to my button like
rotatingButton.widthAnchor.constraint(equalToConstant: 40).isActive = true
then the button is apparently always correctly placed. But I hate to hard-code constraints like this.
Is there a more elegant way to do it?
Try the below steps to perform your task:
Store left bar button items into an NSMutableArray
Replace desired UIBarbuttonItem
Set leftbarbuttonitems to this new array
Hope this steps will work
When you set the image on UIBarButton programmatically, the contentmode of the leftBarButtonItems becomes 'left' and rightBarButtonItems become 'right'. But from storyboard, it is centered. Set the image and adjust the contentMode as required.
All are working fine for Navigationbar and Toolbar
class ViewController: UIViewController {
#IBOutlet weak var toolbar: UIToolbar!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func leftAction(_ sender: Any) {
}
#IBAction func rightAction(_ sender: Any) {
}
#IBAction func changeLeftItems(_ sender: Any) {
if let items = self.navigationItem.leftBarButtonItems {
var addItems = [UIBarButtonItem]()
addItems.append(contentsOf: items)
let barItem = UIBarButtonItem(title: "3", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.leftAction(_:)))
addItems.append(barItem)
self.navigationItem.leftBarButtonItems = addItems
}
if let items = self.toolbar.items {
var addItems = [UIBarButtonItem]()
addItems.append(contentsOf: items)
let barItem = UIBarButtonItem(title: "L3", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.leftAction(_:)))
addItems.insert(barItem, at: 2)
self.toolbar.setItems(addItems, animated: true)
}
}
}
This is the best solution I found so far:
Get the width of a view of another bar button item using key value coding. This is from Jeremy W. Sherman’s answer here.
Please note that it does not use any private API, see the discussion there. The worst thing that can happen is that the view property of the UIBarButtonItem cannot be accessed. In this case, I use a default value:
var leftBarButtonItems = self.navigationItem.leftBarButtonItems
let rotatingButton = UIButton(type: .custom)
rotatingButton.setImage(UIImage(named: "LocalizationInUseNoFix"), for: .normal)
rotatingButton.addTarget(self, action: #selector(localizationButtonTapped), for: .touchUpInside)
rotatingButton.rotateStart()
// Get the width of the bar button items if possible, else set default
let leftmostBarButtonItem = leftBarButtonItems![0]
let barButtonItemWidth: CGFloat
if let leftmostBarButtonItemView = leftmostBarButtonItem.value(forKey: "view") as? UIView {
barButtonItemWidth = leftmostBarButtonItemView.frame.size.width
} else {
barButtonItemWidth = 40.0
}
rotatingButton.widthAnchor.constraint(equalToConstant: barButtonItemWidth).isActive = true
let barButtonItem = UIBarButtonItem(customView: rotatingButton)
leftBarButtonItems![2] = barButtonItem
self.navigationItem.leftBarButtonItems = leftBarButtonItems
This is working fine for me. Best way is identify item to replace and change the content
#IBAction func changeLeftItems(_ sender: Any) {
if let items = self.toolbar.items {
var addItems = [UIBarButtonItem]()
addItems.append(contentsOf: items)
let barItem = UIBarButtonItem(title: "L5", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.leftAction(_:)))
addItems.remove(at: 1)
addItems.insert(barItem, at: 1)
self.toolbar.setItems(addItems, animated: true)
}
}
first post so apologies if I mess something up. I have researched this for hours upon hours and read other posts here on stack exchange to no avail.
I have created a nib file that defines a custom view and have defined a custom class (UIView) to manage the outlets of the custom view. As you can see from the code below excerpted from my custom UIView class associated with the nib, I have a date picker as the input view for the custom class and a UIToolBar with two UIBarButtonItems. Both of these appear as desired through a tap gesture recognizer... however the problem is the UIBarButtonItems do not call the action when tapped. Placing a breakpoint in the action function reveals that the code is never run. I feel that something with the view lifecycle is preventing a reference from being made, but I am new to Swift so some help here would be appreciated. I don't think it is selector syntax as the tap gesture recognizer works as desired. I've tried messing with button click handling access levels. I've tried doing input view setup when the view awakes from the nib as well, along with trying to put the code in different parts of the lifecycle.
If it matters for lifecycle's sake, this nib is a part of a table view cell. I call for this nib to be loaded when the table view cell awakes from it's nib.
Thanks!
#IBOutlet weak var timerStackView: UIStackView!{
didSet{
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(HandleTap(_:)))
timerStackView.addGestureRecognizer(tapGesture)
}
}
var datePicker: UIDatePicker {
let picker = UIDatePicker()
picker.backgroundColor = UIColor.black
picker.isOpaque = false
picker.setValue(UIColor.white, forKey: "textColor")
return picker
}
var datePickerAccessoryView: UIToolbar {
let accessoryView = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.done, target: self, action: #selector(handleDatePickerButtonClick(_:)))
doneButton.tintColor = UIColor.white
let cancelButton = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.plain, target: self, action: #selector(handleDatePickerButtonClick(_:)))
cancelButton.tintColor = UIColor.white
accessoryView.setItems([cancelButton, doneButton], animated: true)
return accessoryView
}
override var inputView: UIView? {return datePicker}
override var inputAccessoryView: UIView? {return datePickerAccessoryView}
override var canBecomeFirstResponder: Bool {return true}
override var canResignFirstResponder: Bool {return true}
// MARK: - Private functions
#objc fileprivate func HandleTap(_ sender: UITapGestureRecognizer) -> Void {
if !self.isFirstResponder {
switch sender.state {
case .ended:
datePicker.date = Date()
self.becomeFirstResponder()
default:
break
}
}
}
#objc #IBAction internal func handleDatePickerButtonClick(_ sender: UIBarButtonItem) -> Void {
switch sender.title! {
case "Done":
// To be implemented
case "Cancel":
// To be implemented
default:
break
}
}
You are initialising the UIToolbar without a frame and that would make it not register any touch events because they would be out of the toolbar's bounds.
Replace let accessoryView = UIToolbar() with something like let accessoryView = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 44))
Or you can call accessoryView.sizeToFit() before return accessoryView
I suppose the tap gesture recognizer is interfering with native UIBarButtonItem click events. But why do you use a gesture recognizer for that?
You should better add an action to each particular UIBarButtonItem.
By default, Navigation back button text comes as previous screen title or <
I am trying to change that to just <=|
But Its coming as shown in the picture
BackButton Image.
So, I want to know how to change its font to make big <=| and remove the default <
I tried
Tried the same code in viewDidLoad of first start screen,
So i also want to know where to place this code:
override func viewWillAppear(animated: Bool)
{
self.navigationItem.leftBarButtonItem?.title = "<=|"
let FntStgVal = [NSFontAttributeName:UIFont.systemFontOfSize(50, weight: UIFontWeightLight)]
self.navigationItem.leftBarButtonItem?.setTitleTextAttributes(FntStgVal, forState: .Normal)
}
Change your code in viewDidLoad like this.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func setNavigationWithCustomBackButton() {
let btnLeft:UIButton! = UIButton(frame: CGRectMake(0, 0, 20, 16))
btnLeft.setTitle("<=|", forState: .Normal)
btnLeft.titleLabel?.font = UIFont.systemFontOfSize(19, weight: UIFontWeightLight)
btnLeft!.addTarget(self, action: "handleBack:",forControlEvents: UIControlEvents.TouchUpInside)
let leftItem:UIBarButtonItem = UIBarButtonItem(customView: btnLeft!)
self.navigationItem.leftBarButtonItem = leftItem
}
func handleBack(sender: UIButton) {
self.navigationController?.popViewControllerAnimated(true)
}
}
Now use this BaseViewController as parent of your all viewController and call its method in viewDidLoad like this.
class ViewController1: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setNavigationWithCustomBackButton()
}
}
Now it will add custom back button in your NavigationBar.
Hope this will help you.