How to add a segmented control to a navigation bar ? (iOS) - ios

I would like to add a segmented control to a navigation bar like this
but when i drag the segmented control to the navigation bar the large title is gone. How can create the above UI ?

You should add the segmented control as the titleView of the navigation bar.
Below is the sample code:
let titles = ["All", "Missed"]
segmentControl = UISegmentedControl(items: titles)
segmentControl.tintColor = UIColor.white
segmentControl.backgroundColor = UIColor.blue
segmentControl.selectedSegmentIndex = 0
for index in 0...titles.count-1 {
segmentControl.setWidth(120, forSegmentAt: index)
}
segmentControl.sizeToFit()
segmentControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged)
segmentControl.selectedSegmentIndex = 0
segmentControl.sendActions(for: .valueChanged)
navigationItem.titleView = segmentControl

You can try the below simple code,
var segmentedController: UISegmentedControl!
let items = ["Label A", "Label B"]
segmentedController = UISegmentedControl(items: items)
navigationItem.titleView = segmentedController
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(handleSignOut))
navigationItem.rightBarButtonItem?.tintColor = UIColor.black

If you want to add UISegmentControl by using XIB then you can do that by following these simple steps:
Design your custom view in XIB (Refer XIB for your reference https://i.stack.imgur.com/AJDCo.png)
Put the code in your ViewController
class ViewController: UIViewController {
lazy var navSegmentedView: YourCustomView = {
guard let aView = Bundle.main.loadNibNamed("\(YourCustomView.self)", owner: self, options: nil)?.first as? YourCustomView else { return YourCustomView() }
aView.backgroundColor = .clear
aView.frame = CGRect(x: 0, y: 0, width: 160, height: 40)
aView.segmentControl.addTarget(self, action: #selector(segmentChanged(_:)), for: .valueChanged)
return aView
}()
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
}
func setupNavBar() {
navigationItem.titleView = navSegmentedView
navigationItem.titleView?.backgroundColor = .clear
}
#objc func segmentChanged(_ sender: UISegmentedControl) {
print(sender.selectedSegmentIndex)
}
}

Related

UIDatePicker with toolbar but without text field

