Development Tip

UISearchBar는 iOS 11에서 탐색 모음 높이를 높입니다.

yourdevel 2020. 10. 11. 11:24
반응형

UISearchBar는 iOS 11에서 탐색 모음 높이를 높입니다.


나는 UISearchBar내비게이션 바의 일부가 다음과 같습니다.

 let searchBar = UISearchBar()
 //some more configuration to the search bar
 .....
 navigationItem.titleView = searchBar

업데이트 후 iOS 11내 앱의 검색 창에 이상한 일이 발생했습니다. iOS 10전에 나는 나의 탐색 바처럼 보이는했다 :

여기에 이미지 설명 입력

이제 iOS 11내가 가지고 있습니다.

여기에 이미지 설명 입력

보시다시피 두 개의 검색 막대의 반올림에 차이가있어 나를 괴롭히지 않습니다. 문제는 검색 막대가 탐색 막대의 높이를 증가 시킨다는 것입니다. 그래서 다른 컨트롤러로 가면 이상하게 보입니다.

여기에 이미지 설명 입력

사실 이상한 검은 색 선의 높이에 현재 탐색 모음의 높이를 더한 것이 두 번째 그림에 표시된 탐색 모음의 높이와 같습니다.

검은 선을 제거하고 모든 뷰 컨트롤러에서 일관된 탐색 모음 높이를 갖는 방법에 대한 아이디어가 있습니까?


iOS 11의 검색 창에 높이 44 제약 조건을 추가 할 수 있습니다.

if #available(iOS 11.0, *) {
    searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
}

// 목표 -c

   if (@available(iOS 11.0, *)) {
      [searchBar.heightAnchor constraintEqualToConstant:44].active = YES;
    }

두 가지 경우에 iOS 11에서 SearchBar가있는 NavigationBar 아래에 검은 색 선이 표시됩니다.

  • UISearchBar로 ViewController에서 다른 ViewController를 푸시했을 때 여기에 이미지 설명 입력

  • UISearchBar로 ViewController를 닫았을 때 "오른쪽 끌기" 여기에 이미지 설명 입력

내 솔루션은 UISearchBar를 사용하여 내 ViewController에이 코드를 추가하는 것입니다.

-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [self.navigationController.view setNeedsLayout]; // force update layout
    [self.navigationController.view layoutIfNeeded]; // to fix height of the navigation bar
}

Swift 4 업데이트

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.view.setNeedsLayout() // force update layout
    navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}

나는 iOS 11 UISearchBar의 높이가 56과 같고 UINavigationBar는 자동 레이아웃을 사용하여 하위 뷰에 맞게 높이를 높입니다. iOS 11 이전과 같이 UISearchBar를 titleView로 사용하고 싶다면 UISearchBar를 사용자 정의보기에 포함하고이보기의 높이를 44로 설정 한 다음 navigationItem.titleView에 할당하는 것이 가장 좋은 방법임을 알았습니다.

class SearchBarContainerView: UIView {  

    let searchBar: UISearchBar  

    init(customSearchBar: UISearchBar) {  
        searchBar = customSearchBar  
        super.init(frame: CGRect.zero)  

        addSubview(searchBar)  
    }

    override convenience init(frame: CGRect) {  
        self.init(customSearchBar: UISearchBar())  
        self.frame = frame  
    }  

    required init?(coder aDecoder: NSCoder) {  
        fatalError("init(coder:) has not been implemented")  
    }  

    override func layoutSubviews() {  
        super.layoutSubviews()  
        searchBar.frame = bounds  
    }  
}  

class MyViewController: UIViewController {  

    func setupNavigationBar() {  
        let searchBar = UISearchBar()  
        let searchBarContainer = SearchBarContainerView(customSearchBar: searchBar)  
        searchBarContainer.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 44)  
        navigationItem.titleView = searchBarContainer  
    }  
} 

viewDidLoad의 "ACKNOWLEDGEMENTS"뷰 컨트롤러에서이 코드를 시도하십시오.

self.extendedLayoutIncludesOpaqueBars = true

Objective-C에서

if (@available(iOS 11.0, *)) {
        [self.searchBar.heightAnchor constraintLessThanOrEqualToConstant: 44].active = YES;
}              

모두 감사합니다! 마침내 해결책을 찾았습니다.

UISearchBar를 사용하여 ViewController에 다음 코드를 추가합니다.

  1. 첫 번째 단계: viewDidLoad
-(void)viewDidLoad
{
    [super viewDidLoad];
    self.extendedLayoutIncludesOpaqueBars = YES;
    ...
}
override func viewDidLoad() {
    super.viewDidLoad()
    self.extendedLayoutIncludesOpaqueBars = true
}
  1. 두번째 단계:viewWillDisappear
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
     // force update layout
    [self.navigationController.view setNeedsLayout]; 
    // to fix height of the navigation bar
    [self.navigationController.view layoutIfNeeded];  
}
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        navigationController?.view.setNeedsLayout() // force update layout
        navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
    }

편집 : @zgjie의 대답은이 문제에 대한 더 나은 솔루션입니다 : https://stackoverflow.com/a/46356265/1713123

이는 iOS 11에서 SearchBar의 기본 높이 값이 이전 iOS 버전에서 44 대신 56으로 변경 되었기 때문에 발생하는 것 같습니다.

지금은 searchBar 높이를 다시 44로 설정하여이 해결 방법을 적용했습니다.

let barFrame = searchController.searchBar.frame
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: barFrame.width, height: 44)    

또 다른 솔루션은 iOS 11의 navigationItem에서 새로운 searchController 속성을 사용할 수 있습니다 .

navigationItem.searchController = searchController

그러나 이렇게하면 탐색 제목 아래에 searchBar가 나타납니다.


navBar를 44로 유지하는 솔루션을 사용할 수 없었습니다. 그래서 하루가 걸렸지 만 드디어 바 높이를 변경하지 않고 바 중간에 버튼을 배치하지 않는 솔루션을 찾았습니다. 문제는 버튼이 수평 스택보기로 구성되어 높이 변경에 맞게 조정되지 않는 스택보기에 배치된다는 것입니다.

이것은 init에서 수행됩니다.

UIBarButtonItem *cancelButton;
if (@available(iOS 11.0, *)) {
    // For iOS11 creating custom button to accomadate the change of navbar + search bar being 56 points
    self.navBarCustomButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.navBarCustomButton setTitle:@"Cancel"];
    [self.navBarCustomButton addTarget:self action:@selector(cancelButtonTapped) forControlEvents:UIControlEventTouchUpInside];
    cancelButton = [[UIBarButtonItem alloc] initWithCustomView:self.navBarCustomButton];
} else {
    cancelButton = [[UIBarButtonItem alloc] initWithTitle:MagicLocalizedString(@"button.cancel", @"Cancel")
                                                                                         style:UIBarButtonItemStylePlain
                                                                                        target:self
                                                                                        action:@selector(cancelButtonTapped)];
}

viewWillApear에서 (또는보기가 탐색 스택에 추가 된 후 언제든지)

   if (@available(iOS 11.0, *)) {
        UIView *buttonsStackView = [navigationController.navigationBar subviewOfClass:[UIStackView class]];
        if (buttonsStackView ) {
            [buttonsStackView.centerYAnchor constraintEqualToAnchor:navigationController.navigationBar.centerYAnchor].active = YES;
            [self.navBarCustomButton.heightAnchor constraintEqualToAnchor:buttonsStackView.heightAnchor];
        }
    }

그리고 subviewOfClass는 UIView의 카테고리입니다.

- (__kindof UIView *)subviewOfClass:(Class)targetClass {
     // base case
     if ([self isKindOfClass:targetClass]) {
        return self;
     }

     // recursive
    for (UIView *subview in self.subviews) {
        UIView *dfsResult = [subview subviewOfClass:targetClass];

        if (dfsResult) {
           return dfsResult;
       }
   }
   return nil;
}

UISearchBar를 하위 클래스로 만들고 "intrinsicContentSize"를 재정의하기 만하면됩니다.

@implementation CJSearchBar
-(CGSize)intrinsicContentSize{
    CGSize s = [super intrinsicContentSize];
    s.height = 44;
    return s;
}
@end

여기에 이미지 설명 입력


제 경우에는 더 큰 UINavigationBar의 높이가 문제가되지 않았습니다. 왼쪽 및 오른쪽 막대 단추 항목을 다시 정렬해야했습니다. 그것이 내가 생각 해낸 해결책입니다.

