forked from zelo72/mastodon-ios
feat: make search bar works on iPad
This commit is contained in:
parent
1da803fb97
commit
9d66119f9e
|
@ -69,8 +69,8 @@
|
||||||
"repositoryURL": "https://github.com/MainasuK/FPSIndicator.git",
|
"repositoryURL": "https://github.com/MainasuK/FPSIndicator.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "b2a002d689c400485f2ba41f9e71e15f7b99764a",
|
"revision": "e4a5067ccd5293b024c767f09e51056afd4a4796",
|
||||||
"version": "1.0.1"
|
"version": "1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,6 +11,12 @@ import GameplayKit
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
final class HeightFixedSearchBar: UISearchBar {
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 44)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class SearchViewController: UIViewController, NeedsDependency {
|
final class SearchViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
let logger = Logger(subsystem: "Search", category: "UI")
|
let logger = Logger(subsystem: "Search", category: "UI")
|
||||||
|
@ -41,6 +47,11 @@ final class SearchViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
private(set) lazy var viewModel = SearchViewModel(context: context)
|
private(set) lazy var viewModel = SearchViewModel(context: context)
|
||||||
|
|
||||||
|
// use AutoLayout could set search bar margin automatically to
|
||||||
|
// layout alongside with split mode button (on iPad)
|
||||||
|
let titleViewContainer = UIView()
|
||||||
|
let searchBar = HeightFixedSearchBar()
|
||||||
|
|
||||||
// recommend
|
// recommend
|
||||||
let scrollView: UIScrollView = {
|
let scrollView: UIScrollView = {
|
||||||
|
@ -112,6 +123,10 @@ extension SearchViewController {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
viewModel.viewDidAppeared.send()
|
viewModel.viewDidAppeared.send()
|
||||||
|
|
||||||
|
// note:
|
||||||
|
// need set alpha because (maybe) SDK forget set alpha back
|
||||||
|
titleViewContainer.alpha = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,10 +136,17 @@ extension SearchViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupSearchBar() {
|
private func setupSearchBar() {
|
||||||
let searchBar = UISearchBar()
|
|
||||||
searchBar.placeholder = L10n.Scene.Search.SearchBar.placeholder
|
searchBar.placeholder = L10n.Scene.Search.SearchBar.placeholder
|
||||||
searchBar.delegate = self
|
searchBar.delegate = self
|
||||||
navigationItem.titleView = searchBar
|
searchBar.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
titleViewContainer.addSubview(searchBar)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
searchBar.topAnchor.constraint(equalTo: titleViewContainer.topAnchor),
|
||||||
|
searchBar.leadingAnchor.constraint(equalTo: titleViewContainer.leadingAnchor),
|
||||||
|
searchBar.trailingAnchor.constraint(equalTo: titleViewContainer.trailingAnchor),
|
||||||
|
searchBar.bottomAnchor.constraint(equalTo: titleViewContainer.bottomAnchor),
|
||||||
|
])
|
||||||
|
navigationItem.titleView = titleViewContainer
|
||||||
|
|
||||||
searchBarTapPublisher
|
searchBarTapPublisher
|
||||||
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false)
|
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false)
|
||||||
|
|
|
@ -10,6 +10,8 @@ import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Pageboy
|
import Pageboy
|
||||||
|
|
||||||
|
// Fake search bar not works on iPad with UISplitViewController
|
||||||
|
// check device and fallback to standard UISearchController
|
||||||
final class SearchDetailViewController: PageboyViewController, NeedsDependency {
|
final class SearchDetailViewController: PageboyViewController, NeedsDependency {
|
||||||
|
|
||||||
let logger = Logger(subsystem: "SearchDetail", category: "UI")
|
let logger = Logger(subsystem: "SearchDetail", category: "UI")
|
||||||
|
@ -19,6 +21,10 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency {
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
|
let isPhoneDevice: Bool = {
|
||||||
|
return UIDevice.current.userInterfaceIdiom == .phone
|
||||||
|
}()
|
||||||
|
|
||||||
var viewModel: SearchDetailViewModel!
|
var viewModel: SearchDetailViewModel!
|
||||||
var viewControllers: [SearchResultViewController]!
|
var viewControllers: [SearchResultViewController]!
|
||||||
|
@ -39,8 +45,22 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency {
|
||||||
navigationBar.setItems([navigationItem], animated: false)
|
navigationBar.setItems([navigationItem], animated: false)
|
||||||
return navigationBar
|
return navigationBar
|
||||||
}()
|
}()
|
||||||
let searchBar: UISearchBar = {
|
|
||||||
let searchBar = UISearchBar()
|
let searchController: UISearchController = {
|
||||||
|
let searchController = UISearchController()
|
||||||
|
searchController.automaticallyShowsScopeBar = false
|
||||||
|
searchController.dimsBackgroundDuringPresentation = false
|
||||||
|
return searchController
|
||||||
|
}()
|
||||||
|
private(set) lazy var searchBar: UISearchBar = {
|
||||||
|
let searchBar: UISearchBar
|
||||||
|
if isPhoneDevice {
|
||||||
|
searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 320, height: 44))
|
||||||
|
} else {
|
||||||
|
searchBar = searchController.searchBar
|
||||||
|
searchController.automaticallyShowsScopeBar = false
|
||||||
|
searchController.searchBar.setShowsScope(true, animated: false)
|
||||||
|
}
|
||||||
searchBar.placeholder = L10n.Scene.Search.SearchBar.placeholder
|
searchBar.placeholder = L10n.Scene.Search.SearchBar.placeholder
|
||||||
searchBar.scopeButtonTitles = SearchDetailViewModel.SearchScope.allCases.map { $0.segmentedControlTitle }
|
searchBar.scopeButtonTitles = SearchDetailViewModel.SearchScope.allCases.map { $0.segmentedControlTitle }
|
||||||
searchBar.sizeToFit()
|
searchBar.sizeToFit()
|
||||||
|
@ -71,48 +91,27 @@ extension SearchDetailViewController {
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
navigationBar.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.addSubview(navigationBar)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
navigationBar.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
|
||||||
navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
])
|
|
||||||
setupSearchBar()
|
setupSearchBar()
|
||||||
navigationBar.layer.observe(\.bounds, options: [.new]) { [weak self] navigationBar, _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.viewModel.navigationBarFrame.value = navigationBar.frame
|
|
||||||
}
|
|
||||||
.store(in: &observations)
|
|
||||||
|
|
||||||
navigationBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.insertSubview(navigationBarBackgroundView, belowSubview: navigationBar)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
navigationBarBackgroundView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
||||||
navigationBarBackgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
navigationBarBackgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
navigationBarBackgroundView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor),
|
|
||||||
])
|
|
||||||
|
|
||||||
navigationBarVisualEffectBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.insertSubview(navigationBarVisualEffectBackgroundView, belowSubview: navigationBarBackgroundView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
navigationBarVisualEffectBackgroundView.topAnchor.constraint(equalTo: navigationBarBackgroundView.topAnchor),
|
|
||||||
navigationBarVisualEffectBackgroundView.leadingAnchor.constraint(equalTo: navigationBarBackgroundView.leadingAnchor),
|
|
||||||
navigationBarVisualEffectBackgroundView.trailingAnchor.constraint(equalTo: navigationBarBackgroundView.trailingAnchor),
|
|
||||||
navigationBarVisualEffectBackgroundView.bottomAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor),
|
|
||||||
])
|
|
||||||
|
|
||||||
addChild(searchHistoryViewController)
|
addChild(searchHistoryViewController)
|
||||||
searchHistoryViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
searchHistoryViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(searchHistoryViewController.view)
|
view.addSubview(searchHistoryViewController.view)
|
||||||
searchHistoryViewController.didMove(toParent: self)
|
searchHistoryViewController.didMove(toParent: self)
|
||||||
NSLayoutConstraint.activate([
|
if isPhoneDevice {
|
||||||
searchHistoryViewController.view.topAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor),
|
NSLayoutConstraint.activate([
|
||||||
searchHistoryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
searchHistoryViewController.view.topAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor),
|
||||||
searchHistoryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
searchHistoryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
searchHistoryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
])
|
searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
searchHistoryViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
searchHistoryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
searchHistoryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
transition = Transition(style: .fade, duration: 0.1)
|
transition = Transition(style: .fade, duration: 0.1)
|
||||||
isScrollEnabled = false
|
isScrollEnabled = false
|
||||||
|
@ -215,33 +214,83 @@ extension SearchDetailViewController {
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
navigationController?.setNavigationBarHidden(true, animated: animated)
|
if isPhoneDevice {
|
||||||
searchBar.setShowsScope(true, animated: false)
|
navigationController?.setNavigationBarHidden(true, animated: animated)
|
||||||
searchBar.setNeedsLayout()
|
searchBar.setShowsScope(true, animated: false)
|
||||||
searchBar.layoutIfNeeded()
|
searchBar.setNeedsLayout()
|
||||||
|
searchBar.layoutIfNeeded()
|
||||||
|
} else {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
if !isModal {
|
if isPhoneDevice {
|
||||||
// prevent bar restore conflict with modal style issue
|
if !isModal {
|
||||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
// prevent bar restore conflict with modal style issue
|
||||||
|
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
searchBar.setShowsCancelButton(true, animated: animated)
|
if isPhoneDevice {
|
||||||
searchBar.becomeFirstResponder()
|
searchBar.setShowsCancelButton(true, animated: animated)
|
||||||
|
searchBar.becomeFirstResponder()
|
||||||
|
} else {
|
||||||
|
searchController.isActive = true
|
||||||
|
searchController.searchBar.becomeFirstResponder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SearchDetailViewController {
|
extension SearchDetailViewController {
|
||||||
private func setupSearchBar() {
|
private func setupSearchBar() {
|
||||||
navigationBar.topItem?.titleView = searchBar
|
if isPhoneDevice {
|
||||||
|
navigationBar.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.addSubview(navigationBar)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
navigationBar.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
||||||
|
navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
])
|
||||||
|
navigationBar.topItem?.titleView = searchBar
|
||||||
|
navigationBar.layer.observe(\.bounds, options: [.new]) { [weak self] navigationBar, _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.viewModel.navigationBarFrame.value = navigationBar.frame
|
||||||
|
}
|
||||||
|
.store(in: &observations)
|
||||||
|
|
||||||
|
navigationBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.insertSubview(navigationBarBackgroundView, belowSubview: navigationBar)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
navigationBarBackgroundView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
navigationBarBackgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
navigationBarBackgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
navigationBarBackgroundView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
navigationBarVisualEffectBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.insertSubview(navigationBarVisualEffectBackgroundView, belowSubview: navigationBarBackgroundView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
navigationBarVisualEffectBackgroundView.topAnchor.constraint(equalTo: navigationBarBackgroundView.topAnchor),
|
||||||
|
navigationBarVisualEffectBackgroundView.leadingAnchor.constraint(equalTo: navigationBarBackgroundView.leadingAnchor),
|
||||||
|
navigationBarVisualEffectBackgroundView.trailingAnchor.constraint(equalTo: navigationBarBackgroundView.trailingAnchor),
|
||||||
|
navigationBarVisualEffectBackgroundView.bottomAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
navigationItem.setHidesBackButton(true, animated: false)
|
||||||
|
navigationItem.titleView = nil
|
||||||
|
navigationItem.searchController = searchController
|
||||||
|
searchController.searchBar.sizeToFit()
|
||||||
|
}
|
||||||
|
|
||||||
searchBar.delegate = self
|
searchBar.delegate = self
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue