How to implement toolBar in tabBar? - ios

I would like to implement a toolbar in my tab bar or ender my tab bar like in the photo app. I show you :
Tab Bar
turn to when select button tapped
Tool bar
I already try to put the toolbar in the Tab bar controller view, without success.
Thanks for your replies !

A simple solution for this is to hide the tabBar on some event (Photos app has a button which does this). Then create a custom ToolBar and add it as a subview of the view controller.

I believe this might be the sort of behaviour you're looking for. You don't have to use a custom UIButton you could use a UIBarButtonItem, but that's up to you, I just chose to demonstrate it with a custom UIButton to show you that this is one way of doing it.
class ViewController: UIViewController {
var selectButton: UIButton!
var isInSelectMode = false
override func viewDidLoad() {
super.viewDidLoad()
selectButton = UIButton(type: .system)
selectButton.addTarget(self, action: #selector(selectButtonTapped), for: .touchUpInside)
selectButton.setTitle("Select", for: .normal)
view.addSubview(selectButton)
let trashButton = UIBarButtonItem(barButtonSystemItem: .trash, target: nil, action: nil)
toolbarItems = [trashButton]
selectButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
selectButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -15),
selectButton.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 15),
])
}
#objc func selectButtonTapped() {
isInSelectMode = !isInSelectMode
if isInSelectMode {
selectButton.setTitle("Done", for: .normal)
tabBarController?.tabBar.isHidden = true
tabBarController?.tabBar.backgroundColor = .systemBackground
navigationController?.setToolbarHidden(false, animated: true)
} else {
selectButton.setTitle("Select", for: .normal)
tabBarController?.tabBar.isHidden = false
tabBarController?.tabBar.backgroundColor = .clear
navigationController?.setToolbarHidden(true, animated: true)
}
}
}
Here's what it looks like:

==> Tested in iOS 15.5
==> First your view controller must embed in UINavigationController
==> in viewDidLoad set editButtonItem to navigationItem and also configure UINavigationController native toolbarItems
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = editButtonItem
self.toolbarItems = [UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: nil)]
}
==> After that override setEditing Method and implement your logic as required. and you are done
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
self.tableView.setEditing(editing, animated: true)
self.tabBarController?.tabBar.isHidden = editing
self.navigationController?.setToolbarHidden(!editing, animated: true)
}
You can check it out image here
==> But there is one issue and need further assistance... if anyone have any idea then comment bellow..
Issue : toolbar leave safeArea space and came top of that...
You can also checkout Demo Native toolbar Demo

Related

Swift 5 - How to hide back button in Navigation bar or move to another screen without back button

I was having this issue and I've tried a lot of solutions that was proposed by some kind people here in the following topic:
Swift - How to hide back button in navigation item
I created a ViewController class:
import SwiftUI
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.setHidesBackButton(true, animated: true)
self.navigationController?.navigationBar.isHidden = true
//self.navigationItem.backButtonTitle = "hohoho"
self.navigationItem.leftBarButtonItem = nil
self.navigationItem.hidesBackButton = true
//UINavigationBar.appearance().isHidden = true
//navigationItem.backBarButtonItem = UIBarButtonItem(title: "Home/Return or nohing", style: .bordered, target: nil, action: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.setHidesBackButton(true, animated: true)
self.navigationController?.navigationBar.isHidden = true
//self.navigationItem.backButtonTitle = "hohoho"
self.navigationItem.leftBarButtonItem = nil
self.navigationItem.hidesBackButton = true
//UINavigationBar.appearance().isHidden = true
//navigationItem.backBarButtonItem = UIBarButtonItem(title: "Home/Return or nohing", style: .bordered, target: nil, action: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationItem.setHidesBackButton(true, animated: true)
self.navigationController?.navigationBar.isHidden = true
//self.navigationItem.backButtonTitle = "hohoho"
self.navigationItem.leftBarButtonItem = nil
self.navigationItem.hidesBackButton = true
//UINavigationBar.appearance().isHidden = true
//navigationItem.backBarButtonItem = UIBarButtonItem(title: "Home/Return or nohing", style: .bordered, target: nil, action: nil)
}
}
and AS you can see in the above code I tried every single way with no change - back button still appear - then I try to make simple change like change the text of the back button or the shape and also there is no result!!
Am I do something wrong :( Because I feel like the whole class is not active for my view
Do I need to create an object of ViewController or something like that? Because I just wrote the mentioned code about my view code.
MY GOAL: I just want to move from view to another with no back button if there is another way I wouldn't mind to do it.
PPLLLSSSS HELPP ME Guys I'm so tired, I'll work on another things until find a solution for that and I'm sure there is a lot of people who want a solution for that issue.
Once I find the solution I'll share it with you guys :) Best Wishes and Regards
You just have to add the below code in the ViewController where you want to hide the backbutton.
navigationItem.setHidesBackButton(true, animated: true)
Segue from viewController to viewController2 and name the segue testSegue. This should work.
I had the exact same problem and the only solution that worked was adding
.navigationBarBackButtonHidden(true) in my SwiftUI view

Is there a way to resign first responder of view that isn't currently presented?

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

Disable animation with backBarButtonItem

As my title saying is there any way to customize default back button action method ?
I tried to find on Google but not get any answer that fulfill my requirement.
I know one way to Add UIBarButtonItem to self.navigationItem.leftBarButtonItem and customize action method but I will be hide < sign of Back button and I don't want this.
Some R&D that done by Google.
1) How to override self.navigationItem.backBarButtonItem Action?
2) iOS disable animation for NavigationController back button
3) Google Question/Answer
Add image that look like <Back button that is one way but I want to going with native way.
One option is to implement the viewWillDisappear method on the View Controller and set animationEnable No
override func viewWillDisappear(animated : Bool) {
super.viewWillDisappear(animated)
UIView.setAnimationsEnabled(false)
}
override func viewDidDisappear(_ animated: Bool) {
UIView.setAnimationsEnabled(false)
}
you have to add your custom bar button there...
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: YourDesiredFrame)
button.setImage(UIImage(named : "back"), for: .normal)
button.addTarget(self, action: #selector(backButtonTapped(_:)), for: .touchUpInside)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.backBarButtonItem = barButton
}
func backButtonTapped(_ sender : UIButton) {
_ = self.navigationController?.popViewController(animated : false)
}
// Swift 4
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// turn on animations
UIView.setAnimationsEnabled(true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// turn off animations
UIView.setAnimationsEnabled(false)
}

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)
}
}

Using Back on Navigation Bar bugs UIBarButtonItem

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.

Resources