お手軽に実装できるLargeNavigationBar

iOS11から追加されたLarge Navigation Barですが、あまり使われている場面を見かけませんが、個人的には好きなのと、実装が簡単なので紹介したいと思います。

## 使い方

### IB
Navigation Barを選択し、「Prefers Large　Titles」にチェックを入れる

<img width="854" alt="スクリーンショット 2017-12-20 12.51.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/186277/953de163-9e47-b259-96a7-e3ace10d2e04.png">

タイトルの編集も今まで通り、直感的に変更できます
<img width="660" alt="スクリーンショット 2017-12-20 13.00.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/186277/9964e886-5b07-d584-8afe-307846d051c0.png">

NavigationBarのタイトルの大きさの設定には３種類あり、

* Automatic：対応しているOSには自動で大きくなる
* Always：常に大きく表示する
* Never：従来のまま表示する


<img width="242" alt="スクリーンショット 2017-12-20 13.01.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/186277/c4594b8a-ef8b-1a71-6179-a615aef529da.png">


### Code

```
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
```

## 小技

### スクロールした際にタイトルを動的に動かす
![sample.gif](https://qiita-image-store.s3.amazonaws.com/0/186277/d05ea761-786f-d891-0fca-95e04e2aae08.gif)

IB上では通常のナビゲーションバーを設定し、Code側でprefersLargeTitlesを設定する

あとはTableViewを配置すればスクロールした際に動的に小さくなってくれます
```
navigationController?.navigationBar.prefersLargeTitles = true
```
<img width="856" alt="スクリーンショット 2017-12-20 14.25.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/186277/78aa32db-3452-5831-dac7-f9a593fe37f0.png">

### NavigationItemBarに画像を入れる
![navibar.gif](https://qiita-image-store.s3.amazonaws.com/0/186277/d19051be-f72a-d06f-d8d1-b7e66fec3b2b.gif)


これは記事があったので参考にして実装しました

ハック感がすごいですね。移動量で計算しているのでもっと簡単に実装できると良いのですが、、
参考 :
https://stackoverflow.com/questions/35243328/setting-navigation-bar-item-image-in-storyboard-does-not-work
https://blog.uptech.team/how-to-build-resizing-image-in-navigation-bar-with-large-title-8ba2e8bcb840

### 今回の実装内容
```ViewController.swift
import UIKit

class ViewController: UIViewController, UIScrollViewDelegate {
    
    @IBOutlet weak var tableview: UITableView!
    
    private let imageView = UIImageView(image: #imageLiteral(resourceName: "dog"))
    
    private struct Const {
        /// Image height/width for Large NavBar state
        static let ImageSizeForLargeState: CGFloat = 50
        /// 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
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        tableview.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
        setupUI()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    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)
            ])
    }
    
    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)
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard let height = navigationController?.navigationBar.frame.height else { return }
        moveAndResizeImage(for: height)
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 40
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
        
        cell.textLabel!.text = "sample"
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        performSegue(withIdentifier: "NextViewController", sender: nil)
    }
}

```