0

I want to add a search bar to the navigation bar, but I do not know how to use search bar with sidebar icon in the same HStack. I put example screenshot with ContentView code. Any help would be appreciated.

Screenshot:

enter image description here

ContentView:

struct ContentView: View {
@State private var isShowing = false
var body: some View {
    ZStack {
        if isShowing {
            SideMenuView(isShowing: $isShowing)
        }
        
        TabView {
            NavigationView {
                HomeView()
                    .navigationBarItems(leading: Button(action: {
                        withAnimation(.spring()) {
                            isShowing.toggle()
                        }
                    } , label: {
                        Image(systemName: "list.bullet")
                    }))
            }
            .tabItem {
                Image(systemName: "1.circle")
                Text("Page 1")
            }
            NavigationView {
                HomeTwoView()
                    .navigationBarItems(leading: Button(action: {
                        withAnimation(.spring()) {
                            isShowing.toggle()
                        }
                    } , label: {
                        Image(systemName: "list.bullet")
                    }))
            }
            .tabItem {
                Image(systemName: "2.circle")
                Text("Page 2")
            }
        }
        .edgesIgnoringSafeArea(.bottom)
        //.cornerRadius(isShowing ? 20 : 0) //<< disabled due to strange effect
        .offset(x: isShowing ? 300 : 0, y: isShowing ? 44: 0)
        .scaleEffect(isShowing ? 0.8 : 1)

    }.onAppear {
        isShowing=false
    }
  } 
 }

1 Answers1

1

As I mentioned in comments this is not possible in SwiftUI (2.0) yet. What you can do is integrating with UIKit.


Integrate with UIKit

class UIKitSearchBar: NSObject, ObservableObject {
  @Published var text: String = ""
  let searchController = UISearchController(searchResultsController: nil)
  override init() {
    super.init()
    self.searchController.obscuresBackgroundDuringPresentation = false
    self.searchController.definesPresentationContext = true
    self.searchController.searchResultsUpdater = self
  }
}

extension UIKitSearchBar: 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: UIKitSearchBar
  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: UIKitSearchBar) -> 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)
  }
  @available(*, unavailable)
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  override func didMove(toParent parent: UIViewController?) {
    super.didMove(toParent: parent)
    if let parent = parent {
      onResolve(parent)
    }
  }
}

Usage

struct Example: View {

  @StateObject var searchBar = UIKitSearchBar()

  var body: some View {
    NavigationView {
      Text("Example")
        .add(searchBar)
        .navigationTitle("Example")
    }
  }
}

In my own project I am using computed property to filter stuff, it can be helpful for you too. Here is my code:

var filteredExams: [Exam] {
        examModel.exams.filter({ searchBar.text.isEmpty || $0.examName.localizedStandardContains(searchBar.text)})
    }

Screenshot

enter image description here

egeeke
  • 753
  • 1
  • 6
  • 18
  • Tnx so much, I will check it. When it work I will click vote. Tnx so much :) –  Mar 03 '21 at 20:36
  • One problem with this: in iOS 13 the search text won't update. See this [answer](https://stackoverflow.com/a/65067190/14351818). – aheze Mar 03 '21 at 20:41
  • 1
    since I am using `@StateObject` I assume he is not considering iOS 13.0, thanks for comment @aheze – egeeke Mar 03 '21 at 20:44
  • @muhal24 you can accept it if it works fine. :) – egeeke Mar 03 '21 at 20:46
  • 1
    @egeeke np. As a side note, for iOS 13 you can just replace `@StateObject` with `@ObservedObject`. – aheze Mar 03 '21 at 20:47
  • @aheze we can padding top search bar above?, there is much more space between search bar and navigation bar. –  Mar 09 '21 at 19:04
  • @Skysoft13 Did you use an extra NavigationView? I can take a look at your project if you want. – aheze Mar 09 '21 at 19:44
  • @aheze ı do but it is look like above, some gap between navigation –  Mar 09 '21 at 19:53