fix: search bar active with re-layout animation on iPad device issue
This commit is contained in:
parent
f5aaf2737f
commit
8a33ed9f9f
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue