iOS 11 탐색 모음 높이 사용자 지정
이제 iOS 11에서는 sizeThatFits
메서드가 UINavigationBar
하위 클래스 에서 호출되지 않습니다 . 프레임을 변경 UINavigationBar
하면 결함 및 잘못된 삽입 이 발생합니다. 이제 navbar 높이를 사용자 정의하는 방법에 대한 아이디어가 있습니까?
Apple 개발자 ( 여기 , 여기 및 여기 참조 ) 에 따르면 iOS 11에서 탐색 모음 높이 변경은 지원되지 않습니다. 여기 에서는 탐색 모음 아래에보기 (외부에 있음)를 표시 한 다음 탐색 모음 테두리를 제거하는 등의 해결 방법을 제안합니다. 결과적으로 스토리 보드에 다음이 표시됩니다.
장치에서 다음과 같이 보입니다.
이제 다른 답변에서 제안 된 해결 방법을 수행 할 수 있습니다.의 사용자 지정 하위 클래스를 만들고 여기에 사용자 UINavigationBar
지정 큰 하위보기를 추가 sizeThatFits
하고 및 재정의 layoutSubviews
한 다음 additionalSafeAreaInsets.top
탐색의 상위 컨트롤러에 대해 차이 customHeight - 44px
를 설정하지만 막대보기는 여전히 시각적으로 모든 것이 완벽하게 보일지라도 기본값은 44px입니다. 을 재정의하지 않았습니다 setFrame
.하지만 Apple 개발자가 위의 링크 중 하나에 썼 듯이 "... 그리고 어느 쪽도 UINavigationController (내비게이션)가 소유 한 탐색 모음의 프레임을 변경하지 않습니다. 컨트롤러는 적합하다고 판단 될 때마다 프레임 변경을 기꺼이 밟습니다. "
필자의 경우 위의 해결 방법은보기를 다음과 같이 만들었습니다 (테두리를 표시하는 디버그보기).
보시다시피 시각적 인 모양이 매우 좋고 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()
}
}
}
}
스토리 보드 설정
Custom 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 베타 6에서 문제가 해결되었으므로 아래 코드는 쓸모가 없습니다 ^ _ ^
원래 답변 :
아래 코드로 해결되었습니다.
(나는 항상 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
}
}
}
}
}
오버라이드 (override)과 함께 -layoutSubviews
하고 -setFrame:
당신이 새로 추가의 UIViewController의 체크 아웃해야 additionalSafereaInsets
부동산 ( 애플 문서를 당신이 당신의 내용을 숨기고 크기가 조정 된 탐색 모음을 원하지 않는 경우).
베타 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에서 여전히 문제가 있습니다. Bar는 항상 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.0px)에 대해 작동하므로 그에 따라 값을 업데이트해야합니다. 어느 시점에서 브레이크가 걸릴 수 있으므로 자신의 책임하에 이것을 사용하십시오.
이것은 90.0px 높이의 하드 코딩 된 navbar로, iOS 11 및 이전 버전 모두에서 작동합니다. UIBarButtonItem
iOS 11 이전의 경우 동일하게 보이 려면에 일부 삽입을 추가해야 할 수 있습니다 .
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")
}
}
이것은 일반 탐색 모음에서 잘 작동합니다. LargeTitle을 사용하는 경우 titleView 크기가 44 포인트의 고정 높이가 아니기 때문에 제대로 작동하지 않습니다. 그러나 일반적인 관점에서는 이것으로 충분합니다.
@frangulyan apple이 navBar 아래에보기를 추가하고가는 선 (그림자 이미지)을 숨기라고 제안한 것처럼. 이것이 제가 아래에서 생각 해낸 것입니다. navigationItem의 titleView에 uiview를 추가 한 다음 해당 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
}
}
UINavigationBar를 서브 클래 싱하고 sizeThatFits를 사용하여 높이를 재정 의하여 기본 탐색 컨트롤 위에 상태 아이콘 행을 추가 할 수 있도록 탐색 모음의 높이를 두 배로 늘 렸습니다. 다행히도 이것은 동일한 효과를 가지며 부작용이 적고 더 간단합니다. iOS 8에서 11까지 테스트했습니다. 뷰 컨트롤러에 다음을 입력합니다.
- (void)viewDidLoad {
[super viewDidLoad];
if (self.navigationController) {
self.navigationItem.prompt = @" "; // this adds empty space on top
}
}
참고 URL : https://stackoverflow.com/questions/44387285/ios-11-navigation-bar-height-customizing
'Development Tip' 카테고리의 다른 글
Sublime Text 2에 증가하는 숫자 열을 삽입하는 방법은 무엇입니까? (0) | 2020.12.01 |
---|---|
numpy 배열 목록을 단일 numpy 배열로 변환하는 방법은 무엇입니까? (0) | 2020.12.01 |
사용자 정의 태그에 대한 Eclipse "태그 라이브러리 설명자를 찾을 수 없음"(JSTL이 아님!) (0) | 2020.12.01 |
시뮬레이터에서 푸시 알림을 확인할 수 있습니까? (0) | 2020.12.01 |
두 DOM 요소가 동일한 지 확인하는 방법이 있습니까? (0) | 2020.12.01 |