diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index c8ce4acb..1f803001 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -144,7 +144,7 @@ extension SceneCoordinator { case popover(sourceView: UIView) case panModal case custom(transitioningDelegate: UIViewControllerTransitioningDelegate) - case customPush + case customPush(animated: Bool) case safariPresent(animated: Bool, completion: (() -> Void)? = nil) case alertController(animated: Bool, completion: (() -> Void)? = nil) case activityViewControllerPresent(animated: Bool, completion: (() -> Void)? = nil) @@ -339,10 +339,10 @@ extension SceneCoordinator { viewController.transitioningDelegate = transitioningDelegate (splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil) - case .customPush: + case .customPush(let animated): // set delegate in view controller assert(sender?.navigationController?.delegate != nil) - sender?.navigationController?.pushViewController(viewController, animated: true) + sender?.navigationController?.pushViewController(viewController, animated: animated) case .safariPresent(let animated, let completion): if UserDefaults.shared.preferredUsingDefaultBrowser, case let .safari(url) = scene { diff --git a/Mastodon/Scene/Search/Search/SearchViewController.swift b/Mastodon/Scene/Search/Search/SearchViewController.swift index 6f4d2200..7594eb47 100644 --- a/Mastodon/Scene/Search/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/Search/SearchViewController.swift @@ -15,7 +15,7 @@ import MastodonLocalization final class HeightFixedSearchBar: UISearchBar { override var intrinsicContentSize: CGSize { - return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 44) + return CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) } } @@ -35,20 +35,20 @@ final class SearchViewController: UIViewController, NeedsDependency { // layout alongside with split mode button (on iPad) let titleViewContainer = UIView() let searchBar = HeightFixedSearchBar() - - let collectionView: UICollectionView = { - var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) - configuration.backgroundColor = .clear - configuration.headerMode = .supplementary - let layout = UICollectionViewCompositionalLayout.list(using: configuration) - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.backgroundColor = .clear - return collectionView - }() + +// let collectionView: UICollectionView = { +// var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) +// configuration.backgroundColor = .clear +// configuration.headerMode = .supplementary +// let layout = UICollectionViewCompositionalLayout.list(using: configuration) +// let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) +// collectionView.backgroundColor = .clear +// return collectionView +// }() let searchBarTapPublisher = PassthroughSubject() - private(set) lazy var trendViewController: DiscoveryViewController = { + private(set) lazy var discoveryViewController: DiscoveryViewController = { let viewController = DiscoveryViewController() viewController.context = context viewController.coordinator = coordinator @@ -78,29 +78,31 @@ extension SearchViewController { setupSearchBar() - collectionView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(collectionView) +// collectionView.translatesAutoresizingMaskIntoConstraints = false +// view.addSubview(collectionView) +// NSLayoutConstraint.activate([ +// collectionView.topAnchor.constraint(equalTo: view.topAnchor), +// collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), +// collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), +// collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), +// ]) +// +// collectionView.delegate = self +// viewModel.setupDiffableDataSource( +// collectionView: collectionView +// ) + + addChild(discoveryViewController) + discoveryViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(discoveryViewController.view) NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: view.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + discoveryViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + discoveryViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + discoveryViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + discoveryViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - collectionView.delegate = self - viewModel.setupDiffableDataSource( - collectionView: collectionView - ) - - addChild(trendViewController) - trendViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(trendViewController.view) - NSLayoutConstraint.activate([ - trendViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - trendViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - trendViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - trendViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) +// discoveryViewController.view.isHidden = true } override func viewDidAppear(_ animated: Bool) { @@ -130,7 +132,10 @@ extension SearchViewController { searchBar.trailingAnchor.constraint(equalTo: titleViewContainer.trailingAnchor), searchBar.bottomAnchor.constraint(equalTo: titleViewContainer.bottomAnchor), ]) + searchBar.setContentHuggingPriority(.required, for: .horizontal) + searchBar.setContentHuggingPriority(.required, for: .vertical) navigationItem.titleView = titleViewContainer +// navigationItem.titleView = searchBar searchBarTapPublisher .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false) @@ -140,7 +145,10 @@ extension SearchViewController { let searchDetailViewModel = SearchDetailViewModel() searchDetailViewModel.needsBecomeFirstResponder = true self.navigationController?.delegate = self.searchTransitionController - self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush) + // FIXME: + // use `.customPush(animated: false)` false to disable navigation bar animation for searchBar layout + // but that should be a fade transition whe fixed size searchBar + self.coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .customPush(animated: false)) } .store(in: &disposeBag) } @@ -168,21 +176,21 @@ extension SearchViewController: UISearchControllerDelegate { } // MARK: - UICollectionViewDelegate -extension SearchViewController: UICollectionViewDelegate { - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select item at: \(indexPath.debugDescription)") - - defer { - collectionView.deselectItem(at: indexPath, animated: true) - } - - guard let diffableDataSource = viewModel.diffableDataSource else { return } - guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - - switch item { - case .trend(let hashtag): - let viewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag.name) - coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: self, transition: .show) - } - } -} +//extension SearchViewController: UICollectionViewDelegate { +// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { +// logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select item at: \(indexPath.debugDescription)") +// +// defer { +// collectionView.deselectItem(at: indexPath, animated: true) +// } +// +// guard let diffableDataSource = viewModel.diffableDataSource else { return } +// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } +// +// switch item { +// case .trend(let hashtag): +// let viewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag.name) +// coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: self, transition: .show) +// } +// } +//} diff --git a/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift b/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift index ca741b7f..3f448289 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel+Diffable.swift @@ -8,35 +8,35 @@ import UIKit import MastodonSDK -extension SearchViewModel { - - func setupDiffableDataSource( - collectionView: UICollectionView - ) { - diffableDataSource = SearchSection.diffableDataSource( - collectionView: collectionView, - context: context - ) - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.trend]) - diffableDataSource?.apply(snapshot) - - $hashtags - .receive(on: DispatchQueue.main) - .sink { [weak self] hashtags in - guard let self = self else { return } - guard let diffableDataSource = self.diffableDataSource else { return } - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.trend]) - - let trendItems = hashtags.map { SearchItem.trend($0) } - snapshot.appendItems(trendItems, toSection: .trend) - - diffableDataSource.apply(snapshot) - } - .store(in: &disposeBag) - } - -} +//extension SearchViewModel { +// +// func setupDiffableDataSource( +// collectionView: UICollectionView +// ) { +// diffableDataSource = SearchSection.diffableDataSource( +// collectionView: collectionView, +// context: context +// ) +// +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.trend]) +// diffableDataSource?.apply(snapshot) +// +// $hashtags +// .receive(on: DispatchQueue.main) +// .sink { [weak self] hashtags in +// guard let self = self else { return } +// guard let diffableDataSource = self.diffableDataSource else { return } +// +// var snapshot = NSDiffableDataSourceSnapshot() +// snapshot.appendSections([.trend]) +// +// let trendItems = hashtags.map { SearchItem.trend($0) } +// snapshot.appendItems(trendItems, toSection: .trend) +// +// diffableDataSource.apply(snapshot) +// } +// .store(in: &disposeBag) +// } +// +//} diff --git a/Mastodon/Scene/Search/Search/SearchViewModel.swift b/Mastodon/Scene/Search/Search/SearchViewModel.swift index 84e09725..b47bc2e8 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel.swift @@ -29,31 +29,31 @@ final class SearchViewModel: NSObject { self.context = context super.init() - Publishers.CombineLatest( - context.authenticationService.activeMastodonAuthenticationBox, - viewDidAppeared - ) - .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in - return authenticationBox - } - .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) - .asyncMap { authenticationBox in - try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) - } - .retry(3) - .map { response in Result, Error> { response } } - .catch { error in Just(Result, Error> { throw error }) } - .receive(on: DispatchQueue.main) - .sink { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let response): - self.hashtags = response.value - case .failure: - break - } - } - .store(in: &disposeBag) +// Publishers.CombineLatest( +// context.authenticationService.activeMastodonAuthenticationBox, +// viewDidAppeared +// ) +// .compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in +// return authenticationBox +// } +// .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) +// .asyncMap { authenticationBox in +// try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil) +// } +// .retry(3) +// .map { response in Result, Error> { response } } +// .catch { error in Just(Result, Error> { throw error }) } +// .receive(on: DispatchQueue.main) +// .sink { [weak self] result in +// guard let self = self else { return } +// switch result { +// case .success(let response): +// self.hashtags = response.value +// case .failure: +// break +// } +// } +// .store(in: &disposeBag) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index 5e143a33..ecc1c0c0 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -12,6 +12,14 @@ import Pageboy import MastodonAsset import MastodonLocalization +final class CustomSearchController: UISearchController { + + let customSearchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 300, height: 100)) + + override var searchBar: UISearchBar { customSearchBar } + +} + // Fake search bar not works on iPad with UISplitViewController // check device and fallback to standard UISearchController final class SearchDetailViewController: PageboyViewController, NeedsDependency { @@ -48,8 +56,8 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency { return navigationBar }() - let searchController: UISearchController = { - let searchController = UISearchController() + let searchController: CustomSearchController = { + let searchController = CustomSearchController() searchController.automaticallyShowsScopeBar = false searchController.dimsBackgroundDuringPresentation = false return searchController @@ -235,11 +243,17 @@ extension SearchDetailViewController { if isPhoneDevice { searchBar.setShowsCancelButton(true, animated: animated) - searchBar.becomeFirstResponder() + UIView.performWithoutAnimation { + self.searchBar.becomeFirstResponder() + } } else { - searchController.isActive = true - DispatchQueue.main.asyncAfter(deadline: .now() + 0.33) { - self.searchController.searchBar.becomeFirstResponder() + searchController.searchBar.setShowsCancelButton(true, animated: false) + searchController.searchBar.setShowsScope(true, animated: false) + UIView.performWithoutAnimation { + self.searchController.isActive = true + } + DispatchQueue.main.async { + self.searchController.searchBar.becomeFirstResponder() } } } diff --git a/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift b/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift index f06d04d9..e060acc8 100644 --- a/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift +++ b/Mastodon/Scene/Transition/Search/SearchToSearchDetailViewControllerAnimatedTransitioning.swift @@ -46,14 +46,17 @@ extension SearchToSearchDetailViewControllerAnimatedTransitioning { let toViewEndFrame = transitionContext.finalFrame(for: toVC) transitionContext.containerView.addSubview(toView) toView.frame = toViewEndFrame + toView.setNeedsLayout() + toView.layoutIfNeeded() + toVC.searchBar.setNeedsLayout() + toVC.searchBar.layoutIfNeeded() toView.alpha = 0 let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), curve: curve) animator.addAnimations { - + toView.alpha = 1 } animator.addCompletion { position in - toView.alpha = 1 transitionContext.completeTransition(true) } return animator