Skip to content

Commit

Permalink
Fix ItemsViewController search bar size transition
Browse files Browse the repository at this point in the history
  • Loading branch information
mvasilak committed Sep 6, 2023
1 parent 1aa910f commit 7838c24
Showing 1 changed file with 58 additions and 69 deletions.
127 changes: 58 additions & 69 deletions Zotero/Scenes/Detail/Items/Views/ItemsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,9 @@ final class ItemsViewController: UIViewController {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)

// TODO: Modify this to use trait transitions
if UIDevice.current.userInterfaceIdiom == .pad {
let position = self.setupSearchBar(for: size)
if position == .navigationItem {
self.resetSearchBar()
}
} else {
coordinator.animate(alongsideTransition: { _ in
self.setupSearchBar(for: size)
}, completion: nil)
}
coordinator.animate(alongsideTransition: { _ in
self.setupSearchBar(for: size)
}, completion: nil)
}

override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
Expand Down Expand Up @@ -571,7 +563,6 @@ final class ItemsViewController: UIViewController {

private func setupTitle() {
guard traitCollection.horizontalSizeClass == .compact || UIDevice.current.userInterfaceIdiom == .phone else { return }
// TODO: Check why it is ignored in compact iPad
self.title = self.viewModel.state.collection.name
}

Expand Down Expand Up @@ -686,66 +677,64 @@ final class ItemsViewController: UIViewController {
@discardableResult
private func setupSearchBar(for windowSize: CGSize) -> SearchBarPosition {
// Detect current position of search bar
let current: SearchBarPosition? = self.navigationItem.searchController != nil ? .navigationItem : (self.navigationItem.titleView != nil ? .titleView : nil)
let current: SearchBarPosition? = navigationItem.searchController != nil ? .navigationItem : (navigationItem.titleView != nil ? .titleView : nil)
// The search bar can change position based on current window size. If the window is too narrow, the search bar appears in
// navigationItem, otherwise it can appear in titleView.
let new: SearchBarPosition = UIDevice.current.isCompactWidth(size: windowSize) ? .navigationItem : .titleView

let new: SearchBarPosition = traitCollection.horizontalSizeClass == .compact ? .navigationItem : .titleView
// Only change search bar if the position changes.
guard current != new else { return new }

self.removeSearchBar()
self.setupSearchBar(in: new)

removeSearchBar()
setupSearchBar(in: new)
return new
}

/// Setup `searchBar` in given position.
/// - parameter position: Position of `searchBar`.
private func setupSearchBar(in position: SearchBarPosition) {
switch position {
case .titleView:
let searchBar = UISearchBar()
self.setup(searchBar: searchBar)
// Workaround for broken `titleView` animation, check `SearchBarContainer` for more info.
let container = SearchBarContainer(searchBar: searchBar)
self.navigationItem.titleView = container
self.searchBarContainer = container

case .navigationItem:
let controller = UISearchController(searchResultsController: nil)
self.setup(searchBar: controller.searchBar)
controller.obscuresBackgroundDuringPresentation = false
if UIDevice.current.userInterfaceIdiom == .phone {
self.navigationItem.hidesSearchBarWhenScrolling = false

/// Removes `searchBar` from all positions.
func removeSearchBar() {
if navigationItem.searchController != nil {
navigationItem.searchController = nil
}
self.navigationItem.searchController = controller
}
}

/// Setup `searchBar`, start observing text changes.
/// - parameter searchBar: `searchBar` to setup and observe.
private func setup(searchBar: UISearchBar) {
searchBar.placeholder = L10n.Items.searchTitle
searchBar.rx
.text.observe(on: MainScheduler.instance)
.skip(1)
.debounce(.milliseconds(150), scheduler: MainScheduler.instance)
.subscribe(onNext: { [weak self] text in
self?.viewModel.process(action: .search(text ?? ""))
})
.disposed(by: self.disposeBag)
}

/// Removes `searchBar` from all positions.
private func removeSearchBar() {
if self.navigationItem.searchController != nil {
self.navigationItem.searchController = nil
if navigationItem.titleView != nil {
navigationItem.titleView = nil
}
searchBarContainer = nil
}
if self.navigationItem.titleView != nil {
self.navigationItem.titleView = nil

/// Setup `searchBar` in given position.
/// - parameter position: Position of `searchBar`.
func setupSearchBar(in position: SearchBarPosition) {
switch position {
case .titleView:
let searchBar = UISearchBar()
setup(searchBar: searchBar)
// Workaround for broken `titleView` animation, check `SearchBarContainer` for more info.
let container = SearchBarContainer(searchBar: searchBar)
navigationItem.titleView = container
searchBarContainer = container

case .navigationItem:
let controller = UISearchController(searchResultsController: nil)
setup(searchBar: controller.searchBar)
controller.obscuresBackgroundDuringPresentation = false
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = controller
}

/// Setup `searchBar`, start observing text changes.
/// - parameter searchBar: `searchBar` to setup and observe.
func setup(searchBar: UISearchBar) {
searchBar.placeholder = L10n.Items.searchTitle
searchBar.rx
.text.observe(on: MainScheduler.instance)
.skip(1)
.debounce(.milliseconds(150), scheduler: MainScheduler.instance)
.subscribe(onNext: { [weak self] text in
self?.viewModel.process(action: .search(text ?? ""))
})
.disposed(by: disposeBag)
}
}
self.searchBarContainer = nil
}

private func setupPullToRefresh() {
Expand Down Expand Up @@ -797,7 +786,7 @@ extension ItemsViewController: ItemsToolbarControllerDelegate {
}

///
/// This is a conainer for `UISearchBar` to fix broken UIKit `titleView` animation in navigation bar.
/// This is a container for `UISearchBar` to fix broken UIKit `titleView` animation in navigation bar.
/// The `titleView` is assigned an expanding view (`UISearchBar`), so the `titleView` expands to full width on animation to different screen.
/// For example, if the new screen has fewer `rightBarButtonItems`, the `titleView` width expands and the animation looks as if the search bar is
/// moving to the right, even though the screen is animating out to the left.
Expand All @@ -815,6 +804,9 @@ extension ItemsViewController: ItemsToolbarControllerDelegate {
private final class SearchBarContainer: UIView {
unowned let searchBar: UISearchBar
private var widthConstraint: NSLayoutConstraint!
private var maxSize: CGFloat {
max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
}

init(searchBar: UISearchBar) {
searchBar.translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -831,7 +823,6 @@ private final class SearchBarContainer: UIView {
searchBar.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor)
])

let maxSize = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
self.widthConstraint = self.searchBar.widthAnchor.constraint(equalToConstant: maxSize)
self.widthConstraint.priority = .defaultLow
self.widthConstraint.isActive = true
Expand All @@ -844,7 +835,6 @@ private final class SearchBarContainer: UIView {
override var intrinsicContentSize: CGSize {
// Changed from .greatestFiniteValue to this because of error "This NSLayoutConstraint is being configured with a constant that exceeds
// internal limits." This works fine as well and the debugger doesn't show the error anymore.
let maxSize = max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
return CGSize(width: maxSize, height: self.searchBar.bounds.height)
}

Expand All @@ -853,8 +843,7 @@ private final class SearchBarContainer: UIView {
}

func unfreezeWidth() {
let size = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
self.widthConstraint.constant = size
self.widthConstraint.constant = maxSize
}
}

Expand Down

0 comments on commit 7838c24

Please sign in to comment.