- (void)iOS11FixNavigationItemsVerticalAlignment
{
    [self.navigationController.navigationBar layoutIfNeeded];

    NSString * currSysVer = [[UIDevice currentDevice] systemVersion];
    if ([currSysVer compare:@"11" options:NSNumericSearch] != NSOrderedAscending)
    {
        UIView * navigationBarContentView;
        for (UIView * subview in [self.navigationController.navigationBar subviews])
        {
            if ([subview isKindOfClass:NSClassFromString(@"_UINavigationBarContentView")])
            {
                navigationBarContentView = subview;
                break;
            }
        }

        if (navigationBarContentView)
        {
            for (UIView * subview in [navigationBarContentView subviews])
            {
                if (![subview isKindOfClass:NSClassFromString(@"_UIButtonBarStackView")]) continue;

                NSLayoutConstraint * topSpaceConstraint;
                NSLayoutConstraint * bottomSpaceConstraint;

                CGFloat topConstraintMultiplier = 1.0f;
                CGFloat bottomConstraintMultiplier = 1.0f;

                for (NSLayoutConstraint * constraint in navigationBarContentView.constraints)
                {
                    if (constraint.firstItem == subview && constraint.firstAttribute == NSLayoutAttributeTop)
                    {
                        topSpaceConstraint = constraint;
                        break;
                    }

                    if (constraint.secondItem == subview && constraint.secondAttribute == NSLayoutAttributeTop)
                    {
                        topConstraintMultiplier = -1.0f;
                        topSpaceConstraint = constraint;
                        break;
                    }
                }

                for (NSLayoutConstraint * constraint in navigationBarContentView.constraints)
                {
                    if (constraint.firstItem == subview && constraint.firstAttribute == NSLayoutAttributeBottom)
                    {
                        bottomSpaceConstraint = constraint;
                        break;
                    }

                    if (constraint.secondItem == subview && constraint.secondAttribute == NSLayoutAttributeBottom)
                    {
                        bottomConstraintMultiplier = -1.0f;
                        bottomSpaceConstraint = constraint;
                        break;
                    }
                }

                CGFloat contentViewHeight = navigationBarContentView.frame.size.height;
                CGFloat subviewHeight = subview.frame.size.height;
                topSpaceConstraint.constant = topConstraintMultiplier * (contentViewHeight - subviewHeight) / 2.0f;
                bottomSpaceConstraint.constant = bottomConstraintMultiplier * (contentViewHeight - subviewHeight) / 2.0f;
            }
        }
    }
}

기본적으로 막대 버튼 항목이 포함 된 스택 뷰를 검색 한 다음 해당 항목의 상단 및 하단 제약 조건 값을 변경합니다. 예, 그것은 먼지 해킹이며 다른 방법으로 문제를 해결할 수 있다면 사용하지 않는 것이 좋습니다.


//
//  Created by Sang Nguyen on 10/23/17.
//  Copyright © 2017 Sang. All rights reserved.
//

import Foundation
import UIKit

class CustomSearchBarView: UISearchBar {
    final let SearchBarHeight: CGFloat = 44
    final let SearchBarPaddingTop: CGFloat = 8
    override open func awakeFromNib() {
        super.awakeFromNib()
        self.setupUI()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
       // fatalError("init(coder:) has not been implemented")
    }
    func findTextfield()-> UITextField?{
        for view in self.subviews {
            if view is UITextField {
                return view as? UITextField
            } else {
                for textfield in view.subviews {
                    if textfield is UITextField {
                        return textfield as? UITextField
                    }
                }
            }
        }
        return nil;
    }
    func setupUI(){
        if #available(iOS 11.0, *) {
            self.translatesAutoresizingMaskIntoConstraints = false
            self.heightAnchor.constraint(equalToConstant: SearchBarHeight).isActive = true
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        if #available(iOS 11.0, *) {
            if let textfield = self.findTextfield() {
                textfield.frame = CGRect(x: textfield.frame.origin.x, y: SearchBarPaddingTop, width: textfield.frame.width, height: SearchBarHeight - SearchBarPaddingTop * 2)`enter code here`
                return
            }
        }
    }
}

모든 솔루션이 나를 위해 작동하지 않았으므로 뷰 컨트롤러를 밀기 전에 다음을 수행했습니다.

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    self.navigationItem.titleView = UIView()
}

되돌아 갈 때 검색 창을 표시하려면 :

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    self.navigationItem.titleView = UISearchBar()
}

Mai Mai의 솔루션이 실제로 사용 가능한 유일한 솔루션이라는 것을 알았습니다.
그러나 여전히 완벽하지는 않습니다.
장치를 회전 할 때 검색 창의 크기가 제대로 조정되지 않고 더 작은 크기로 유지됩니다.

나는 그것에 대한 해결책을 찾았습니다. 다음은 관련 부분에 주석이 달린 Objective C의 코드입니다.

// improvements in the search bar wrapper
@interface SearchBarWrapper : UIView
@property (nonatomic, strong) UISearchBar *searchBar;
- (instancetype)initWithSearchBar:(UISearchBar *)searchBar;
@end
@implementation SearchBarWrapper
- (instancetype)initWithSearchBar:(UISearchBar *)searchBar {
    // setting width to a large value fixes stretch-on-rotation
    self = [super initWithFrame:CGRectMake(0, 0, 4000, 44)];
    if (self) {
        self.searchBar = searchBar;
        [self addSubview:searchBar];
    }
    return self;
}
- (void)layoutSubviews {
    [super layoutSubviews];
    self.searchBar.frame = self.bounds;
}
// fixes width some cases of resizing while search is active
- (CGSize)sizeThatFits:(CGSize)size {
    return size;
}
@end

// then use it in your VC
@implementation MyViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.titleView = [[SearchBarWrapper alloc] initWithSearchBar:self.searchController.searchBar];
}
@end

이제 아직 알아 내지 못한 케이스가 하나 남아 있습니다. 재현하려면 다음을 수행하십시오
.-세로로 시작
-검색 필드 활성화
-가로로 회전
-오류 : 막대 크기가 조정되지 않음


검색 막대가 포함 된 맵 뷰 컨트롤러의 viewDidAppear에 제약 조건을 추가하여이 문제를 해결했습니다.

public override func viewDidAppear(_ animated: Bool) {
    if #available(iOS 11.0, *) {

        resultSearchController?.searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
        // searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
    }
}

안녕 사용하는 사람들에게 UISearchController다음의 부착 UISearchBar받는 navigationItem.titleView. 이 문제를 해결하기 위해 하루 중 4 ~ 5 시간을 보냈습니다. iOS 11+ 권장 접근 방식 searchController을 따르는 navigation.searchController것은 내 경우에 적합하지 않습니다. 이 searchController / searchBar가있는 화면에는 사용자 정의 인 backButton이 있습니다.

iOS 10, iOS 11 및 12에서 이것을 테스트했습니다. 다른 장치에서. 그냥해야 했어요. 이 악마를 해결하지 않고는 집에 갈 수 없습니다. 빡빡한 마감일을 감안할 때 오늘 할 수있는 가장 완벽한 방법입니다.

그래서 저는 제가 한 힘든 일을 공유하고 싶습니다. 모든 것을 원하는 곳에 넣는 것은 여러분에게 달려 있습니다 (예 : viewModel의 변수). 여기 간다:

내 첫 번째 화면 (예 :이 검색 컨트롤러가없는 홈 화면)에서는 viewDidLoad().

self.extendedLayoutIncludesOpaqueBars = true

두 번째 화면 인 searchController가있는 화면에서는 viewDidAppear.

func viewDidAppear (_ animated : Bool) {super.viewDidAppear (animated) 재정의

    let systemMajorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
    if systemMajorVersion < 12 {
        // Place the search bar in the navigation item's title view.
        self.navigationItem.titleView = self.searchController.searchBar
    }

    if systemMajorVersion >= 11 {

        self.extendedLayoutIncludesOpaqueBars = true

        UIView.animate(withDuration: 0.3) {
            self.navigationController?.navigationBar.setNeedsLayout()
            self.navigationController?.navigationBar.layoutIfNeeded()
        }

        self.tableView.contentInset = UIEdgeInsets(top: -40, left: 0, bottom: 0, right: 0)

        if self.viewHadAppeared {
            self.tableView.contentInset = .zero
        }
    }

    self.viewHadAppeared = true // this is set to false by default.
}

