fix: search bar active with re-layout animation on iPad device issue

This commit is contained in:
CMK 2022-04-15 17:20:41 +08:00
parent f5aaf2737f
commit 8a33ed9f9f
6 changed files with 144 additions and 119 deletions

View File

@ -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 {

View File

@ -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<Void, Never>()
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)
// }
// }
//}

View File

@ -8,35 +8,35 @@
import UIKit
import MastodonSDK
extension SearchViewModel {
func setupDiffableDataSource(
collectionView: UICollectionView
) {
diffableDataSource = SearchSection.diffableDataSource(
collectionView: collectionView,
context: context
)
var snapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>()
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<SearchSection, SearchItem>()
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<SearchSection, SearchItem>()
// 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<SearchSection, SearchItem>()
// snapshot.appendSections([.trend])
//
// let trendItems = hashtags.map { SearchItem.trend($0) }
// snapshot.appendItems(trendItems, toSection: .trend)
//
// diffableDataSource.apply(snapshot)
// }
// .store(in: &disposeBag)
// }
//
//}

View File

@ -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<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { response } }
.catch { error in Just(Result<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, 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<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { response } }
// .catch { error in Just(Result<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, 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)
}
}

View File

@ -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()
}
}
}

View File

@ -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