In my app, I have a button which when clicked should display time picker with toolbar on it. Most of examples I saw added toolbar as an inputAccessoryView on text field, but in my case I don't have a text field.
So, I created a custom view which has date time picker and toolbar and I am adding that view as a subview to my controller's view, but I don't see the custom view on the app.
Below is the controller code for button clicked :
func buttonClicked(date: Date) {
let timePicker = EditTimeHelper.createTimePickerAndToolbar(displayDate: date)
self.view.addSubview(timePicker)
}
Code for custom view in separate EditTimeHelper class:
static func createTimePickerAndToolbar(displayDate: Date) -> UIView {
let pickerView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 300))
let timePicker = createTimePicker(displayDate: displayDate)
pickerView.addSubview(timePicker)
let toolbar = createUIToolBar()
pickerView.addSubview(toolbar)
return pickerView
}
static func createTimePicker(displayDate: Date) -> UIDatePicker {
let timePicker:UIDatePicker = UIDatePicker()
timePicker.datePickerMode = UIDatePicker.Mode.time
timePicker.date = displayDate
timePicker.minuteInterval = 15
if #available(iOS 13.4, *) {
timePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions where time picker is wheels style by default.
}
timePicker.addTarget(self, action: #selector(timeChanged(_:)), for: UIControl.Event.valueChanged)
timePicker.backgroundColor = .white
timePicker.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 200, width: UIScreen.main.bounds.width, height: 200)
return timePicker
}
private static func createUIToolBar() -> UIToolbar {
let pickerToolbar = UIToolbar()
pickerToolbar.autoresizingMask = .flexibleHeight
//customize the toolbar
pickerToolbar.barStyle = .default
pickerToolbar.barTintColor = UIColor.black
pickerToolbar.backgroundColor = UIColor.white
pickerToolbar.isTranslucent = false
pickerToolbar.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 100)
// add buttons
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelBtnClicked(_:)))
cancelButton.tintColor = UIColor.white
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneBtnClicked(_:)))
doneButton.tintColor = UIColor.white
//add the items to the toolbar
pickerToolbar.items = [cancelButton, flexSpace, doneButton]
return pickerToolbar
}
#objc func timeChanged(_ sender: UIDatePicker) {
}
#objc func cancelBtnClicked(_ button: UIBarButtonItem?) {
}
#objc func doneBtnClicked(_ button: UIBarButtonItem?) {
}
Any idea what am I doing wrong and not seeing custom view?
If I call EditTimeHelper.createTimePicker(displatDate: date), then I see the time picker, but I want to add toolbar on top of it.
When I debug this code, I do see time picker and toolbar as custom view's subviews, but I just don't see them on the app.
The reason why you can't see the picker and the tool bar is because you have positioned the time picker and the tool bar incorrectly. Notice these two lines:
timePicker.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 200, width: UIScreen.main.bounds.width, height: 200)
// and
pickerToolbar.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 100)
Since these are subviews of the pickerView, the coordinates are relative to the top left corner of pickerView, not the top left corner of the screen. You should instead do
timePicker.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 200)
// and
pickerToolbar.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 100)
Now you should see the tool bar and the time picker.
There are other problems with your code, however. First, timeChanged, cancelBtnClicked and doneBtnClicked won't be called. You have added self as the target for the bar button items and the picker, but since you are in a static method, self refers to the class itself. When the user presses the done button, it would try to call a method called doneBtnClicked on the class, rather than a particular instance. But the class doesn't have such a method! The doneBtnClicked you have declared is an instance method, available on instances only.
Second, you are giving these views fixed positions. This means that the layout will look very weird when the user rotates the screen. Just use AutoLayout!
You can make timeChanged, cancelBtnClicked and doneBtnClicked all static too, but a much better way is to just create a custom UIView subclass. Here is an example, as a starting point:
class TimePickerToolBarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
let timePicker = createTimePicker()
addSubview(timePicker)
let toolBar = createUIToolBar()
addSubview(toolBar)
timePicker.translatesAutoresizingMaskIntoConstraints = false
toolBar.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
toolBar.heightAnchor.constraint(equalToConstant: 44),
toolBar.topAnchor.constraint(equalTo: topAnchor),
toolBar.leftAnchor.constraint(equalTo: leftAnchor),
toolBar.rightAnchor.constraint(equalTo: rightAnchor),
timePicker.leftAnchor.constraint(equalTo: leftAnchor),
timePicker.rightAnchor.constraint(equalTo: rightAnchor),
timePicker.bottomAnchor.constraint(equalTo: bottomAnchor),
timePicker.topAnchor.constraint(equalTo: toolBar.bottomAnchor),
])
}
private func createTimePicker() -> UIDatePicker {
let timePicker:UIDatePicker = UIDatePicker(frame: .zero)
timePicker.datePickerMode = UIDatePicker.Mode.time
timePicker.minuteInterval = 15
if #available(iOS 13.4, *) {
timePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions where time picker is wheels style by default.
}
timePicker.addTarget(self, action: #selector(timeChanged(_:)), for: UIControl.Event.valueChanged)
timePicker.backgroundColor = .white
return timePicker
}
private func createUIToolBar() -> UIToolbar {
let pickerToolbar = UIToolbar(frame: .zero)
//customize the toolbar
pickerToolbar.barStyle = .default
pickerToolbar.barTintColor = UIColor.black
pickerToolbar.backgroundColor = UIColor.white
pickerToolbar.isTranslucent = false
// add buttons
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelBtnClicked(_:)))
cancelButton.tintColor = UIColor.white
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneBtnClicked(_:)))
doneButton.tintColor = UIColor.white
//add the items to the toolbar
pickerToolbar.items = [cancelButton, flexSpace, doneButton]
return pickerToolbar
}
#objc func timeChanged(_ sender: UIDatePicker) {
}
#objc func cancelBtnClicked(_ button: UIBarButtonItem?) {
}
#objc func doneBtnClicked(_ button: UIBarButtonItem?) {
}
}

Flicker during transition when Search bar (within Large Title Navigation Bar) tapped - Swift 4.2

I have added search bar in navigation bar large title. I am facing a flicker issue when the search bar is tapped to type into it.
Here is the video to the explain the issue:
The code I have written to achieve this is:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.title = "Leads"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addNewLeadsAction(_:)))
let mysearchController = UISearchController(searchResultsController: nil)
mysearchController.searchResultsUpdater = self
mysearchController.searchBar.placeholder = "Search Lead"
mysearchController.searchBar.showsCancelButton = true
mysearchController.searchBar.barStyle = UIBarStyle.default
mysearchController.searchBar.backgroundColor = UIColor(patternImage: Common.gradientImage(view: (self.navigationController?.navigationBar)!))
if let textfield = mysearchController.searchBar.value(forKey: "searchField") as? UITextField {
textfield.textColor = UIColor.white
if let backgroundview = textfield.subviews.first {
backgroundview.backgroundColor = .white
backgroundview.layer.cornerRadius = 10
backgroundview.clipsToBounds = true
}
}
for searchBarView in mysearchController.searchBar.subviews{
for cancelButtonView in searchBarView.subviews{
if cancelButtonView.isKind(of: UIButton.self) {
let filterButton:UIButton = cancelButtonView as! UIButton
filterButton.isEnabled = true
filterButton.setTitle("", for: .normal)
let image1 = UIImage(named: "filterIcon")?.withRenderingMode(.alwaysTemplate)
filterButton.setImage(image1, for: .normal)
filterButton.contentMode = .scaleAspectFit
filterButton.tintColor = .white
filterButton.addTarget(self, action: #selector(filterAction(_:)), for: .touchUpInside)
}
}
}
self.navigationItem.searchController = mysearchController
self.definesPresentationContext = true
leadsTableView.reloadData()
}
Trying to make the transition smooth is what i'm looking to achieve.
Following are the things I have tried already:
iOS 11 UISearchBar in UINavigationBar
Navigation bar + Search controller + Large title: Hairline during scrolling
Any help will be appreciated

Swift 3 - Push a new view but keep the same NavigationBar

I have a UISearchBar in my application. When I press a button to "open" the searchbar does a new view appear. But the problem is that the NavigationController changes and the UISearchBar disappear. How can I do so I can keep the current NavigationController with my searchbar even if a new view appear. (So I still searching when the new view appear)
P.s my code is not the best and I´m not using Storyboard!
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
setupNavigationBar()
}
Here is the new view that appear:
class UserSearchController: UICollectionViewController, UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.blue
}
}
And here is the whole searchbar code:
import UIKit
var searchBar = UISearchBar()
var searchBarButtonItem: UIBarButtonItem?
var logoImageView: UIImageView!
extension HomeController {
func setupNavigationBar() {
let button = UIButton(type: .system)
button.setImage(#imageLiteral(resourceName: "search"), for: .normal)
button.addTarget(self, action: #selector(showSearchBar), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = barButton
let logoImage = UIImage(named: "home")!
logoImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: logoImage.size.width, height: logoImage.size.height))
logoImageView.image = logoImage
navigationItem.titleView = logoImageView
searchBar.delegate = self
searchBar.searchBarStyle = UISearchBarStyle.minimal
searchBar.placeholder = "Search"
searchBar.barTintColor = UIColor.gray
searchBarButtonItem = navigationItem.rightBarButtonItem
}
func showSearchBar() {
let layout = UICollectionViewFlowLayout()
let userSearchController = UserSearchController(collectionViewLayout: layout)
self.navigationController?.pushViewController(userSearchController, animated: true)
searchBar.alpha = 0
navigationItem.titleView = searchBar
navigationItem.setLeftBarButton(nil, animated: true)
navigationItem.rightBarButtonItem = nil
searchBar.showsCancelButton = true
UIView.animate(withDuration: 0.5, animations: {
self.searchBar.alpha = 1
}, completion: { finished in
self.searchBar.becomeFirstResponder()
})
}
public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
hideSearchBar()
}
func hideSearchBar() {
navigationItem.setRightBarButton(searchBarButtonItem, animated: true)
logoImageView.alpha = 0
UIView.animate(withDuration: 0.3, animations: {
self.navigationItem.titleView = self.logoImageView
self.logoImageView.alpha = 1
}, completion: { finished in
})
}
}
The reason why your search bar is only visible on your first View Controller is because you are using the View Controller's titleView property. Each UIViewController has it's own titleView property, so if you push a View Controller onto your first VC, it will also need to have the titleView property set to a search bar view with the required configuration.
I think you can create a base class, add UISearchBar above the base class, and then you want the current controller with UISearchBar to inherit your base class

