Development Tip

iOS 11 탐색 모음 높이 사용자 지정

yourdevel 2020. 12. 1. 19:50
반응형

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()

            }
        }


    }


}

스토리 보드 설정

여기에 이미지 설명 입력

NavigationBar 클래스 설정

Custom NavigationBar 클래스 설정

TestView 추가

여기에 이미지 설명 입력

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 및 이전 버전 모두에서 작동합니다. UIBarButtonItemiOS 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

반응형