iOS 10 Imessage App Extension: How to Calculate the Height of the Extra Tall Navbar

iOS 10 iMessage app extension: how do i calculate the height of the extra tall navbar

It may help to have a constraint with the top layout guide like so:

view.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor).isActive = true

iMessage Extension - need to know the height of the iMessages header

I found the answer in this question. Here's the code that worked for me.

override func viewDidLayoutSubviews() {
self.recentQuotesTableView.contentInset = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, 0, 0)
}

It works on all devices, including iPad with split view.

Messages-like taller / standard navigation bar during push / pop

Very interesting problem. I spent some time to achieve something like this in the Messages app and that is what I've done.

Sample Image

Finally, I use this trick to animate navigationBar height during push/pop and also pop with swipe gesture.

UIView.beginAnimations(nil, context: nil)
self.frame = navFrame
UIView.commitAnimations()

Below you can see my implementation:

extension UINavigationBar {
func applyHeight(_ height: CGFloat, animated: Bool = true) {
var navFrame = self.frame
navFrame.size.height = height
if animated {
UIView.beginAnimations(nil, context: nil)
self.frame = navFrame
UIView.commitAnimations()
} else {
self.frame = navFrame
}
}
}

class ViewControllerA: UIViewController {

override func loadView() {
super.loadView()
title = "A"
view.backgroundColor = .blue
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "NEXT", style: .plain, target: self, action: #selector(self.showController))
navigationController?.navigationBar.isTranslucent = false
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}

func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
}

class ViewControllerB: UIViewController {

override func loadView() {
super.loadView()
title = "B"
view.backgroundColor = .red
}

override func viewWillAppear(_ animated: Bool) {
navigationController?.navigationBar.applyHeight(100)
super.viewWillAppear(animated)
}

override func willMove(toParentViewController parent: UIViewController?) {
if parent == nil { // here you know that back button was tapped
navigationController?.navigationBar.applyHeight(44)
}
super.willMove(toParentViewController: parent)
}
}

Things to improve

  • Title jumps to top

Jumping title is visible while you swipe to pop, but personally, I think this is a small problem :)

Hope it helps you, and maybe someone can improve this implementation. Of course, I will still try to figure out how to make this better :)
Here it's a github repository. Please use navigation_bar_height branch.

TopAnchor of viewController changin in iMessage Extension between presentation modes

You are setting up constraints on view but you have set translatesAutoresizingMaskIntoConstraints to true. The autoresizing mask constraints will likely conflict with the constraints you are adding, cause unexpected results. You should change to:

controller.view.translatesAutoresizingMaskIntoConstraints = false

Also rather than pinning to view.topAnchor, you should pin to the topLayoutGuide, which will take the top navigation bar into account.

controller.view.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true

Similarly,

controller.view.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true

Image for Navigation Bar with Large Title iOS 11

After several hours of coding, I finally managed to make it work. I also decided to write a detailed tutorial: link. Follow it in case you prefer very detailed instructions.

Demo:
Sample Image

Complete project on GitHub: link.

Here are 5 steps to accomplish it:

Step 1: Create an image

private let imageView = UIImageView(image: UIImage(named: "image_name"))

Step 2: Add Constants

/// WARNING: Change these constants according to your project's design
private struct Const {
/// Image height/width for Large NavBar state
static let ImageSizeForLargeState: CGFloat = 40
/// Margin from right anchor of safe area to right anchor of Image
static let ImageRightMargin: CGFloat = 16
/// Margin from bottom anchor of NavBar to bottom anchor of Image for Large NavBar state
static let ImageBottomMarginForLargeState: CGFloat = 12
/// Margin from bottom anchor of NavBar to bottom anchor of Image for Small NavBar state
static let ImageBottomMarginForSmallState: CGFloat = 6
/// Image height/width for Small NavBar state
static let ImageSizeForSmallState: CGFloat = 32
/// Height of NavBar for Small state. Usually it's just 44
static let NavBarHeightSmallState: CGFloat = 44
/// Height of NavBar for Large state. Usually it's just 96.5 but if you have a custom font for the title, please make sure to edit this value since it changes the height for Large state of NavBar
static let NavBarHeightLargeState: CGFloat = 96.5
}

Step 3: setup UI:

private func setupUI() {
navigationController?.navigationBar.prefersLargeTitles = true

title = "Large Title"

// Initial setup for image for Large NavBar state since the the screen always has Large NavBar once it gets opened
guard let navigationBar = self.navigationController?.navigationBar else { return }
navigationBar.addSubview(imageView)
imageView.layer.cornerRadius = Const.ImageSizeForLargeState / 2
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.rightAnchor.constraint(equalTo: navigationBar.rightAnchor,
constant: -Const.ImageRightMargin),
imageView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor,
constant: -Const.ImageBottomMarginForLargeState),
imageView.heightAnchor.constraint(equalToConstant: Const.ImageSizeForLargeState),
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
])
}

Step 4: create image resizing method

private func moveAndResizeImage(for height: CGFloat) {
let coeff: CGFloat = {
let delta = height - Const.NavBarHeightSmallState
let heightDifferenceBetweenStates = (Const.NavBarHeightLargeState - Const.NavBarHeightSmallState)
return delta / heightDifferenceBetweenStates
}()

let factor = Const.ImageSizeForSmallState / Const.ImageSizeForLargeState

let scale: CGFloat = {
let sizeAddendumFactor = coeff * (1.0 - factor)
return min(1.0, sizeAddendumFactor + factor)
}()

// Value of difference between icons for large and small states
let sizeDiff = Const.ImageSizeForLargeState * (1.0 - factor) // 8.0

let yTranslation: CGFloat = {
/// This value = 14. It equals to difference of 12 and 6 (bottom margin for large and small states). Also it adds 8.0 (size difference when the image gets smaller size)
let maxYTranslation = Const.ImageBottomMarginForLargeState - Const.ImageBottomMarginForSmallState + sizeDiff
return max(0, min(maxYTranslation, (maxYTranslation - coeff * (Const.ImageBottomMarginForSmallState + sizeDiff))))
}()

let xTranslation = max(0, sizeDiff - coeff * sizeDiff)

imageView.transform = CGAffineTransform.identity
.scaledBy(x: scale, y: scale)
.translatedBy(x: xTranslation, y: yTranslation)
}

Step 5:

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let height = navigationController?.navigationBar.frame.height else { return }
moveAndResizeImage(for: height)
}

Hope it's clear and helps you!
Please let me know in comments if you have any additional questions.

Adjust position of bar button item when using large titles with iOS 11

To solve my own problem, I just added a button as a subview of the navbar and set the right and bottom constraints to the navbar. The button will now move up and down when the navbar changes size. However, this requires the button to be removed in any view controllers that you show segue from this view controller. Thus, I added a tag of 1 to the button and removed it from its superview from the other view controller. This is the easiest way to solve it, and I found it the easiest method.

To setup the right button:

func setupNavBar() {

self.title = "Home"
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationBar.isTranslucent = false

let searchController = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = searchController

let rightButton = UIButton()
rightButton.setTitle("Right Button", for: .normal)
rightButton.setTitleColor(.purple, for: .normal)
rightButton.addTarget(self, action: #selector(rightButtonTapped(_:)), for: .touchUpInside)
navigationController?.navigationBar.addSubview(rightButton)
rightButton.tag = 1
rightButton.frame = CGRect(x: self.view.frame.width, y: 0, width: 120, height: 20)

let targetView = self.navigationController?.navigationBar

let trailingContraint = NSLayoutConstraint(item: rightButton, attribute:
.trailingMargin, relatedBy: .equal, toItem: targetView,
attribute: .trailingMargin, multiplier: 1.0, constant: -16)
let bottomConstraint = NSLayoutConstraint(item: rightButton, attribute: .bottom, relatedBy: .equal,
toItem: targetView, attribute: .bottom, multiplier: 1.0, constant: -6)
rightButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([trailingContraint, bottomConstraint])

}

To remove it from any show segued view controllers:

func removeRightButton(){
guard let subviews = self.navigationController?.navigationBar.subviews else{return}
for view in subviews{
if view.tag != 0{
view.removeFromSuperview()
}
}
}

Both functions are called in the viewWillAppear function



Related Topics



Leave a reply



Submit