Explore Tab: Use a segmented picker under the search bar (IOS-237) (#1261)
* Remove custom tab-bar from explore-tab (IOS-237) * Add scope-bar to SearchBar on discovery-screen and attach scrolling etc. (IOS-237) * Replace searchbar-scopes with proper segmented control (IOS-237) The reason for this is that scopes didn't work on iPad in DetailView of the UISplitViewController. I should blog about this. * kill some whitespace
This commit is contained in:
parent
cc9faf5aea
commit
eace1ea815
|
@ -7,13 +7,12 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import Tabman
|
||||
import Pageboy
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
public class DiscoveryViewController: TabmanViewController, NeedsDependency {
|
||||
public class DiscoveryViewController: PageboyViewController, NeedsDependency {
|
||||
|
||||
public static let containerViewMarginForRegularHorizontalSizeClass: CGFloat = 64
|
||||
public static let containerViewMarginForCompactHorizontalSizeClass: CGFloat = 16
|
||||
|
@ -25,71 +24,12 @@ public class DiscoveryViewController: TabmanViewController, NeedsDependency {
|
|||
|
||||
var viewModel: DiscoveryViewModel!
|
||||
|
||||
private(set) lazy var buttonBar: TMBar.ButtonBar = {
|
||||
let buttonBar = TMBar.ButtonBar()
|
||||
buttonBar.backgroundView.style = .custom(view: buttonBarBackgroundView)
|
||||
buttonBar.layout.interButtonSpacing = 0
|
||||
buttonBar.layout.contentInset = .zero
|
||||
buttonBar.indicator.backgroundColor = Asset.Colors.Label.primary.color
|
||||
buttonBar.indicator.weight = .custom(value: 2)
|
||||
return buttonBar
|
||||
}()
|
||||
|
||||
let buttonBarBackgroundView: UIView = {
|
||||
let view = UIView()
|
||||
let barBottomLine = UIView.separatorLine
|
||||
barBottomLine.backgroundColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.5)
|
||||
barBottomLine.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(barBottomLine)
|
||||
NSLayoutConstraint.activate([
|
||||
barBottomLine.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
barBottomLine.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
barBottomLine.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
barBottomLine.heightAnchor.constraint(equalToConstant: 2).priority(.required - 1),
|
||||
])
|
||||
return view
|
||||
}()
|
||||
|
||||
func customizeButtonBarAppearance() {
|
||||
// The implmention use CATextlayer. Adapt for Dark Mode without dynamic colors
|
||||
// Needs trigger update when `userInterfaceStyle` chagnes
|
||||
let userInterfaceStyle = traitCollection.userInterfaceStyle
|
||||
buttonBar.buttons.customize { button in
|
||||
switch userInterfaceStyle {
|
||||
case .dark:
|
||||
// Asset.Colors.Label.primary.color
|
||||
button.selectedTintColor = UIColor(red: 238.0/255.0, green: 238.0/255.0, blue: 238.0/255.0, alpha: 1.0)
|
||||
// Asset.Colors.Label.secondary.color
|
||||
button.tintColor = UIColor(red: 151.0/255.0, green: 157.0/255.0, blue: 173.0/255.0, alpha: 1.0)
|
||||
default:
|
||||
// Asset.Colors.Label.primary.color
|
||||
button.selectedTintColor = UIColor(red: 40.0/255.0, green: 44.0/255.0, blue: 55.0/255.0, alpha: 1.0)
|
||||
// Asset.Colors.Label.secondary.color
|
||||
button.tintColor = UIColor(red: 60.0/255.0, green: 60.0/255.0, blue: 67.0/255.0, alpha: 0.6)
|
||||
}
|
||||
|
||||
button.backgroundColor = .clear
|
||||
button.contentInset = UIEdgeInsets(top: 12, left: 26, bottom: 12, right: 26)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DiscoveryViewController {
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupAppearance()
|
||||
|
||||
dataSource = viewModel
|
||||
addBar(
|
||||
buttonBar,
|
||||
dataSource: viewModel,
|
||||
at: .top
|
||||
)
|
||||
customizeButtonBarAppearance()
|
||||
|
||||
viewModel.$viewControllers
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
|
@ -99,21 +39,9 @@ extension DiscoveryViewController {
|
|||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
customizeButtonBarAppearance()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DiscoveryViewController {
|
||||
|
||||
private func setupAppearance() {
|
||||
view.backgroundColor = .secondarySystemBackground
|
||||
buttonBarBackgroundView.backgroundColor = .systemBackground
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - ScrollViewContainer
|
||||
|
@ -148,5 +76,4 @@ extension DiscoveryViewController: PageboyNavigateable {
|
|||
@objc func pageboyNavigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) {
|
||||
pageboyNavigateKeyCommandHandler(sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import Tabman
|
||||
import Pageboy
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
|
@ -24,7 +23,7 @@ final class DiscoveryViewModel {
|
|||
let discoveryNewsViewController: DiscoveryNewsViewController
|
||||
let discoveryForYouViewController: DiscoveryForYouViewController
|
||||
|
||||
@Published var viewControllers: [ScrollViewContainer & PageViewController]
|
||||
@Published var viewControllers: [ScrollViewContainer]
|
||||
|
||||
@MainActor
|
||||
init(context: AppContext, coordinator: SceneCoordinator, authContext: AuthContext) {
|
||||
|
@ -110,53 +109,3 @@ extension DiscoveryViewModel: PageboyViewControllerDataSource {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - TMBarDataSource
|
||||
extension DiscoveryViewModel: TMBarDataSource {
|
||||
func barItem(for bar: TMBar, at index: Int) -> TMBarItemable {
|
||||
guard !viewControllers.isEmpty, index < viewControllers.count else {
|
||||
assertionFailure()
|
||||
return TMBarItem(title: "")
|
||||
}
|
||||
return viewControllers[index].tabItem
|
||||
}
|
||||
}
|
||||
|
||||
protocol PageViewController: UIViewController {
|
||||
var tabItemTitle: String { get }
|
||||
var tabItem: TMBarItemable { get }
|
||||
}
|
||||
|
||||
// MARK: - PageViewController
|
||||
extension DiscoveryPostsViewController: PageViewController {
|
||||
var tabItemTitle: String { L10n.Scene.Discovery.Tabs.posts }
|
||||
var tabItem: TMBarItemable {
|
||||
return TMBarItem(title: tabItemTitle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - PageViewController
|
||||
extension DiscoveryHashtagsViewController: PageViewController {
|
||||
var tabItemTitle: String { L10n.Scene.Discovery.Tabs.hashtags }
|
||||
var tabItem: TMBarItemable {
|
||||
|
||||
return TMBarItem(title: tabItemTitle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PageViewController
|
||||
extension DiscoveryNewsViewController: PageViewController {
|
||||
var tabItemTitle: String { L10n.Scene.Discovery.Tabs.news }
|
||||
var tabItem: TMBarItemable {
|
||||
return TMBarItem(title: tabItemTitle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PageViewController
|
||||
extension DiscoveryForYouViewController: PageViewController {
|
||||
var tabItemTitle: String { L10n.Scene.Discovery.Tabs.forYou }
|
||||
var tabItem: TMBarItemable {
|
||||
return TMBarItem(title: tabItemTitle)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,18 +6,12 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
|
||||
final class HeightFixedSearchBar: UISearchBar {
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: CGFloat.greatestFiniteMagnitude, height: 36)
|
||||
}
|
||||
}
|
||||
import Pageboy
|
||||
|
||||
final class SearchViewController: UIViewController, NeedsDependency {
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
|
@ -30,8 +24,7 @@ final class SearchViewController: UIViewController, NeedsDependency {
|
|||
|
||||
// use AutoLayout could set search bar margin automatically to
|
||||
// layout alongside with split mode button (on iPad)
|
||||
let titleViewContainer = UIView()
|
||||
let searchBar = HeightFixedSearchBar()
|
||||
let searchBar = UISearchBar()
|
||||
|
||||
// value is the initial search text to set
|
||||
let searchBarTapPublisher = PassthroughSubject<String, Never>()
|
||||
|
@ -46,11 +39,34 @@ final class SearchViewController: UIViewController, NeedsDependency {
|
|||
coordinator: coordinator,
|
||||
authContext: authContext
|
||||
)
|
||||
viewController.delegate = self
|
||||
return viewController
|
||||
}()
|
||||
}
|
||||
|
||||
extension SearchViewController {
|
||||
let segmentedControl: UISegmentedControl
|
||||
let segmentedControlBackground: UIView
|
||||
|
||||
init() {
|
||||
segmentedControl = UISegmentedControl(items: [
|
||||
L10n.Scene.Discovery.Tabs.posts,
|
||||
L10n.Scene.Discovery.Tabs.hashtags,
|
||||
L10n.Scene.Discovery.Tabs.news,
|
||||
L10n.Scene.Discovery.Tabs.forYou
|
||||
])
|
||||
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
|
||||
segmentedControl.selectedSegmentIndex = 0
|
||||
|
||||
segmentedControlBackground = UIView()
|
||||
segmentedControlBackground.translatesAutoresizingMaskIntoConstraints = false
|
||||
segmentedControlBackground.backgroundColor = .systemBackground
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
segmentedControl.addTarget(self, action: #selector(SearchViewController.segmentedControlValueChanged(_:)), for: .valueChanged)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
@ -59,26 +75,40 @@ extension SearchViewController {
|
|||
title = L10n.Scene.Search.title
|
||||
|
||||
setupSearchBar()
|
||||
guard let discoveryViewController = self.discoveryViewController else { return }
|
||||
guard let discoveryViewController else { return }
|
||||
|
||||
segmentedControlBackground.addSubview(segmentedControl)
|
||||
view.addSubview(segmentedControlBackground)
|
||||
|
||||
|
||||
addChild(discoveryViewController)
|
||||
discoveryViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(discoveryViewController.view)
|
||||
discoveryViewController.view.pinToParent()
|
||||
discoveryViewController.didMove(toParent: self)
|
||||
|
||||
let constraints = [
|
||||
segmentedControl.topAnchor.constraint(equalTo: segmentedControlBackground.topAnchor, constant: 8),
|
||||
segmentedControl.leadingAnchor.constraint(equalTo: segmentedControlBackground.leadingAnchor, constant: 8),
|
||||
segmentedControlBackground.trailingAnchor.constraint(equalTo: segmentedControl.trailingAnchor, constant: 8),
|
||||
segmentedControlBackground.bottomAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8),
|
||||
|
||||
segmentedControlBackground.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
||||
segmentedControlBackground.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
|
||||
view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: segmentedControlBackground.trailingAnchor),
|
||||
|
||||
discoveryViewController.view.topAnchor.constraint(equalTo: segmentedControlBackground.bottomAnchor),
|
||||
discoveryViewController.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
|
||||
view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: discoveryViewController.view.trailingAnchor),
|
||||
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: discoveryViewController.view.bottomAnchor),
|
||||
]
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
viewModel?.viewDidAppeared.send()
|
||||
|
||||
// note:
|
||||
// need set alpha because (maybe) SDK forget set alpha back
|
||||
titleViewContainer.alpha = 1
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
searchBar.scopeBarBackgroundImage = .placeholder(color: .systemBackground)
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController {
|
||||
private func setupAppearance() {
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
|
||||
|
@ -97,13 +127,8 @@ extension SearchViewController {
|
|||
private func setupSearchBar() {
|
||||
searchBar.placeholder = L10n.Scene.Search.SearchBar.placeholder
|
||||
searchBar.delegate = self
|
||||
searchBar.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleViewContainer.addSubview(searchBar)
|
||||
searchBar.pinToParent()
|
||||
searchBar.setContentHuggingPriority(.required, for: .horizontal)
|
||||
searchBar.setContentHuggingPriority(.required, for: .vertical)
|
||||
navigationItem.titleView = titleViewContainer
|
||||
// navigationItem.titleView = searchBar
|
||||
searchBar.sizeToFit()
|
||||
navigationItem.titleView = searchBar
|
||||
|
||||
searchBarTapPublisher
|
||||
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false)
|
||||
|
@ -122,6 +147,11 @@ extension SearchViewController {
|
|||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
@objc
|
||||
private func segmentedControlValueChanged(_ sender: UISegmentedControl) {
|
||||
discoveryViewController?.scrollToPage(.at(index: sender.selectedSegmentIndex), animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UISearchBarDelegate
|
||||
|
@ -152,3 +182,22 @@ extension SearchViewController: ScrollViewContainer {
|
|||
discoveryViewController?.scrollToTop(animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - PageboyViewControllerDelegate
|
||||
extension SearchViewController: PageboyViewControllerDelegate {
|
||||
func pageboyViewController(_ pageboyViewController: Pageboy.PageboyViewController, didReloadWith currentViewController: UIViewController, currentPageIndex: Pageboy.PageboyViewController.PageIndex) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func pageboyViewController(_ pageboyViewController: Pageboy.PageboyViewController, didScrollTo position: CGPoint, direction: Pageboy.PageboyViewController.NavigationDirection, animated: Bool) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func pageboyViewController(_ pageboyViewController: PageboyViewController, willScrollToPageAt index: PageboyViewController.PageIndex, direction: PageboyViewController.NavigationDirection, animated: Bool) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func pageboyViewController(_ pageboyViewController: PageboyViewController, didScrollToPageAt index: PageboyViewController.PageIndex, direction: PageboyViewController.NavigationDirection, animated: Bool) {
|
||||
segmentedControl.selectedSegmentIndex = index
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ final class SearchViewModel: NSObject {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext?
|
||||
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UICollectionViewDiffableDataSource<SearchSection, SearchItem>?
|
||||
|
|
Loading…
Reference in New Issue