Search code examples
swiftswiftuiuikitnavigationbarsearchbar

How to change position of SearchBar in NavigationBar?


I followed this article on how to display a SearchBar in the NavigationBar. I integrated it like this into my view:

struct ExploreView: View {

    @ObservedObject var searchBar = SearchBar()

    var body: some View {
        NavigationView {
            ZStack {
                Color(red: 250/255, green: 250/255, blue: 250/255)
                    .edgesIgnoringSafeArea(.all)
            
                VStack(spacing: 0) {
                    Image(R.image.navigationBarBackground)
                        .resizable()
                        .scaledToFit()
                        .frame(width: UIScreen.main.bounds.width)
                        .edgesIgnoringSafeArea(.all)
                    Spacer()
                }
            }
            .navigationBarTitle("", displayMode: .inline)
            .add(self.searchBar)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

class SearchBar: NSObject, ObservableObject {

    @Published var text: String = ""
    let searchController: UISearchController = UISearchController(searchResultsController: nil)

    override init() {
        super.init()
        self.searchController.obscuresBackgroundDuringPresentation = false
        self.searchController.searchResultsUpdater = self
    }
}

extension SearchBar: UISearchResultsUpdating {

    func updateSearchResults(for searchController: UISearchController) {
        // Publish search bar text changes.
        if let searchBarText = searchController.searchBar.text {
            self.text = searchBarText
        }
    }
}

struct SearchBarModifier: ViewModifier {

    let searchBar: SearchBar

    func body(content: Content) -> some View {
        content
            .overlay(
                ViewControllerResolver { viewController in
                    viewController.navigationItem.searchController = self.searchBar.searchController
                }
                .frame(width: 0, height: 0)
            )
    }
}

extension View {
    func add(_ searchBar: SearchBar) -> some View {
        return self.modifier(SearchBarModifier(searchBar: searchBar))
    
    }
}

final class ViewControllerResolver: UIViewControllerRepresentable {

    let onResolve: (UIViewController) -> Void
    
    init(onResolve: @escaping (UIViewController) -> Void) {
        self.onResolve = onResolve
    }

    func makeUIViewController(context: Context) -> ParentResolverViewController {
        ParentResolverViewController(onResolve: onResolve)
    }

    func updateUIViewController(_ uiViewController: ParentResolverViewController, context: Context) {
    }
}

class ParentResolverViewController: UIViewController {

    let onResolve: (UIViewController) -> Void

    init(onResolve: @escaping (UIViewController) -> Void) {
        self.onResolve = onResolve
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("Use init(onResolve:) to instantiate ParentResolverViewController.")
    }
    
    override func didMove(toParent parent: UIViewController?) {
        super.didMove(toParent: parent)
    
        if let parent = parent {
            onResolve(parent)
        }
    }
}

It look's like this: SearchBar: inactive, SearchBar: active

But I would like to have the inactive SearchBar in the same position as the active SearchBar to avoid the free space. In the end it should look like the SearchBar in the Instagram App. Does anyone know how to do this?


Solution

  • Add this piece of code inside your init() method of SearchBar. It will make search bar at same position when it's active.

    self.searchController.hidesNavigationBarDuringPresentation = false
    

    If you want to set search bar to navigation bar title instead of text, inside your overlay(_:)

    Change this code

    ViewControllerResolver { viewController in
        viewController.navigationItem.searchController = self.searchBar.searchController
    }
    

    To

    ViewControllerResolver { viewController in
        viewController.navigationItem.titleView = self.searchBar.searchController.searchBar
    }