IOS 11では、sizeThatFits
メソッドはUINavigationBar
サブクラスから呼び出されません。 UINavigationBar
のフレームを変更すると、グリッチと誤ったインセットが発生します。だから、今navbarの高さをカスタマイズする方法はありますか?
Apple開発者によると( here 、 here 、および here を参照)、iOS 11でのナビゲーションバーの高さの変更はサポートされていません。 ここ ナビゲーションバーの下(ただしその外側)にビューを表示するなどの回避策を実行し、ナビゲーションバーの境界線を削除することを提案します。その結果、ストーリーボードにこれが表示されます。
デバイスでは次のようになります。
これで、他の回答で提案された回避策を実行できます:UINavigationBar
のカスタムサブクラスを作成し、それにカスタムの大きなサブビューを追加し、sizeThatFits
とlayoutSubviews
をオーバーライドしてから、ナビゲーションのトップコントローラーのadditionalSafeAreaInsets.top
を差分customHeight - 44px
に設定しますが、視覚的にはすべてが完璧に見えますが、ビューは依然としてデフォルトの44ピクセルです。私はsetFrame
をオーバーライドしようとしませんでしたが、おそらく動作します。ただし、Apple開発者が上記のリンクのいずれかで書いたように: "...どちらもナビゲーションのフレームを変更しません[サポート] UINavigationControllerが所有するバー(Navigation Controllerは、それが適切であると判断すると、フレームの変更を喜んで踏みます)。 "
私の場合、上記の回避策により、ビューは次のようになりました(境界線を表示するデバッグビュー)。
ご覧のとおり、視覚的な外観は非常に良好で、additionalSafeAreaInsets
がコンテンツを正しくプッシュし、大きなナビゲーションバーが表示されますが、このバーにはカスタムボタンがあり、標準の44ピクセルのナビゲーションバーの下にある領域のみがクリック可能(画像の緑色の領域)。標準のナビゲーションバーの高さより下のタッチは、カスタムサブビューに到達しないため、ナビゲーションバー自体のサイズを変更する必要がありますが、Appleの開発者はサポートされていないと言います。
2018年1月7日更新
このコードはXCode 9.2、iOS 11.2をサポートしています
同じ問題がありました。以下は私の解決策です。私は高さのサイズが66だと仮定します。
それがあなたを助けるならば、私の答えを選んでください。
CINavgationBar.Swiftを作成します
import UIKit
@IBDesignable
class CINavigationBar: UINavigationBar {
//set NavigationBar's height
@IBInspectable var customHeight : CGFloat = 66
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: customHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
print("It called")
self.tintColor = .black
self.backgroundColor = .red
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("UIBarBackground") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight)
subview.backgroundColor = .green
subview.sizeToFit()
}
stringFromClass = NSStringFromClass(subview.classForCoder)
//Can't set height of the UINavigationBarContentView
if stringFromClass.contains("UINavigationBarContentView") {
//Set Center Y
let centerY = (customHeight - subview.frame.height) / 2.0
subview.frame = CGRect(x: 0, y: centerY, width: self.frame.width, height: subview.frame.height)
subview.backgroundColor = .yellow
subview.sizeToFit()
}
}
}
}
ストーリーボードを設定する
カスタムNavigationBarクラスを設定します
TestViewの追加+ SafeAreaの設定
ViewController.Swift
import UIKit
class ViewController: UIViewController {
var navbar : UINavigationBar!
@IBOutlet weak var testView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//update NavigationBar's frame
self.navigationController?.navigationBar.sizeToFit()
print("NavigationBar Frame : \(String(describing: self.navigationController!.navigationBar.frame))")
}
//Hide Statusbar
override var prefersStatusBarHidden: Bool {
return true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
//Important!
if #available(iOS 11.0, *) {
//Default NavigationBar Height is 44. Custom NavigationBar Height is 66. So We should set additionalSafeAreaInsets to 66-44 = 22
self.additionalSafeAreaInsets.top = 22
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
SecondViewController.Swift
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create BackButton
var backButton: UIBarButtonItem!
let backImage = imageFromText("Back", font: UIFont.systemFont(ofSize: 16), maxWidth: 1000, color:UIColor.white)
backButton = UIBarButtonItem(image: backImage, style: UIBarButtonItemStyle.plain, target: self, action: #selector(SecondViewController.back(_:)))
self.navigationItem.leftBarButtonItem = backButton
self.navigationItem.leftBarButtonItem?.setBackgroundVerticalPositionAdjustment(-10, for: UIBarMetrics.default)
}
override var prefersStatusBarHidden: Bool {
return true
}
@objc func back(_ sender: UITabBarItem){
self.navigationController?.popViewController(animated: true)
}
//Helper Function : Get String CGSize
func sizeOfAttributeString(_ str: NSAttributedString, maxWidth: CGFloat) -> CGSize {
let size = str.boundingRect(with: CGSize(width: maxWidth, height: 1000), options:(NSStringDrawingOptions.usesLineFragmentOrigin), context:nil).size
return size
}
//Helper Function : Convert String to UIImage
func imageFromText(_ text:NSString, font:UIFont, maxWidth:CGFloat, color:UIColor) -> UIImage
{
let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = NSLineBreakMode.byWordWrapping
paragraph.alignment = .center // potentially this can be an input param too, but i guess in most use cases we want center align
let attributedString = NSAttributedString(string: text as String, attributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.paragraphStyle:paragraph])
let size = sizeOfAttributeString(attributedString, maxWidth: maxWidth)
UIGraphicsBeginImageContextWithOptions(size, false , 0.0)
attributedString.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
黄色はbarbackgroundViewです。黒い不透明度はBarContentViewです。
そして、BarContentViewのbackgroundColorを削除しました。
それでおしまい。
追加:この問題はiOS 11 beta 6で解決されたため、以下のコードは役に立たない^ _ ^
元の回答:
以下のコードで解決しました:
(statusBarの非表示がtrueであるかどうかに関係なく、常にnavigationBar.height + statusBar.height == 64が必要です)
@implementation P1AlwaysBigNavigationBar
- (CGSize)sizeThatFits:(CGSize)size {
CGSize sizeThatFit = [super sizeThatFits:size];
if ([UIApplication sharedApplication].isStatusBarHidden) {
if (sizeThatFit.height < 64.f) {
sizeThatFit.height = 64.f;
}
}
return sizeThatFit;
}
- (void)setFrame:(CGRect)frame {
if ([UIApplication sharedApplication].isStatusBarHidden) {
frame.size.height = 64;
}
[super setFrame:frame];
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (![UIApplication sharedApplication].isStatusBarHidden) {
return;
}
for (UIView *subview in self.subviews) {
NSString* subViewClassName = NSStringFromClass([subview class]);
if ([subViewClassName containsString:@"UIBarBackground"]) {
subview.frame = self.bounds;
}else if ([subViewClassName containsString:@"UINavigationBarContentView"]) {
if (subview.height < 64) {
subview.y = 64 - subview.height;
}else {
subview.y = 0;
}
}
}
}
@end
これは私のために働く:
- (CGSize)sizeThatFits:(CGSize)size {
CGSize sizeThatFit = [super sizeThatFits:size];
if ([UIApplication sharedApplication].isStatusBarHidden) {
if (sizeThatFit.height < 64.f) {
sizeThatFit.height = 64.f;
}
}
return sizeThatFit;
}
- (void)setFrame:(CGRect)frame {
if ([UIApplication sharedApplication].isStatusBarHidden) {
frame.size.height = 64;
}
[super setFrame:frame];
}
- (void)layoutSubviews
{
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"BarBackground"]) {
CGRect subViewFrame = subview.frame;
subViewFrame.Origin.y = 0;
subViewFrame.size.height = 64;
[subview setFrame: subViewFrame];
}
if ([NSStringFromClass([subview class]) containsString:@"BarContentView"]) {
CGRect subViewFrame = subview.frame;
subViewFrame.Origin.y = 20;
subViewFrame.size.height = 44;
[subview setFrame: subViewFrame];
}
}
}
Swift 4で簡略化されました。
class CustomNavigationBar : UINavigationBar {
private let hiddenStatusBar: Bool
// MARK: Init
init(hiddenStatusBar: Bool = false) {
self.hiddenStatusBar = hiddenStatusBar
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Overrides
override func layoutSubviews() {
super.layoutSubviews()
if #available(iOS 11.0, *) {
for subview in self.subviews {
let stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarBackground") {
subview.frame = self.bounds
} else if stringFromClass.contains("BarContentView") {
let statusBarHeight = self.hiddenStatusBar ? 0 : UIApplication.shared.statusBarFrame.height
subview.frame.Origin.y = statusBarHeight
subview.frame.size.height = self.bounds.height - statusBarHeight
}
}
}
}
}
-layoutSubviews
と-setFrame:
をオーバーライドするとともに、ナビゲーションバーのサイズを変更してコンテンツを非表示にしたくない場合は、新しく追加されたUIViewControllerのadditionalSafereaInsets
プロパティ( Apple Documentation )をチェックアウトする必要があります。
ベータ4では修正されていますが、ナビゲーションバーの背景画像は実際のビューに合わせて拡大縮小されていないようです(ビュー階層ビューアーで確認することで確認できます)。現時点での回避策は、カスタムlayoutSubviews
のUINavigationBar
をオーバーライドしてから、次のコードを使用することです。
- (void)layoutSubviews
{
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"BarBackground"]) {
CGRect subViewFrame = subview.frame;
subViewFrame.Origin.y = -20;
subViewFrame.size.height = CUSTOM_FIXED_HEIGHT+20;
[subview setFrame: subViewFrame];
}
}
}
お気づきのように、実際にはバーの背景にはステータスバーの後ろに表示されるように-20
のオフセットがあるため、上記の計算ではそれが追加されます。
xcode 9 Beta 6では、まだ問題があります。バーは常に44ピクセルの高さに見え、ステータスバーの下にプッシュされます。
それを解決するために、@ strangetimesコードでサブクラスを作成しました(Swiftで)
class NavigationBar: UINavigationBar {
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
print("--------- \(stringFromClass)")
if stringFromClass.contains("BarBackground") {
subview.frame.Origin.y = -20
subview.frame.size.height = 64
}
}
}
}
ステータスバーよりも下にバーを配置します
let newNavigationBar = NavigationBar(frame: CGRect(Origin: CGPoint(x: 0,
y: 20),
size: CGSize(width: view.frame.width,
height: 64)
)
)
これは私が使用するものです。 UISearchBar
をタイトルまたはバーコンテンツのサイズを変更する他のビューとして使用する場合、通常のコンテンツ(44.0 px)で機能します。それに応じて値を更新する必要があります。ある時点でブレーキがかかる可能性があるため、これは自己の責任において使用してください。
これは、高さ90.0ピクセルのハードコードされたnavbarであり、iOS 11と古いバージョンの両方で動作します。同じように見えるように、iOS 11より前の場合は、UIBarButtonItem
にいくつかのインセットを追加する必要があります。
class NavBar: UINavigationBar {
override init(frame: CGRect) {
super.init(frame: frame)
if #available(iOS 11, *) {
translatesAutoresizingMaskIntoConstraints = false
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: 70.0)
}
override func layoutSubviews() {
super.layoutSubviews()
guard #available(iOS 11, *) else {
return
}
frame = CGRect(x: frame.Origin.x, y: 0, width: frame.size.width, height: 90)
if let parent = superview {
parent.layoutIfNeeded()
for view in parent.subviews {
let stringFromClass = NSStringFromClass(view.classForCoder)
if stringFromClass.contains("NavigationTransition") {
view.frame = CGRect(x: view.frame.Origin.x, y: frame.size.height - 64, width: view.frame.size.width, height: parent.bounds.size.height - frame.size.height + 4)
}
}
}
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarBackground") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: 90)
subview.backgroundColor = .yellow
}
stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarContent") {
subview.frame = CGRect(x: subview.frame.Origin.x, y: 40, width: subview.frame.width, height: subview.frame.height)
}
}
}
}
そして、次のようにUINavigationController
サブクラスに追加します。
class CustomBarNavigationViewController: UINavigationController {
init() {
super.init(navigationBarClass: NavBar.self, toolbarClass: nil)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
override init(rootViewController: UIViewController) {
super.init(navigationBarClass: NavBar.self, toolbarClass: nil)
self.viewControllers = [rootViewController]
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
UINavigationBarをサブクラス化し、sizeThatFitsを使用して高さをオーバーライドすることにより、デフォルトのナビゲーションコントロールの上にステータスアイコンの行を追加できるように、ナビゲーションバーの高さを2倍にしました。幸いなことに、これには同じ効果があり、副作用が少ないシンプルなものです。 iOS 8〜11でテストしました。これをView Controllerに追加します。
- (void)viewDidLoad {
[super viewDidLoad];
if (self.navigationController) {
self.navigationItem.Prompt = @" "; // this adds empty space on top
}
}
これは通常のナビゲーションバーに適しています。 LargeTitleを使用している場合、titleViewのサイズは44ポイントの固定高さにならないため、これはうまく機能しません。ただし、通常のビューではこれで十分です。
@frangulyanのように、AppleはnavBarの下にビューを追加し、細い線(影の画像)を非表示にすることを提案しました。これが私が以下で思いついたものです。 uiviewをnavigationItemのtitleViewに追加し、そのuiview内にimageViewを追加しました。細い線(影の画像)を削除しました。追加したuiviewは、 navBarとまったく同じ色 です。そのビュー内にuiLabelを追加しました。
これが3D画像です。拡張ビューは、navBarの下のusernameLabelの背後にあります。灰色で、その下に細い線があります。 collectionViewまたは薄いseparatorLineの下にあるものを固定します。
コードの各行の上に9つのステップが説明されています。
class ExtendedNavController: UIViewController {
fileprivate let extendedView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
return view
}()
fileprivate let separatorLine: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .gray
return view
}()
fileprivate let usernameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 14)
label.text = "username goes here"
label.textAlignment = .center
label.lineBreakMode = .byTruncatingTail
label.numberOfLines = 1
return label
}()
fileprivate let myTitleView: UIView = {
let view = UIView()
view.backgroundColor = .white
return view
}()
fileprivate let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.clipsToBounds = true
imageView.backgroundColor = .darkGray
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// 1. the navBar's titleView has a height of 44, set myTitleView height and width both to 44
myTitleView.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
// 2. set myTitleView to the nav bar's titleView
navigationItem.titleView = myTitleView
// 3. get rid of the thin line (shadow Image) underneath the navigationBar
navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
navigationController?.navigationBar.layoutIfNeeded()
// 4. set the navigationBar's tint color to the color you want
navigationController?.navigationBar.barTintColor = UIColor(red: 249.0/255.0, green: 249.0/255.0, blue: 249.0/255.0, alpha: 1.0)
// 5. set extendedView's background color to the same exact color as the navBar's background color
extendedView.backgroundColor = UIColor(red: 249.0/255.0, green: 249.0/255.0, blue: 249.0/255.0, alpha: 1.0)
// 6. set your imageView to get pinned inside the titleView
setProfileImageViewAnchorsInsideMyTitleView()
// 7. set the extendedView's anchors directly underneath the navigation bar
setExtendedViewAndSeparatorLineAnchors()
// 8. set the usernameLabel's anchors inside the extendedView
setNameLabelAnchorsInsideTheExtendedView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
// 9. **Optional** If you want the shadow image to show on other view controllers when popping or pushing
navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
navigationController?.navigationBar.setValue(false, forKey: "hidesShadow")
navigationController?.navigationBar.layoutIfNeeded()
}
func setExtendedViewAndSeparatorLineAnchors() {
view.addSubview(extendedView)
view.addSubview(separatorLine)
extendedView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
extendedView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
extendedView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
extendedView.heightAnchor.constraint(equalToConstant: 29.5).isActive = true
separatorLine.topAnchor.constraint(equalTo: extendedView.bottomAnchor).isActive = true
separatorLine.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
separatorLine.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
separatorLine.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
}
func setProfileImageViewAnchorsInsideMyTitleView() {
myTitleView.addSubview(profileImageView)
profileImageView.topAnchor.constraint(equalTo: myTitleView.topAnchor).isActive = true
profileImageView.centerXAnchor.constraint(equalTo: myTitleView.centerXAnchor).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 44).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 44).isActive = true
// round the profileImageView
profileImageView.layoutIfNeeded()
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
}
func setNameLabelAnchorsInsideTheExtendedView() {
extendedView.addSubview(usernameLabel)
usernameLabel.topAnchor.constraint(equalTo: extendedView.topAnchor).isActive = true
usernameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
usernameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}