問題
前回の方法でNavigationBarを実装すると、次のような問題が発生しました。
- Navigationで遷移する画面を2つ以上用意して、1つ目の画面でtitleViewを上書きすると、2つ目の画面に遷移したときに次の実行時エラーが発生します。
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot modify constraints for UINavigationBar managed by a controller'
- 同じく2つ目の画面でtitleViewを上書きすると、表示位置がずれます(左上ではみ出て表示されている)。
解決方法
以下のURLで解決方法が示されています。
重要なのは以下の部分です。
- Create a UIView subclass for custom titleView(意訳:UIViewを継承したサブクラスを作成してください)
- 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メソッドを実装してください)
- If your title can change call titleLabel.sizeToFit() and self.setNeedsUpdateConstraints() inside titleView's subclass after text changes(意訳:サブクラスの大きさが変わるような変更を行ったときは、self.setNeedsUpdateConstraintsメソッドを呼んでください)
- In your ViewController call custom updateTitleView() and make sure to call titleView.sizeToFit() and navigationBar.setNeedsLayout() in there(意訳:NavigationControllerに属するViewControllerからは、titleViewのsizeToFitメソッドとnavigationBarのsetNeedsLayoutメソッドを呼んでください)
出来上がったソースは以下になります。
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)
}
}
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()
}
}