I have looked at all the similar questions I could find and I have not found a solution to this problem.
I am setting the titleView property like this in viewDidLoad:
self.navigationItem.titleView = label
The view controller is part of a navigation stack. When you tap on a row in in the previous view controller it pushes this controller onto the stack. Completely normal UINavigationController stuff.
As this view controller begins to animate in, the label appears at the origin (top left) and then stays there until the view controller finishes animating and then it jumps (no animation) to the proper position in the middle of the nav bar.
After tapping on the back button the title view animates out correctly, just like a normal title would.
Here is the code in viewDidLoad:
let label = UILabel(frame: CGRect.zero)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .red
label.text = "test"
label.sizeToFit()
self.navigationItem.titleView = label
Things I've tried:
Specify a frame: no change. Plus I don't want to specify a frame. I don't want to make assumptions about the height of the nav bar.
Moved it to viewWillAppear: no change.
Moved it to viewDidAppear: better but still not right. The label does not appear at all until the animation is complete and then it appears where it should, no animation it just appears. The correct behaviour is to animate in from right to left like a normal title would.
This is easily reproducible with the Master-Detail project template in Xcode. If you want to try it just add the code above to the top of configureView() in DetailViewController.swift. In that template the navigation item's title is hardcoded into the storyboard. You can easily remove it by searching for "Detail". Click the result that says Navigation Item: Title = "Detail" and then remove the string from the Inspector pane.
Update
#synndicate's suggestion does work perfectly for the UILabel example above. But my real problem is with a UIStackView. When I use a stack view following the approach #synndicate suggests I get yet another animation problem. This time the title view starts sliding in but animates all the way to the origin. When the animation is finished it snaps to where it should be.
Here's the code in prepare(for:sender:) as #synndicate suggests...
let label = UILabel(frame: CGRect.zero)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .red
label.text = "test"
let stackView = UIStackView(frame: CGRect.zero)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
stackView.sizeToFit()
controller.navigationItem.titleView = stackView
Any further suggestions?
Also, this is Xcode 8 and iOS 10.
Update 2
I have discovered that the stack view code above animates perfectly (just like an ordinary title would) in viewDidLoad for a view controller where the root of the nav hierarchy is a UINavigationController. The problem occurs when the root of the nav hierarchy is a UISplitViewController.
So I guess I could update my question to this...
How do I configure a UIStackView that will be set on the navigationItem's titleView property where the root of the navigation hierarchy is a UISplitViewController?
Are you able to move the code to before you push the view controller on the stack?
e.g. In MasterViewController.swift of the Master-Detail project
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
let label = UILabel()
label.textAlignment = .center
label.backgroundColor = .red
label.text = "test"
label.sizeToFit()
controller.navigationItem.titleView = label
}
}
}
If you want the stackView to appear on a specific view controller of the navigation stack, simply add it in the view controller
self.view.addSubview(stackView)
Related
In my app, the first ViewController appears with right navigation bar items. I want to show different bar items on right side in child VC which is appeared by pushing. The items in the first VC shows fine, but the second doesn't. The bar button shows for a second when the VC disappeared.
// The things the first VC did
navigationItem.setHidesBackButton(true, animated: true)
navigationController?.navigationBar.tintColor = .gray600
let stackView = UIStackView()
stackView.addArrangedSubviews(views: [registerButton, notificationButton])
stackView.spacing = 16
let rightBarButton = UIBarButtonItem(customView: stackView)
navigationController?.navigationBar.topItem?.rightBarButtonItem = rightBarButton
// The things the second did
navigationController?.navigationBar.tintColor = .gray600
navigationController?.navigationBar.topItem?.backButtonTitle = ""
navigationController?.navigationBar.backIndicatorImage = .back
navigationController?.navigationBar.backIndicatorTransitionMaskImage = .back
let rightBarButton = UIBarButtonItem(customView: editButton)
navigationController?.navigationBar.topItem?.rightBarButtonItem = rightBarButton
They did almost same things but the second doesn't work.
Here is the gif file i recorded. You can see Edit button for a second when the second VC disappeared. I tried to find the clues but i couldn't. Please check it and give me any comments. Thank you.
Delete the phrase navigationController?.navigationBar.topItem everywhere it appears, and never use it again, ever. It will totally break the navigation controller — as you yourself have demonstrated.
The only navigation item a view controller can talk to is its own navigationItem property. Thus the first vc is much more correct than the second vc, and so it works much better than the second vc.
you should not set a button and its title by using "topItem", instead you should set them by using navigationItem's rightBarButtonItem.
let rightBarButton = UIBarButtonItem(customView: editButton)
navigationItem.rightBarButtonItem = rightBarButton
let backButton = UIBarButtonItem(...)
navigationItem.leftBarButtonItem = backButton
I am currently working on a projekt in swift where I use a navigation controller to segue between different views. My projekts title is displayed in the navigation bar after the segue with "prefer large titles" on and it displays automatic. It all works as it should, but for some reason the title is not displayed the same way after the segue at the different views. When I segue to a viewcontroller with a scrollview the title is displayed like this to begin with
And when I segue to a tableviewcontroller the title is displayed like this
Do you know why it is displayed differently? I do not care how the title is displayed to begin with, just that it is in the same way every time. You can see the problem in this link
https://github.com/Rawchris/small-title
Here is the solution to your problem.. Thanks for sharing the repo.
This declaration is from your ViewController. In addition to this, as you are adding the components programmatically so i would suggest to mark translatesAutoresizingMaskIntoConstraints to be false
lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: .zero)
view.backgroundColor = .white
view.frame = CGRect(x: self.view.bounds.minX, y: CGFloat(0.0), width: self.view.bounds.width, height: self.view.bounds.height)
view.contentSize = contentViewSize
return view
}()
I have subclassed UITabBarController to allow for some customization specific to my app. It is the root view controller of my UIWindow and displays itself correctly on launch, and even shows the correct tab's view hierarchy as well.
The problem is with the selected tabbar item's tint color. Inside viewDidLoad of the custom tab bar controller subclass, I have set both the unselected and selected tint colors for the tab bar. See below:
override func viewDidLoad() {
super.viewDidLoad()
tabBar.tintColor = .tabBarItemActiveTint
tabBar.unselectedItemTintColor = .tabBarItemInactiveTint
tabBar.barTintColor = .tabBarBg
let dashboardVC = DashboardViewController.build()
let settingsVC = SettingsTableViewController.build()
let settingsNavC = UINavigationController(rootViewController: settingsVC)
settingsNavC.navigationBar.barStyle = .black
viewControllers = [dashboardVC, settingsNavC]
selectedViewController = dashboardVC
// Accessing the view property of each tab's root view controller forces
// the system to run "viewDidLoad" which will configure the tab icon and
// title in the tab bar.
let _ = dashboardVC.view
let _ = settingsVC.view
}
As you can see, the controller has its child view hierarchies set, and the views are loaded at the bottom so their respective viewDidLoad methods run where I have code that sets the tabBarItem. Here's an example from the dashboard view controller:
tabBarItem = UITabBarItem(title: "Dashboard", image: UIImage(named: Theme.dashboardTabBarIcon), tag: 0)
Everything about this works except for the selected icon and title. When the app launches I can see the tab bar, the first view hierarchy (the dashboard) is visible onscreen and the tabs all function properly. But the dashboard's icon and title are in an unselected state. I have to actually tap the tab bar icon to get the state to change such that it is selected.
Once you tap one of the tabs, the selected state works as normal. The issue is only on the first presentation of the tab bar.
Here is an image showing the initial state of the tab bar on launch. Notice the dashboard icon is not selected, even though it is the presented view controller.
UPDATE
Skaal's answer below solved the problem for me.
For future reference: the key difference between the code presented here in my question and his sample in the answer is that the tabBarItem is set in viewDidLoad of his custom TabBarController class. By contrast, that code was placed within the viewDidLoad method of each constituent view controller class in my project. There must be a timing issue of when things are called that causes the tint color to not be set in one scenario and work properly in the other.
Key Takeaway: If you set up a tab bar controller programmatically, be sure to set your tabBarItem properties early on to ensure tint colors work properly.
You can use :
selectedIndex = 0 // the index of your dashboardVC
instead of selectedViewController
EDIT - Here is a working sample of UITabBarController:
class TabBarController: UITabBarController {
private lazy var firstController: UIViewController = {
let controller = UIViewController()
controller.title = "First"
controller.view.backgroundColor = .lightGray
return controller
}()
private lazy var secondController: UIViewController = {
let controller = UIViewController()
controller.title = "Second"
controller.view.backgroundColor = .darkGray
return controller
}()
private var controllers: [UIViewController] {
return [firstController, secondController]
}
override func viewDidLoad() {
super.viewDidLoad()
tabBar.tintColor = .magenta
tabBar.unselectedItemTintColor = .white
tabBar.barTintColor = .black
firstController.tabBarItem = UITabBarItem(title: "First", image: UIImage(), tag: 0) // replace with your image
secondController.tabBarItem = UITabBarItem(title: "Second", image: UIImage(), tag: 1) // replace with your image
viewControllers = controllers
selectedViewController = firstController
}
}
I have a project where I have TableViewCells push to different View Controllers. In this project, I use custom navigation controllers. This is the code of the navigation controller in the first view controller:
//Design of Header
let nav_background = UIImage(named: "header_background")
navigationController?.navigationBar.setBackgroundImage(nav_background, for: .default)
navigationController?.navigationBar.layer.shadowOffset = CGSize(width: 0, height: 1)
navigationController?.navigationBar.layer.shadowOpacity = 0.16
navigationController?.navigationBar.layer.shadowRadius = 10
let account = UIImage(named: "header_account")
let imageView = UIImageView(image: account)
let blankView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.layer.shadowOffset = CGSize(width: -3, height: 3)
imageView.layer.shadowOpacity = 0.16
imageView.layer.shadowRadius = 10
self.navigationController?.navigationBar.topItem?.rightBarButtonItem?.customView = imageView
self.navigationController?.navigationBar.topItem?.leftBarButtonItem?.customView = blankView
The first view controller only displays the custom background and no other properties.
This is the code of the second view controller which is loaded when a cell in the TableView is selected:
//Design of Header
let account = UIImage(named: "header_account")
let accountView = UIImageView(image: account)
let back = UIImage(named: "header_backarrow")
let backView = UIImageView(image: back)
navigationItem.leftBarButtonItem?.customView = backView
navigationItem.rightBarButtonItem?.customView = accountView
Theoretically, everything should be replaced with the code written here and the titles listed in the View Controller's properties(This works for all view controllers except the first one.) I am taking all this directly off of the Apple webpage: https://developer.apple.com/documentation/uikit/uinavigationcontroller
Here are the exact paragraphs I am referencing:
Left Item:
If the new top-level view controller has a custom left bar button item, that item is displayed. To specify a custom left bar button item, set the leftBarButtonItem property of the view controller’s navigation item.
Middle Item:
If no custom title view is set, the navigation bar displays a label containing the view controller’s default title. The string for this label is usually obtained from the title property of the view controller itself. If you want to display a different title than the one associated with the view controller, set the title property of the view controller’s navigation item instead.
Right Item:
If the new top-level view controller has a custom right bar button item, that item is displayed. To specify a custom right bar button item, set the rightBarButtonItem property of the view controller’s navigation item.
Does anybody know how to fix this? Thanks.
Your code doesn't work because customView is nil in most cases.
If you want to set a navigation item to a UIImage, create a new UIBarButtonItem from the image, and then assign the bar button item.
For example, from a UIImage:
let accountItem = UIBarButtonItem(image: account, style: .plain, target: nil, action: nil)
self.navigationController?.navigationBar.topItem?.rightBarButtonItem = accountItem
To create one from a custom view, use:
let accountItem = UIBarButtonItem(image: imageView)
self.navigationController?.navigationBar.topItem?.rightBarButtonItem = accountItem
Please note that this can still fail if navigationController is nil or if topItem is nil.
I've got this collection view, and I want to be able to delete a cell by dragging it in the trash bar button on the right of the bar.
I don't understand how to make the button able to receive a drop,
if it was a UIView I can do
addInteraction(UIDropInteraction(..))
but since it is a button I can't act this way.
Someone can help me?
You can try creating a custom view and add interactions to it. And finally, add that to the rightBarButtonItem. Something on the lines of:
func viewDidLoadForBarButton() {
let customView = UIView() //Add your custom UIView() here
customView.sizeToFit()
let dropInteraction = UIDropInteraction(delegate: self)
customView.addInteraction(dropInteraction)
let barButton = UIBarButtonItem(customView: customView)
self.navigationItem.rightBarButtonItem = barButton
}
Let me know if it works.