Search code examples
iosautolayoutuisearchbaruisearchcontrolleruisearchbardelegate

UISearchBar jumping out of position once tapped


I am trying to display a MySearchBar (UISearchBar with added UILabel to display the title) as the navigationItem's view:

// Make search bar autoresize and occupy maximum width
self.navigationItem.titleView = [[UIView alloc] initWithFrame:self.navigationController.navigationBar.bounds];
self.navigationItem.titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.navigationItem.titleView addSubview:self.mySearchBar.view];
self.mySearchBar.view.frame = self.navigationItem.titleView.frame;

The search bar sits in place until it is tapped. After tapping, it moves down and it no longer occupies full width (around 16px are missing).

Before editing (how it should look):

Before editing

Editing began (BUG - after tapping the search bar grows in height to the default 56px and loses width):

Editing began

Editing ended (remains misplaced)

Editing ended

Here is the initialization code of MySearchBar. It consists of two main views, titleView wraps the UILabel with title and searchBarWrapperView wraps the UISearchBar:

-(id) init {
    // Initialize the two wrapping views
    UIView *titleView = [UIView new];
    self.searchBarWrapperView = [UIView new];

    // Resize UILabel to fit its content
    self.titleLabel = [UILabel new];
    self.titleLabel.numberOfLines = 0;
    [self.titleLabel sizeToFit];

    self.searchBar = self.searchController.searchBar;
    self.searchBarTextField = [self.searchBar valueForKey:@"searchField"];// This could be the text field that gets displaced???

    // Add two main subviews (wrappers)
    [self.view addSubview:titleView];
    [self.view addSubview:self.searchBarWrapperView];

    // Add title label and search bar to the subviews
    [titleView addSubview:self.titleLabel];
    [self.searchBarWrapperView addSubview:self.searchBar];

    // Disable auto constraints
    [titleView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.searchBarWrapperView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];

    // Set constraints for title view
    [titleView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
    [titleView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
    [titleView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;
    [titleView.bottomAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor].active = YES;

    // Set constraints for title label
    [self.titleLabel.topAnchor constraintEqualToAnchor:titleView.topAnchor].active = YES;
    [self.titleLabel.leftAnchor constraintEqualToAnchor:titleView.leftAnchor].active = YES;
    [self.titleLabel.rightAnchor constraintEqualToAnchor:titleView.rightAnchor].active = YES;

    // Set constraints for search bar wrapper
    [self.searchBarWrapperView.heightAnchor constraintGreaterThanOrEqualToConstant:30].active = YES;// The search bar together with the title label should occupy the whole 44px height of the navigation bar
    [self.searchBarWrapperView.topAnchor constraintEqualToAnchor:titleView.bottomAnchor].active = YES;
    [self.searchBarWrapperView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
    [self.searchBarWrapperView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
    [self.searchBarWrapperView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor constant:16].active = YES;// Add space that is lost by setSearchFieldBackgroundPositionAdjustment

    // Configure autoresizing mask for UISearchBar
    [self.searchBar setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
    [self.searchBar setFrame:self.searchBarWrapperView.frame];

    //Remove left and right blank spaces around UISearchBar
    [self.searchBar setSearchFieldBackgroundPositionAdjustment:UIOffsetMake(-8,0)];

    // Colors for debugging        
    self.view.backgroundColor = [UIColor blueColor];
    titleView.backgroundColor = [UIColor redColor];
    searchBarWrapperView.backgroundColor = [UIColor greenColor];
    self.searchBar.backgroundColor = [UIColor yellowColor];
    self.searchBarTextField.backgroundColor = [UIColor brownColor];

    return self;
}

Solution

  • The solution I used was to subclass the UISearchBar and the UISearchController. That way I can set the frame and control the search bar events.

    SearchBar.swift:

    import Foundation
    
    class SearchBar : UISearchBar {
    
        var preferredFont:UIFont?
        var preferredTextColor:UIColor?
    
        init(frame: CGRect, font: UIFont, textColor: UIColor) {
            super.init(frame: frame)
    
            self.frame = frame
            self.preferredFont = font
            self.preferredTextColor = textColor
        }
    
        required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)!
        }
    }
    

    SearchController.swift:

    import Foundation
    
    protocol SearchControllerDelegate {
        func didStartSearching()
    
        func didTapOnSearchButton()
    
        func didTapOnCancelButton()
    
        func didChangeSearchText(searchText: String)
    }
    
    class SearchController : UISearchController, UISearchBarDelegate, SearchControllerDelegate {
    
        var customSearchBar: SearchBar!
        var customDelegate: SearchControllerDelegate!
    
        init(searchResultsController: UIViewController!, searchBarFrame: CGRect, searchBarFont: UIFont, searchBarTextColor: UIColor, searchBarTintColor: UIColor) {
            super.init(searchResultsController: searchResultsController)
            configureSearchBar(frame: searchBarFrame, font: searchBarFont, textColor: searchBarTextColor, bgColor: searchBarTintColor)
        }
    
        override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
            super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        }
    
        required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)!
        }
    
        func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) {
            self.customSearchBar = SearchBar(frame: frame, font: font , textColor: textColor)
    
            self.customSearchBar.placeholder = "Search"
            self.customSearchBar.barTintColor = bgColor
            self.customSearchBar.tintColor = textColor
            self.customSearchBar.showsBookmarkButton = false
            self.customSearchBar.showsCancelButton = false
    
            self.customSearchBar.delegate = self
            self.customDelegate = self;
    
            let searchBarTextField:UITextField = self.customSearchBar.value(forKey: "searchField") as! UITextField
    
            searchBarTextField.font = font
            searchBarTextField.layer.borderWidth = 1
            searchBarTextField.layer.cornerRadius = 3
            searchBarTextField.layer.borderColor = UIColor.lightGray.cgColor
        }
    
        // UISearchBarDelegate
    
        func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
            customDelegate.didStartSearching()
        }
    
        func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
            customSearchBar.resignFirstResponder()
            customDelegate.didTapOnSearchButton()
        }
    
        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            customSearchBar.resignFirstResponder()
            customDelegate.didTapOnCancelButton()
        }
    
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            customDelegate.didChangeSearchText(searchText: searchText)
        }
    
        // SearchControllerDelegate
    
        func didStartSearching() {
    
        }
    
        func didTapOnSearchButton() {
            var searchText:String = ""
    
            if (self.customSearchBar.text != nil) {
                searchText = self.customSearchBar.text!
            }
    
            self.search(searchQuery: searchText)
        }
    
        func didTapOnCancelButton() {
    
        }
    
        func didChangeSearchText(searchText: String) {
            self.search(searchQuery: searchText)
        }
    
        // Search
        func search(searchQuery: String) {
            // Start searching
        }
    }