diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 44043f885..1fe981b44 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/MainasuK/FPSIndicator.git", "state": { "branch": null, - "revision": "b2a002d689c400485f2ba41f9e71e15f7b99764a", - "version": "1.0.1" + "revision": "e4a5067ccd5293b024c767f09e51056afd4a4796", + "version": "1.1.0" } }, { diff --git a/Mastodon/Scene/Search/Search/SearchViewController.swift b/Mastodon/Scene/Search/Search/SearchViewController.swift index a3d84cd6a..1c073b4b9 100644 --- a/Mastodon/Scene/Search/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/Search/SearchViewController.swift @@ -11,6 +11,12 @@ import GameplayKit import MastodonSDK import UIKit +final class HeightFixedSearchBar: UISearchBar { + override var intrinsicContentSize: CGSize { + return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 44) + } +} + final class SearchViewController: UIViewController, NeedsDependency { let logger = Logger(subsystem: "Search", category: "UI") @@ -41,6 +47,11 @@ final class SearchViewController: UIViewController, NeedsDependency { var disposeBag = Set() 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 let scrollView: UIScrollView = { @@ -112,6 +123,10 @@ extension SearchViewController { super.viewDidAppear(animated) 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() { - let searchBar = UISearchBar() searchBar.placeholder = L10n.Scene.Search.SearchBar.placeholder 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 .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false) diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index 1debc188c..b401e7955 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -10,6 +10,8 @@ import UIKit import Combine import Pageboy +// Fake search bar not works on iPad with UISplitViewController +// check device and fallback to standard UISearchController final class SearchDetailViewController: PageboyViewController, NeedsDependency { 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 coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + let isPhoneDevice: Bool = { + return UIDevice.current.userInterfaceIdiom == .phone + }() var viewModel: SearchDetailViewModel! var viewControllers: [SearchResultViewController]! @@ -39,8 +45,22 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency { navigationBar.setItems([navigationItem], animated: false) 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.scopeButtonTitles = SearchDetailViewModel.SearchScope.allCases.map { $0.segmentedControlTitle } searchBar.sizeToFit() @@ -71,48 +91,27 @@ extension SearchDetailViewController { } .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() - 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) searchHistoryViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(searchHistoryViewController.view) searchHistoryViewController.didMove(toParent: self) - NSLayoutConstraint.activate([ - searchHistoryViewController.view.topAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor), - searchHistoryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - searchHistoryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - searchHistoryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + if isPhoneDevice { + NSLayoutConstraint.activate([ + searchHistoryViewController.view.topAnchor.constraint(equalTo: navigationBarBackgroundView.bottomAnchor), + searchHistoryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + 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) isScrollEnabled = false @@ -215,33 +214,83 @@ extension SearchDetailViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - navigationController?.setNavigationBarHidden(true, animated: animated) - searchBar.setShowsScope(true, animated: false) - searchBar.setNeedsLayout() - searchBar.layoutIfNeeded() + if isPhoneDevice { + navigationController?.setNavigationBarHidden(true, animated: animated) + searchBar.setShowsScope(true, animated: false) + searchBar.setNeedsLayout() + searchBar.layoutIfNeeded() + } else { + // do nothing + } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - if !isModal { - // prevent bar restore conflict with modal style issue - navigationController?.setNavigationBarHidden(false, animated: animated) + if isPhoneDevice { + if !isModal { + // prevent bar restore conflict with modal style issue + navigationController?.setNavigationBarHidden(false, animated: animated) + } + } else { + // do nothing } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - searchBar.setShowsCancelButton(true, animated: animated) - searchBar.becomeFirstResponder() + if isPhoneDevice { + searchBar.setShowsCancelButton(true, animated: animated) + searchBar.becomeFirstResponder() + } else { + searchController.isActive = true + searchController.searchBar.becomeFirstResponder() + } } } extension SearchDetailViewController { 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 }