NavigationBarのタイトルにアイコンを追加する方法（改良版）

# 問題

前回の方法でNavigationBarを実装すると、次のような問題が発生しました。

1. Navigationで遷移する画面を２つ以上用意して、１つ目の画面でtitleViewを上書きすると、２つ目の画面に遷移したときに次の実行時エラーが発生します。

`Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot modify constraints for UINavigationBar managed by a controller'`

2. 同じく２つ目の画面でtitleViewを上書きすると、表示位置がずれます（左上ではみ出て表示されている）。

![custom_wronglayout.png](https://qiita-image-store.s3.amazonaws.com/0/75307/98625da2-e243-bf59-5b71-6939b5f34307.png)

# 解決方法

以下のURLで解決方法が示されています。

http://stackoverflow.com/questions/15285698/building-a-titleview-programmatically-with-constraints-or-generally-constructin

重要なのは以下の部分です。

1. Create a UIView subclass for custom titleView（意訳：UIViewを継承したサブクラスを作成してください）
2. In your subclass: a) Use auto layout for subviews but not for itself. Set translatesAutoresizingMaskIntoConstraints to false for subviews and true for titleView itself. b) Implement sizeThatFits(size: CGSize)（意訳：サブクラスの中では、 a) 子要素はautolayoutを有効にして、自分自身は無効にしてください。 b) sizeThatFitsメソッドを実装してください）
3. If your title can change call titleLabel.sizeToFit() and self.setNeedsUpdateConstraints() inside titleView's subclass after text changes（意訳：サブクラスの大きさが変わるような変更を行ったときは、self.setNeedsUpdateConstraintsメソッドを呼んでください）
4. In your ViewController call custom updateTitleView() and make sure to call titleView.sizeToFit() and navigationBar.setNeedsLayout() in there（意訳：NavigationControllerに属するViewControllerからは、titleViewのsizeToFitメソッドとnavigationBarのsetNeedsLayoutメソッドを呼んでください）

出来上がったソースは以下になります。

https://github.com/nakaken0629/CustomNavigationBar/tree/20151020

```swift:CustomTitleView.swift
import UIKit

class CustomTitleView : UIView {
    let titleLabel: UILabel
    let titleImage: UIImageView
    
    override init(frame: CGRect) {
        self.titleLabel = UILabel()
        self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.titleImage = UIImageView(image: UIImage(named: "SpeakerOn"))
        self.titleImage.translatesAutoresizingMaskIntoConstraints = false
        super.init(frame: frame)
        self.translatesAutoresizingMaskIntoConstraints = true
        self.addSubview(self.titleLabel)
        self.addSubview(self.titleImage)
    }
    
    convenience init() {
        self.init(frame: CGRectZero)
    }
    
    required init(coder aDecoder: NSCoder) {
        fatalError("CustomTitleView does not support NSCoding")
    }
    
    func setTitle(title: String) {
        self.titleLabel.text = title
        self.titleLabel.sizeToFit()
        setNeedsUpdateConstraints()
    }
    
    override func updateConstraints() {
        removeConstraints(self.constraints)
        self.addConstraints([
            NSLayoutConstraint(item: self.titleLabel, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1.0, constant: 0),
            NSLayoutConstraint(item: self.titleLabel, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1.0, constant: 0),
            NSLayoutConstraint(item: self.titleImage, attribute: .Left, relatedBy: .Equal, toItem: self.titleLabel, attribute: .Right, multiplier: 1.0, constant: 8.0),
            NSLayoutConstraint(item: self.titleImage, attribute: .CenterY, relatedBy: .Equal, toItem: self.titleLabel, attribute: .CenterY, multiplier: 1.0, constant: 0)])
        super.updateConstraints()
    }
    
    override func sizeThatFits(size: CGSize) -> CGSize {
        let width = CGRectGetWidth(self.titleLabel.bounds) + CGRectGetWidth(self.titleImage.bounds) + 8.0
        let height = max(CGRectGetHeight(self.titleLabel.bounds), CGRectGetHeight(self.titleImage.bounds))
        return CGSizeMake(width, height)
    }
}
```

```swift:CustomViewController.swift
import UIKit

class CustomViewController : UIViewController {
    private let titleView = CustomTitleView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.navigationItem.titleView = self.titleView
        updateTitleView("ここにタイトル")
    }
    
    private func updateTitleView(title: String) {
        self.titleView.setTitle(title)
        self.titleView.sizeToFit()
        self.navigationController?.navigationBar.setNeedsLayout()
    }
}
```