내 searchController의 선언은 다음과 같습니다.

lazy var searchController: UISearchController = {
    let searchController = UISearchController(searchResultsController: nil)
    searchController.hidesNavigationBarDuringPresentation = false
    searchController.dimsBackgroundDuringPresentation = false
    searchController.searchBar.textField?.backgroundColor = .lalaDarkWhiteColor
    searchController.searchBar.textField?.tintColor = .lalaDarkGray
    searchController.searchBar.backgroundColor = .white
    return searchController
}()

그래서 언젠가 누군가에게 도움이되기를 바랍니다.


Unable to comment, but wanted to share some additional issues I ran into while spending many hours trying to get to the bottom of this issue even after using one of the other solutions.

It appears the best fix for me was Andrew's answer:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.view.setNeedsLayout() // force update layout
    navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}

However, at the very least in iOS 12.1, if your UINavigationBar:

  • has isTranslucent set to false, the View Controller with the search bar appears to not get it's view's layout adjusted back when interactively dismissing (normal dismissing via back button appears to work).
  • has it's background image set using setBackgroundImage(UIImage(), for: .default), the transition animation doesn't work properly and will jump back to its position after finishing.

These particular properties were set to get the Navigation Bar to appear in a certain way however, so I need to do some adjusting to get it back, or put up with the weird behaviour. Will try to remember to update the above if I run into anything else or find other solutions or differences in other OS versions.


I tried various things to get the size back to the original 44, but then the search bar always looks and behaves weird - like being to far stretched, y-offset and alike.

I found a nice solution here (via some other stackoverflow post): https://github.com/DreamTravelingLight/searchBarDemo

Just derive your viewcontroller from the SearchViewController and include in your project the SearchViewController and WMSearchbar classes. Worked out of the box for me without any ugly if (iOS11) else... uglyness.


제 경우에는 textField의 높이를 36pt-> 28pt로 줄여야합니다.

여기에 이미지 설명 입력

그래서 프레임의 높이, 레이어의 높이를 변경해 보았습니다. 그러나 방법은 작동하지 않았습니다.

마지막으로 마스크라는 솔루션을 찾았습니다. 좋은 방법은 아니지만 효과가 있다고 생각합니다.

    let textField              = searchBar.value(forKey: "searchField") as? UITextField
    textField?.font            = UIFont.systemFont(ofSize: 14.0, weight: .regular)
    textField?.textColor       = #colorLiteral(red: 0.1960784314, green: 0.1960784314, blue: 0.1960784314, alpha: 1)
    textField?.textAlignment   = .left

    if #available(iOS 11, *) {
        let radius: CGFloat           = 5.0
        let magnifyIconWidth: CGFloat = 16.0
        let inset                     = UIEdgeInsets(top: 4.0, left: 0, bottom: 4.0, right: 0)

        let path = CGMutablePath()
        path.addArc(center: CGPoint(x: searchBar.bounds.size.width - radius - inset.right - magnifyIconWidth, y: inset.top + radius), radius: radius, startAngle: .pi * 3.0/2.0, endAngle: .pi*2.0, clockwise: false)                        // Right top
        path.addArc(center: CGPoint(x: searchBar.bounds.size.width - radius - inset.right - magnifyIconWidth, y: searchBar.bounds.size.height - radius - inset.bottom), radius: radius, startAngle: 0, endAngle: .pi/2.0, clockwise: false)  // Right Bottom
        path.addArc(center: CGPoint(x: inset.left + radius, y: searchBar.bounds.size.height - radius - inset.bottom), radius: radius, startAngle: .pi/2.0, endAngle: .pi, clockwise: false)                                                  // Left Bottom
        path.addArc(center: CGPoint(x: inset.left + radius, y: inset.top + radius),  radius: radius, startAngle: .pi, endAngle: .pi * 3.0/2.0, clockwise: false)                                                                             // Left top

        let maskLayer      = CAShapeLayer()
        maskLayer.path     = path
        maskLayer.fillRule = kCAFillRuleEvenOdd

        textField?.layer.mask = maskLayer
    }

textField의 프레임을 변경하려면 인세 트를 변경할 수 있습니다.

참고 URL : https://stackoverflow.com/questions/46318022/uisearchbar-increases-navigation-bar-height-in-ios-11

반응형