I have two view controllers and i am using push segue to show them. My storyboard looks like this:
But i want to use standalone navigation bar on second view controller. To do this i wrote following code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
placeNavigationBar()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
var normalButton: UIButton!
var counter = 0
let item = UINavigationItem()
let navigationBar = UINavigationBar()
private func placeNavigationBar() {
let backButtonImage = UIImage(named: "backButton")
normalButton = UIButton(frame: CGRect(x: 0, y: 0, width: backButtonImage!.size.width, height: backButtonImage!.size.height))
normalButton.setImage(backButtonImage, for: .normal)
normalButton.setTitleColor(.systemBlue, for: .normal)
normalButton.contentHorizontalAlignment = .leading
normalButton.contentVerticalAlignment = .center
normalButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 3.0, bottom: 0.0, right: -3.0)
normalButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
let backBarButton = UIBarButtonItem(customView: normalButton)
backBarButton.tintColor = .systemBlue
let leftLabel = UILabel()
leftLabel.text = "anony-12345678645"
leftLabel.textColor = .black
let profilePicture = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
profilePicture.translatesAutoresizingMaskIntoConstraints = false
profilePicture.image = #imageLiteral(resourceName: "test2")
profilePicture.contentMode = .scaleAspectFill
profilePicture.clipsToBounds = true
profilePicture.widthAnchor.constraint(equalToConstant: 30).isActive = true
profilePicture.heightAnchor.constraint(equalToConstant: 30).isActive = true
profilePicture.layer.cornerRadius = 15
item.leftBarButtonItems = [backBarButton, UIBarButtonItem(customView: profilePicture) ,UIBarButtonItem(customView: leftLabel)]
navigationBar.isTranslucent = false
navigationBar.barTintColor = .white
self.view.addSubview(navigationBar)
navigationBar.translatesAutoresizingMaskIntoConstraints = false
navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
navigationBar.items = [item]
navigationBar.delegate = self
}
It is working well but i am not happy with the navigation bar transition animation. This video shows how it looks now:
https://www.youtube.com/watch?v=1wakUhA940c
What i want:
https://youtu.be/h3-HzKQsxWc
Check the navigation bar transitions on both videos, in second video the navigation bar comes to top of the first view controller's navigation bar. But in the first video the second navigation bar just pushes the first navigation bar to off screen.
How can i achive the same tranisiton in second video?
I did something similar where the initial controller had a custom navigation bar look-alike, and what I did was to call navigationController.setNavigationBarHidden(_, animated: true) on each controller on their viewWillAppear methods, with true on the main controller and false on the rest.
You could do the same, and add a separate UINavigationBar to your view. If you set its constraints properly (basically pin it to the safeArea.top of your controller), it will expand to the safe area.
You will have to populate the navigation bar by accessing it directly instead of your controller's navigationItem, but it's not a big deal and you'll get the transition you want.
If you want this to happen between all the controllers in the stack, just hide the navigation bar on the navigation controller and set a navigation bar on each view yourself.
What you don't prefer is actually the default behavior, animation of iOS. Now, one way to do what you do prefer is making your own navigation bar.
You may hide or show (toggle) the visibility of your navigation bar in your viewWillAppear method by using the following:
/// Shows the navigation bar.
public func showNavBar(animated: Bool) {
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
/// Hides the navigation bar.
public func hideNavBar(animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
And then start doing the layout of your navigationBar in Storyboard.
Lastly, making use of Simulated Metrics, like selecting NONE to Top Bar will help you visualize what you're doing.
Related
I'm adding a button programaticly to TabBarController using this code:
let tabBarHeight = tabBar.layer.bounds.height * 1.2
let win: UIWindow = ((UIApplication.shared.delegate?.window)!)!
let button = Floaty(frame: CGRect(origin: CGPoint(x: 0.0, y: win.frame.size.height),size: CGSize(width: tabBarHeight, height: tabBarHeight)))
button.center = CGPoint(x: win.center.x, y: win.frame.size.height - tabBar.layer.bounds.height)
button.buttonImage = UIImage(named: "icoZ")
let item = FloatyItem()
item.buttonColor = UIColor(named: "ButtonGreen") ?? UIColor.green
item.iconImageView.image = #imageLiteral(resourceName: "percentButton")
item.handler = { item in
let vc = self.storyboard?.instantiateViewController(withIdentifier: "PercentVC") ?? PercentViewController()
DispatchQueue.main.async {
self.present(vc, animated: true, completion: nil)
button.close()
}
}
button.addItem(item: item)
let item2 = FloatyItem()
item2.buttonColor = UIColor(named: "ButtonGreen") ?? UIColor.green
item2.iconImageView.image = #imageLiteral(resourceName: "scanQr")
item2.handler = { item in
let vc = ScannerViewController()
DispatchQueue.main.async {
self.present(vc, animated: true, completion: nil)
button.close()
}
}
button.addItem(item: item2)
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
let bottom = button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
let center = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let height = button.heightAnchor.constraint(equalToConstant: tabBarHeight)
let width = button.widthAnchor.constraint(equalToConstant: tabBarHeight)
NSLayoutConstraint.activate([bottom,center,height,width])
but when I present a modal view controller and dismiss it the button is moved to the middle of the screen. like this.
the desired is this.
A quick workaround may be to change your modal presentation style to be overFullScreen. The default modal presentation style removes your view from the hierarchy and re-adds it when the modal is dismissed. That causes your views to re-layout.
If that's not acceptable, start with Xcode's view debugger. I'm guessing that the parent view for the button isn't being laid out correctly. If it is being laid out correctly you can try call setNeedsLayout() on the view in viewWillAppear to trigger a recalculation of the layout.
Finally, what you're doing isn't really best practice. You have no guarantee that the tab bar won't be moved above your button the next time the UITabBarController triggers a layout. This honestly should probably be a custom component if you're concerned about long term stability.
I have a problem working on the UI. I was using a navigationController to move the screen.
But I didn't want to use the basic back button, so I hid the basic back button, added the navigation bar to the storyboard,
and added items to make the back button. However, the screen does not move to the previous screen. How can I get to the previous screen?
Basic backButton // I'm hiding it now.
I want to use the back button I made.
Backward command for back button items.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
#IBAction func backButton(_ sender: UIBarButtonItem) {
self.dismiss(animated: false, completion: nil) // Did not worked!
}
In addition, I would like to remove the underline of the navigation header. How can I remove it?
I use this in a project for the same situation:
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: false, completion: nil)
this can be used after iOS 13 deprecation of keyWindow:
let keyW = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
keyW?.rootViewController?.dismiss(animated: false, completion: nil)
This can also be used:
self.navigationController?.popViewController(animated: false)
or
self.navigationController?.popToRootViewController(animated: false)
About the button:
Why not just use the navigation button by creating this hierarchy in storyboard:
Then just change the image for btn to your custom image (I use burger1.png) like this:
You can customize back button with following code
let buttonView = UIView(frame: CGRect(x: 0, y: 0, width: 25, height: 25))
let buttonImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 25, height: 25))
buttonImageView.contentMode = .scaleAspectFit
buttonImageView.image = UIImage(named: "back_button")
let btnLogo = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
btnLogo.setTitle("", for: .normal)
btnLogo.backgroundColor = UIColor.clear
btnLogo.layer.masksToBounds = true
btnLogo.addTarget(self, action: #selector(backButton), for: .touchUpInside)
buttonView.addSubview(buttonImageView)
buttonView.addSubview(btnLogo)
let barButton = UIBarButtonItem(customView: buttonView)
self.navigationItem.leftBarButtonItem = barButton
if you are using navigationbar you should use navigationController.popViewController instead of dismiss method
I have a UIView which has a button, I have added a round border around the button using UIView, the button works and is positioned perfectly, and I want the UIView in front of the navigation bar, but once I add the UIView in front of the navigation bar, the button disappears but the UIVew come in front of the navigation bar.
let viewBar = UIView()
let userProfileView = UIView()
let userProfileButton = UIButton(type: .custom)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
setupNextButton()
setupViewBar()
setupUserProfileButton()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// To Bring the viewBar in front of the navigation bar
self.navigationController?.view.addSubview(viewBar)
}
func setupViewBar() {
viewBar.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.5)
view.addSubview(viewBar)
addNavigationBarConstraints()
}
func setupUserProfileButton() {
userProfileButton.setImage(#imageLiteral(resourceName: "profilePictureSmall.png"), for: .normal)
userProfileButton.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
userProfileButton.addTarget(self, action: #selector(profilePictureTapped), for: .touchUpInside)
userProfileView.layer.cornerRadius = 20
userProfileView.layer.borderWidth = 1.5
userProfileView.clipsToBounds = true
userProfileView.layer.borderColor = UIColor.black.cgColor
userProfileView.addSubview(userProfileButton)
//viewBar.addSubview(userProfileView)
view.addSubview(userProfileView)
addUserProfileButtonConstraints()
}
Here are the constraints to the user profile button and the viewBar
func addViewBarConstraints() {
viewBar.translatesAutoresizingMaskIntoConstraints = false
viewBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
viewBar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
viewBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
viewBar.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
func addUserProfileButtonConstraints() {
userProfileView.translatesAutoresizingMaskIntoConstraints = false
userProfileView.topAnchor.constraint(equalTo: viewBar.safeAreaLayoutGuide.topAnchor, constant: 5).isActive = true
userProfileView.trailingAnchor.constraint(equalTo: viewBar.safeAreaLayoutGuide.trailingAnchor, constant: -15).isActive = true
userProfileView.widthAnchor.constraint(equalToConstant: 40).isActive = true
userProfileView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
How can I make the button appear? I have added some images too.
The navigation bar goes on top of the stack of views and so it's hiding your red UIView. So, essentially, you have to add the button to the navigation bar instead of adding it to the UIView:
let barButton = UIBarButtonItem(customView: userProfileButton)
self.navigationItem.rightBarButtonItem = barButton
I am working on a chat app for my graduation project in iOS. I designed the following navigation bar:
Now I am trying to develop above graphic in my Xcode project, but I don't know if this is the correct way to achieve this and doesn't get it like the graphic.
I am using a xib file, that I load in my ViewController.swift using an instance of it. Than add it as the titleView of the navigationItem:
let helperView = HelperView()
navigationItem.titleView = helperView
This is the result of above code snippet:
The problem of this result is that it is overlapping the left bar button item and another problem, that I still doesn't figured out is, if the message bubble can have a dynamic height, when it have multiple lines (max is 3 lines).
Does anyone have experience with this kind of design within Xcode and is this the correct way to do this, or is there a better way to achieve this. Maybe a custom UINavigationController class?
Try create the whole navigation view that you desire, that mean the width is equal to the view, then try use this code to add it
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.backgroundColor = .clear
navigationController?.view.insertSubview(subview, belowSubview: navigationController?.navigationBar)
It will makes your navigation bar become invisible but still showing the bar button
Update, by dimpiax
But better to override your UINavigationController class, and setup view in viewDidLoad
navigationBar.shadowImage = UIImage()
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.backgroundColor = .clear
And on dependent viewController's view – show specific view.
// --------------------------------------------------------
// MARK:- Navigation SetUp
// --------------------------------------------------------
private func _navigationBarSetUp(){
///# Navigatoin bar and title #///
navigationController?.navigationBar.isHidden = false
navigationItem.title = vEditScreenName
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor : ColorTheme.white, NSAttributedString.Key.font: UIFont(descriptor: UIFontDescriptor(name: "Poppins-Regular", size: 20), size: 20)]
self.view.backgroundColor = ColorTheme.vidBoomBrandPurple
///# Navigation bar back button #///
let btnBack = UIButton(frame: CGRect(x: 0, y: 0, width: 25, height: 25))
btnBack.setImage(UIImage(named: "icon_back_arrow"), for: .normal)
btnBack.addTarget(self, action: #selector(_handlebtnBackTapped), for: .touchUpInside)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: btnBack)
///# Navigation bar explore button #///
let btnExport = UIButton(frame: CGRect(x: 0, y: 0, width: 80, height: 25))
btnExport.setTitle("Export", for: .normal)
btnExport.backgroundColor = .orange
btnExport.layer.cornerRadius = 18
btnExport.addTarget(self, action: #selector(_handleBtnExportTapped), for: .touchUpInside)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: btnExport)
}
// --------------------------------------------------------
// MARK:- UIBarButtonItem Action
// --------------------------------------------------------
#objc fileprivate func _handlebtnBackTapped() {
self.player.stop()
navigationController?.popViewController(animated: true)
navigationController?.navigationBar.isHidden = true
}
#objc func _handleBtnExportTapped(){
self.player.stop()
let svc = ShareVideoVC(nibName: "ShareVideoVC", bundle: nil)
svc.videoString = videoString
saveFile()
self.navigationController?.pushViewController(svc, animated: true)
}
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