|
import SwiftUI |
|
import UIKit |
|
|
|
extension View { |
|
func navigationSearch(text: Binding<String>, onCancelButtonClicked: @escaping () -> Void = {}, onSearchButtonClicked: @escaping () -> Void = {}) -> some View { |
|
overlay(NavigationSearch<AnyView>(text: text, onCancelButtonClicked: onCancelButtonClicked, onSearchButtonClicked: onSearchButtonClicked).frame(width: 0, height: 0)) |
|
} |
|
|
|
func navigationSearch<SearchResultsContent>(text: Binding<String>, onCancelButtonClicked: @escaping () -> Void = {}, onSearchButtonClicked: @escaping () -> Void = {}, @ViewBuilder searchResultsContent: @escaping () -> SearchResultsContent) -> some View where SearchResultsContent : View { |
|
overlay(NavigationSearch<SearchResultsContent>(text: text, onCancelButtonClicked: onCancelButtonClicked, onSearchButtonClicked: onSearchButtonClicked, searchResultsContent: searchResultsContent).frame(width: 0, height: 0)) |
|
} |
|
} |
|
|
|
struct NavigationSearch<SearchResultsContent>: UIViewControllerRepresentable where SearchResultsContent : View { |
|
typealias UIViewControllerType = Wrapper |
|
|
|
@Binding var text: String |
|
|
|
private let onCancelButtonClicked: () -> Void |
|
private let onSearchButtonClicked: () -> Void |
|
|
|
private let searchResultsContent: () -> SearchResultsContent? |
|
|
|
init(text: Binding<String>, onCancelButtonClicked: @escaping () -> Void = {}, onSearchButtonClicked: @escaping () -> Void = {}, @ViewBuilder searchResultsContent: @escaping () -> SearchResultsContent? = { nil }) { |
|
self._text = text |
|
self.onCancelButtonClicked = onCancelButtonClicked |
|
self.onSearchButtonClicked = onSearchButtonClicked |
|
self.searchResultsContent = searchResultsContent |
|
} |
|
|
|
func makeCoordinator() -> Coordinator { |
|
Coordinator(representable: self) |
|
} |
|
|
|
func makeUIViewController(context: Context) -> Wrapper { |
|
Wrapper() |
|
} |
|
|
|
func updateUIViewController(_ wrapper: Wrapper, context: Context) { |
|
wrapper.searchController = context.coordinator.searchController |
|
wrapper.searchController?.searchBar.text = text |
|
wrapper.navigationBarSizeToFit() |
|
|
|
if let searchResultsContent = self.searchResultsContent() { |
|
(wrapper.searchController?.searchResultsController as? UIHostingController<SearchResultsContent>)?.rootView = searchResultsContent |
|
} |
|
} |
|
|
|
class Coordinator: NSObject, UISearchResultsUpdating, UISearchBarDelegate { |
|
let representable: NavigationSearch |
|
|
|
let searchController: UISearchController |
|
|
|
init(representable: NavigationSearch) { |
|
self.representable = representable |
|
|
|
var searchResultsController: UIViewController? = nil |
|
|
|
if let searchResultsContent = representable.searchResultsContent() { |
|
searchResultsController = UIHostingController(rootView: searchResultsContent) |
|
} |
|
|
|
self.searchController = UISearchController(searchResultsController: searchResultsController) |
|
|
|
super.init() |
|
self.searchController.searchResultsUpdater = self |
|
self.searchController.searchBar.delegate = self |
|
self.searchController.searchBar.text = representable.text |
|
} |
|
|
|
// MARK: - UISearchResultsUpdating |
|
func updateSearchResults(for searchController: UISearchController) { |
|
guard let text = searchController.searchBar.text else { return } |
|
DispatchQueue.main.async { [weak self] in self?.representable.text = text } |
|
} |
|
|
|
// MARK: - UISearchBarDelegate |
|
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { |
|
DispatchQueue.main.async { self.representable.onCancelButtonClicked() } |
|
} |
|
|
|
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { |
|
DispatchQueue.main.async { self.representable.onSearchButtonClicked() } |
|
} |
|
} |
|
|
|
class Wrapper: UIViewController { |
|
var searchController: UISearchController? { |
|
get { |
|
self.parent?.navigationItem.searchController |
|
} |
|
set { |
|
self.parent?.navigationItem.searchController = newValue |
|
} |
|
} |
|
|
|
func navigationBarSizeToFit() { |
|
self.parent?.navigationController?.navigationBar.sizeToFit() |
|
} |
|
} |
|
} |