iOS 10 - How to create custom Navigation bar large title - ios

My app deployment target version is iOS 10. and I added navigation bar large title in my app. it is working as per need in above iOS 10. if I try to test this in iOS 10 it is not working. So I am trying to create custom Navigation bar large tile for iOS 10 as well. but i don't know how to achieve this. please guide me. Thanks Advance
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
} else {
// need to add here as well
}

In case somebody needs this. Here is how I did it. For me, this is better than the default, because it supports whatever customization you may want from large title (ex. multiline)
In my case my layout looks like this. You can have however you want, but make sure title is not inside of the table view / scroll view.
view
view
large title label
view (this view will stick on top)
view
table view
view
view
In this case, I have scrollViewDidScroll delegate, which checks the scrollView content offset to change the titleLabels top constraint. For me top constraint is 16. Change it to whatever you want to have
extension YourViewController: UITableViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let titleHeight = titleLabel.bounds.height
if (scrollView.contentOffset.y <= 0) {
// Title is fully visible - table view is at the top
titleLabelTopConstraint.constant = 16
isLargeTitleHidden = false
} else if (scrollView.contentOffset.y > (titleHeight + 16)){
// Title is not visible at all. Table view is at an unknown position but it is not top
titleLabelTopConstraint.constant = -titleHeight
isLargeTitleHidden = true
} else {
// Title is kind of visible. Not fully hidden or shown.
titleLabelTopConstraint.constant = -scrollView.contentOffset.y + 16
isLargeTitleHidden = false
}
} }
I also have the isLargeTitleHidden to update the nav
var isLargeTitleHidden: Bool = false {
didSet{
if (oldValue != isLargeTitleHidden){
updateNavBar()
}
}
}
func updateNavBar(){
let fadeTextAnimation = CATransition()
fadeTextAnimation.duration = 0.2
fadeTextAnimation.type = CATransitionType.fade
navigationController?.navigationBar.layer.add(fadeTextAnimation, forKey: "fadeText")
if isLargeTitleHidden {
navigationItem.title = titleLabel.text
} else {
navigationItem.title = ""
}
}

NavigationBar have a titleView object where the title is. You can customize a label to go there however you want and make navigationBar.titleView = yourLabel or make a custom UIView all the same.

Related

Get the default shrunk and expanded height of large title navigation bar

I have enabled large titles for the navigation bar with:
navigationController?.navigationBar.prefersLargeTitles = true
This makes the navigation bar start with an expanded height, and shrink as the user scrolls down.
Now, I want to add a subview inside the navigation bar that resizes, based on how tall the navigation bar is. To do this, I will need to get both the maximum and minimum height of the navigation bar, so I can calculate the fraction of how much it's expanded.
I can get the current height of the navigation bar like this:
guard let height = navigationController?.navigationBar.frame.height else { return }
print("Navigation height: \(height)")
I'm calling this inside scrollViewDidScroll, and as I'm scrolling, it seems that the expanded height is around 96 and the shrunk height is around 44. However, I don't want to hardcode values.
iPhone 12
Expanded (96.33)
Shrunk (44)
iPhone 8
Expanded (96.5)
Shrunk (44)
I am also only able to get these values when the user physically scrolls up and down, which won't work in production. And even if I forced the user to scroll, it's still too late, because I need to know both heights in advance so I can insert my resizing subview.
I want to get these values, but without hardcoding or scrolling
Is there any way I can get the height of both the shrunk and expanded navigation bar?
Came across my own question a year later. The other answer didn't work, so I used the view hierarchy.
It seems that the shrunk appearance is embedded in a class called _UINavigationBarContentView. Since this is a private class, I can't directly access it. But, its y origin is 0 and it has a UILabel inside it. That's all I need to know!
extension UINavigationBar {
func getCompactHeight() -> CGFloat {
/// Loop through the navigation bar's subviews.
for subview in subviews {
/// Check if the subview is pinned to the top (compact bar) and contains a title label
if subview.frame.origin.y == 0 && subview.subviews.contains(where: { $0 is UILabel }) {
return subview.bounds.height
}
}
return 0
}
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Navigation"
if
let navigationBar = navigationController?.navigationBar,
let window = UIApplication.shared.keyWindow
{
navigationBar.prefersLargeTitles = true /// Enable large titles.
let compactHeight = navigationBar.getCompactHeight() // 44 on iPhone 11
let statusBarHeight = window.safeAreaInsets.top // 44 on iPhone 11
let navigationBarHeight = compactHeight + statusBarHeight
print(navigationBarHeight) // Result: 88.0
}
}
The drawback of this answer is if Apple changes UINavigationBar's internals, it might not work. Good enough for me though.
Using following extension u can get extra height
extension UINavigationBar
{
var largeTitleHeight: CGFloat {
let maxSize = self.subviews
.filter { $0.frame.origin.y > 0 }
.max { $0.frame.origin.y < $1.frame.origin.y }
.map { $0.frame.size }
return maxSize?.height ?? 0
}
}
And I said earlier u can get extended height by following
guard let height = navigationController?.navigationBar.frame.maxY else { return }
print("Navigation height: \(height)")
let window = UIApplication.shared.keyWindow
let topPadding = window?.safeAreaInsets.top
let extendedHeight = height - topPadding
You can get shrunk height by subtracting difference from extended height
guard let difference = navigationController?.navigationBar.lagreTitleHeight else {return}
let shrunkHeight = extendedHeight - difference

why UITabbarController is going too bottom

I have Implemented top items a view with UICollectionview and UIPagecontroller to get android like pagetabs.
For each menu there is a container ViewController but in one of them need UITabBarController.
Why UITabBarController is going down.
I have tried moving menuVIew(Contacts,Recents,etc) up but still did not work.
For the top view, use UISegmentedControl, and make background color, same as border color.
For the tab bar, you mean that it is covered by home indicator?
Turn on Use Safe Area Layout Guides, for this UITabController, and you should be good to go.
let tabBar = UITabBar()
override func viewDidLoad() {
super.viewDidLoad()
addTabbar()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
addHeightConstraintToTabbar()
}
func addTabbar() -> Void {
self.view.addSubview(tabBar)
tabBar.translatesAutoresizingMaskIntoConstraints = false
tabBar.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
tabBar.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
tabBar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
let item1 = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.bookmarks, tag: 1)
let item2 = UITabBarItem(tabBarSystemItem: UITabBarSystemItem.contacts, tag: 2)
tabBar.items = [item1, item2]
self.view.bringSubview(toFront: tabBar)
}
func addHeightConstraintToTabbar() -> Void {
let heightConstant:CGFloat = self.view.safeAreaInsets.bottom + 49.0
tabBar.heightAnchor.constraint(equalToConstant: heightConstant).isActive = true
}
Result:
I have faced this issue before for iPhone X. I found a weird solution of this as below:
Change the bottom constraint from TabBar to Safe Area not the Super view and change the constraint value to 1 as shown in the below image.
This works on both The normal devices and devices with notch display. Check out the screenshots :
Hope this helps you.

TapGesture doesn't work under tabBar

I have a "classic app" with 3 ViewController and a tabBar that I use to change ViewController.
On my first ViewController, I have a button that display a UIView on all the screen, so I hide tabBar with this setTabBarVisible func :
extension UIViewController
{
func setTabBarVisible(visible: Bool, animated: Bool)
{
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (isTabBarVisible == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration: TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil
{
UIView.animate(withDuration: duration)
{
self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
return
}
}
}
var isTabBarVisible: Bool
{
return (self.tabBarController?.tabBar.frame.origin.y ?? 0) < self.view.frame.maxY
}
}
That's working, the tabBar is hidden and I see all my UIVIew.
The problem is, I have a UILabel at bottom of the UIView (at the place I usually display the tabBar), and I can't use my TapGesture on my UILabel, nothing is happening.
(if I display the label somewhere else the UITapGesture works good.)
I tried to set zPosition of my tabBar to 0 and zPosition of my UIView to 1 but that's doesn't work either.
How can I get my label clickable at bottom of my view?
Check UILabel.isUserInterration = enable
Make sure that when you hide tabbar, perticular view controller Under bottom bar property is unselect . See atteh imnage.
You can try with programatically also like
ViewController.edgesForExtendedLayout = UIRectEdge.top
make sure is true
label.isUserInteractionEnabled = true
Please refer this link may it help you.

Increase uitablview height while run time to content entire content

I created an app with Swift 3 and Xcode 8.1. I have a UITableview and a UIView above it that shows and hides by clicking on a button in it. When the UIView appears, the last cell of UITableview does not show completely.
I use following code in button:
func filterShowHide ()
{
if !isShown
{
filterImage.image = UIImage(named: "ME-Filter-re")
self.filterView.isHidden = false
self.tableViewTop.constant = 0
// tableViewHeight.constant = tableViewHeight.constant * 1.5
isShown = true
}
else
{
filterImage.image = UIImage(named: "ME-Filter")
self.tableViewTop.constant = -(self.HeaderView.frame.height) + self.filterBTN.frame.size.height
self.filterView.isHidden = true
isShown = false
// tableViewHeight.constant = tableViewHeight.constant / 1.5
}
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
For more details here's the screenshot of:
Before clicking and After clicking
How can I show the last cell completely?
I think the problem is your tableview height is still the same and it gets pushed down as you update the top constraint.
You should set the table view bottom constraint to bottom of its super view or you can update tableView.contentInset.top with the values you are using for self.tableViewTop.constant

TableView Showing Behind Tab Bar

I am updating my app to use iOS 7 and I'm having a problem with a table view. My tab bar is translucent. The problem is when I scroll to the bottom of my table view, part of the last cell is still behind the tab bar. I'd like to have a bit of space between the last cell and the tab bar. I could fix this by using an opaque tab bar instead, but I want to keep it translucent.
Try setting
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
Inside the tableview controller
Swift 4.x
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(0, 0, self.tabBarController!.tabBar.frame.height, 0)
self.yourTableView.contentInset = adjustForTabbarInsets
self.yourTableView.scrollIndicatorInsets = adjustForTabbarInsets
Check the screen shot
Check the under top Bar and Un-checke under Bottom Bar
SWIFT 3
put this inside viewDidLoad of your tableViewController:
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
self.automaticallyAdjustsScrollViewInsets = false
Swift 3.0
This is what worked for me. In your Custom ViewController:
override func viewDidLoad() {
super.viewDidLoad()
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(self.tabBarController!.tabBar.frame.height, 0, 0, 0);
//Where tableview is the IBOutlet for your storyboard tableview.
self.tableView.contentInset = adjustForTabbarInsets;
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets;
}
Not to sure I like the solution but it works for me.
With iOS 11 I have no issue, I simply use the following in viewDidLoad():
self.collectionView.bottomAnchor.constraint(self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
However on iOS 10 I need to hack my way like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let tabBarHeight: CGFloat = (self.parent?.tabBarController?.tabBar.frame.size.height)!
if #available(iOS 11.0, *) {
} else {
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -tabBarHeight).isActive = true
}
}
This is working for me
override func viewDidLoad() {
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
}
If any view shows behind a UITabBar you can grab the bottomLayoutGuide and make adjustments at runtime. What I do is have a BaseViewController that all my view controllers inherit from. Then if the tab bar is visible we adjust the view like so:
import UIKit
class BaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
//Ensures that views are not underneath the tab bar
if tabBarController?.tabBar.hidden == false {
var viewBounds = self.view.bounds;
var bottomBarOffset = self.bottomLayoutGuide.length;
self.view.frame = CGRectMake(0, 0, viewBounds.width, viewBounds.height - bottomBarOffset)
}
}
}
Since I don't use storyboards (where you can click a checkbox in IB to fix this problem), this has been the best solution I have found.
It is really hard to resolve the issue without detail information or actual codes. I have similar issue of tabview behind UItabBar in my project. The solutions offered here do not work in my case. After exploring my codes, I found a solution for my case.
Here is brief explanation of my case. I have a UItabBar in main view with two tab buttons. In one tab view, there is table view. If user taps on a row, a detail view is presented by using navigation controller. In the detail view, the tab bar is hidden, and a toolbar is showing at the bottom.
In order to bring tab bar back and hide the toolbar when the main view is brought back, I have to explicitly show tab bar and hide toolbar in the event of viewWillAppear:
class myMainViewController: UITableViewController {
private var tabBarHidden: Bool? = {
didSet {
self.tabBarController?.tabBar.isHidden = tabBarIsHidden ?? true
}
}
private var toolBarIsHidden: Bool? {
didSet {
let hidden = toolBarIsHidden ?? true
self.navigationController?.toolbar.isHidden = hidden
self.navigationController?.setToolbarHidden(hidden, animated: true)
}
}
...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarIsHidden = false
self.toolBarIsHidden = true
}
...
}
I finally realize that the visibility of bar at the bottom is set in the event of viewWillAppear. At that time, the tableView or scroll view's content insets are set already based on no bar at the bottom. That's why my tableView is behind the bottom bar.
The solution I found is to reset content insets in the event of viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
// In the event of viewWillAppear, visibilities of tool bar and tab bar are set or changed,
// The following codes resets scroll view's content insets for tableview
let topInset = self.navigationController!.navigationBar.frame.origin.y +
self.navigationController!.navigationBar.frame.height
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(
topInset, 0,
self.tabBarController!.tabBar.frame.height, 0)
self.tableView.contentInset = adjustForTabbarInsets
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets
}
The best approch would be to Embed TabBarController to your ViewController (Editor -> Embed In -> TabBar Controller)and set the bottom of the tableview to be bottom of safe area of viewcontroller. The other ways wont be as perfect as this one.
You need to adjust the height of the table view. Just leave 49px at the bottom, as the tabbar height is 49 px. Adjust the height of table view so that it leaves 49px space below it.

Resources