Swift: How do I create a custom UINavigationBar and add a custom back button?

I am trying to create a custom navigationBar.
I am hiding the original navigationBar in viewWillAppear like so:
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
}
I am subclassing UINavigationBar like so:
let navBar: UINavigationBar = {
let view = UINavigationBar()
view.backgroundColor = .clear
view.isTranslucent = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
In viewDidLoad I am calling setupNavBar():
func setupNavBar() {
view.addSubview(navBar)
self.navBar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 80)
let backButton = UIBarButtonItem(image: UIImage(named:"backThick"), style: .plain, target: self, action: #selector(popControllerOffStack))
}
The problem is that backButton is added to the original navigationBar that is being hidden. This makes me think I am incorrectly creating the navigationBar. How do I add the button to navBar?
Updated Code (still not working):
class CustomNavBar: UINavigationBar {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .red
}
}
// In the viewController
let navBar = CustomNavBar()
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
}
func setupNavBar() {
view.addSubview(navBar)
navBar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 80)
let backButton = UIBarButtonItem(image: UIImage(named:"backThick"), style: .plain, target: self, action: #selector(popControllerOffStack))
self.navigationItem.leftBarButtonItem = backButton
}
The button is being added to the original navigationBar.
You are not subclassing UINavigationBar. Rather, you are creating a new instance of UINavigationBar and modifying its properties inside a computed variable. This means each time you access navBar, you are initializing a new UINavigationBar object.
To create a subclass:
class MyCustomNavigationBar: UINavigationBar {
// Set properties in here after initialization
}
Once you have a proper subclass created, You can initialize an instance like so:
var navBar = MyCustomNavigationBar()
Finally, add your button to the navigation bar:
let backButton = UIBarButtonItem(image: UIImage(named:"backThick"), style: .plain, target: self, action: #selector(popControllerOffStack))
// Assuming 'self' is an instance of UINavigationController()
self.navigationItem.leftBarButtonItem = backButton
See the official Swift Programming Language Guide on Inheritance.
Swift 3.0
You can set custom Back button as like below
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(image: UIImage(named: "image_name"), style: .plain, target: self, action: #selector(Class.methodName))
backButton.tintColor = UIColor.white
self.navigationItem.leftBarButtonItem = backButton
Also you can try below code:
let btnLeftMenu: UIButton = UIButton()
btnLeftMenu.setImage(UIImage(named: "image_name"), for:UIControlState())
btnLeftMenu.addTarget(self, action: #selector(moveImage), for:UIControlEvents.touchUpInside)
btnLeftMenu.frame = CGRect(x: 0, y: 0, width: 25, height: 25)
let barButton = UIBarButtonItem(customView: btnLeftMenu)
self.navigationItem.leftBarButtonItem = barButton*

BarButtonItem EdgeInsets doesn't work

I'm trying to add a padding to the right of a navigationBar rightBarButtonItems in between the buttons but it's now working.
Here's the button creation code with the given inset:
lazy var previewBarButtonItem: UIBarButtonItem = {
let buttonItem = UIBarButtonItem(title: "Preview", style: .plain, target: self, action: #selector(handlePreview))
buttonItem.tintColor = UIColor.black
buttonItem.setTitleTextAttributes([NSFontAttributeName: UIFont.systemFont(ofSize: 14)], for: .normal)
buttonItem.imageInsets = UIEdgeInsetsMake(0, -15, 0, 15)
return buttonItem
}()
override func viewDidLoad() {
super.viewDidLoad()
setupRightNavItems()
}
fileprivate func setupRightNavItems () {
navigationItem.rightBarButtonItems = [submitBarButtonItem, previewBarButtonItem]
}
Can you give a hint on why this isn't working?
Thanks
I managed to fix this issue, just added a fixedSpace in-between the bar button items as follows:
1) remove any EdgeInsets
2) add the following fixed space:
fileprivate func setupRightNavItems () {
let spacing = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
spacing.width = 25 //change to your desired space width
navigationItem.rightBarButtonItems = [submitBarButtonItem, spacing, previewBarButtonItem]
}

Resources