現在のNavigation ControllerのTab Barアイコンをタップすると、ユーザーはすでにルートビューに戻りますが、スクロールダウンした場合、もう一度タップすると一番上にスクロールします(ステータスバーをタップしたのと同じ効果)。どうすればいいですか?
良い例は、Instagramのフィードです。下にスクロールしてから、タブバーのホームアイコンをタップして、トップに戻ります。
一番上に戻るスクロールは簡単ですが、Tab Bar Controllerに接続することは私が行き詰まっていることです。
UITabBarControllerDelegate
メソッドを実装するtabBarController:didSelectViewController:
ユーザーがタブを選択したときに通知されます。このメソッドは、同じタブが既に選択されている場合でも、同じタブボタンが再度タップされたときにも呼び出されます。
このdelegate
を実装する適切な場所は、おそらくAppDelegate
です。または、Tab Bar Controllerを論理的に「所有する」オブジェクト。
View ControllerでUICollectionView
をスクロールするために呼び出すことができるメソッドを宣言して実装します。
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
static UIViewController *previousController = nil;
if (previousController == viewController) {
// the same tab was tapped a second time
if ([viewController respondsToSelector:@selector(scrollToTop)]) {
[viewController scrollToTop];
}
}
previousController = viewController;
}
スイフト3
ここに行きます。
最初にUITabBarControllerDelegate
をクラスに実装し、デリゲートがviewDidLoadに設定されていることを確認します
class DesignStoryStreamVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UITabBarControllerDelegate {
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
collectionView.delegate = self
collectionView.dataSource = self
}
}
次に、このデリゲート関数をクラスのどこかに配置します。
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
print(tabBarIndex)
if tabBarIndex == 0 {
self.collectionView.setContentOffset(CGPoint.zero, animated: true)
}
}
「if」ステートメントで正しいインデックスを選択してください。印刷機能を追加したので、再確認できます。
このビュー階層を使用していました。
UITabBarController > UINavigationController > UIViewController
UITabBarController
のUIViewController
への参照を得ました
tabBarControllerRef = self.tabBarController as! CustomTabBarClass
tabBarControllerRef!.navigationControllerRef = self.navigationController as! CustomNavigationBarClass
tabBarControllerRef!.viewControllerRef = self
次に、正しい時間に呼び出されるBool
と、上にスムーズにスクロールできるメソッドを作成しました
var canScrollToTop:Bool = true
// Called when the view becomes available
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
canScrollToTop = true
}
// Called when the view becomes unavailable
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
canScrollToTop = false
}
// Scrolls to top nicely
func scrollToTop() {
self.collectionView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}
次に、UITabBarController
カスタムクラスでこれを呼び出しました
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
// Allows scrolling to top on second tab bar click
if (viewController.isKindOfClass(CustomNavigationBarClass) && tabBarController.selectedIndex == 0) {
if (viewControllerRef!.canScrollToTop) {
viewControllerRef!.scrollToTop()
}
}
}
結果はInstagramとTwitterのフィードと同じです:)
Swift 3のアプローチ::
//MARK: Properties
var previousController: UIViewController?
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if self.previousController == viewController || self.previousController == nil {
// the same tab was tapped a second time
let nav = viewController as! UINavigationController
// if in first level of navigation (table view) then and only then scroll to top
if nav.viewControllers.count < 2 {
let tableCont = nav.topViewController as! UITableViewController
tableCont.tableView.setContentOffset(CGPoint(x: 0.0, y: -tableCont.tableView.contentInset.top), animated: true)
}
}
self.previousController = viewController;
return true
}
ここにいくつかの注意事項があります。「didSelect」の代わりに「shouldSelect」。後者はviewController local varが既に変更されていることを意味するため、後者が移行後に行われるためです。 2.スクロール(またはそうでない)アクションに関するナビゲーションのView Controllerの情報を取得するために、コントローラーを変更する前にイベントを処理する必要があります。
説明::現在のビューが実際にリスト/テーブルビューコントローラーである場合、上にスクロールします。ナビゲーションが進んで同じタブバーをタップした場合、望ましいアクションは1ステップだけをポップすること(デフォルトの機能)であり、一番上までスクロールしないことです。ナビゲーションが高度な意味を持たない場合、まだテーブル/リストコントローラーにいますそのときのみもう一度タップしたときに上にスクロールします。 (Facebookはユーザーのプロフィールから「フィード」をタップするときに同じことを行います。トップにスクロールせずにフィードに戻るだけです。
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: self.view)
}
}
これは、Swift 3。
shouldSelect
ではなくdidSelect
を使用できます。これにより、以前のView Controllerを追跡するための外部変数が不要になります。
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if ([viewController isEqual:self] && [tabBarController.selectedViewController isEqual:viewController]) {
// Do custom stuff here
}
return YES;
}
この実装では、静的変数は不要および以前のView Controllerの状態
UINavigationControllerのUITableViewControllerの場合、プロトコルと機能を実装できます。
protocol ScrollableToTop {
func scrollToTop()
}
extension UIScrollView {
func scrollToTop(_ animated: Bool) {
var topContentOffset: CGPoint
if #available(iOS 11.0, *) {
topContentOffset = CGPoint(x: -safeAreaInsets.left, y: -safeAreaInsets.top)
} else {
topContentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top)
}
setContentOffset(topContentOffset, animated: animated)
}
}
次に、UITableViewControllerで:
class MyTableViewController: UITableViewController: ScrollableToTop {
func scrollToTop() {
if isViewLoaded {
tableView.scrollToTop(true)
}
}
}
次に、UITabBarControllerDelegateで:
extension MyTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard tabBarController.selectedViewController === viewController else { return true }
guard let navigationController = viewController as? UINavigationController else {
assertionFailure()
return true
}
guard
navigationController.viewControllers.count <= 1,
let destinationViewController = navigationController.viewControllers.first as? ScrollableToTop
else {
return true
}
destinationViewController.scrollToTop()
return false
}
}
Swiftこれは動作します。
var previousController: UIViewController?
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
if previousController == viewController {
if let navVC = viewController as? UINavigationController, vc = navVC.viewControllers.first as? UICollectionViewController {
vc.collectionView?.setContentOffset(CGPointZero, animated: true)
}
}
previousController = viewController;
}
プロジェクトで自由に再利用できるプラグアンドプレイUITabBarControllerを実装しました。最上部へのスクロール機能を有効にするには、サブクラスのみを使用する必要があります。
ストーリーボードでもすぐに使えるはずです。
コード:
/// A UITabBarController subclass that allows "scroll-to-top" gestures via tapping
/// tab bar items. You enable the functionality by simply subclassing.
class ScrollToTopTabBarController: UITabBarController, UITabBarControllerDelegate {
/// Determines whether the scrolling capability's enabled.
var scrollEnabled: Bool = true
private var previousIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
/*
Always call "super" if you're overriding this method in your subclass.
*/
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
guard scrollEnabled else {
return
}
guard let index = viewControllers?.indexOf(viewController) else {
return
}
if index == previousIndex {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), { [weak self] () in
guard let scrollView = self?.iterateThroughSubviews(self?.view) else {
return
}
dispatch_async(dispatch_get_main_queue(), {
scrollView.setContentOffset(CGPointZero, animated: true)
})
})
}
previousIndex = index
}
/*
Iterates through the view hierarchy in an attempt to locate a UIScrollView with "scrollsToTop" enabled.
Since the functionality relies on "scrollsToTop", it plugs easily into existing architectures - you can
control the behaviour by modifying "scrollsToTop" on your UIScrollViews.
*/
private func iterateThroughSubviews(parentView: UIView?) -> UIScrollView? {
guard let view = parentView else {
return nil
}
for subview in view.subviews {
if let scrollView = subview as? UIScrollView where scrollView.scrollsToTop == true {
return scrollView
}
if let scrollView = iterateThroughSubviews(subview) {
return scrollView
}
}
return nil
}
}
編集(2016年8月9日):
デフォルトのリリース構成(アーカイブ)でコンパイルしようとすると、コンパイラーは再帰関数でキャプチャーされた多数のクロージャーを作成する可能性を許可しないため、コンパイルされません。クロージャーを使用せずに「scrollsToTop」をtrueに設定して最初に見つかったUIScrollViewを返すようにコードを変更しました。
scrollRectToVisible
メソッドはsetContentOffset
よりも優れていることがわかりました。
Swift:
デリゲートからタブバーをクリックすると、次のようになります。
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
if (viewController.isKindOfClass(SomeControllerClass) && tabBarController.selectedIndex == 0)
{
viewController.scrollToTop()
}
}
コントローラー内のscrollToTop
関数の場合:
func scrollToTop()
{
self.tableView.scrollRectToVisible(CGRectMake(0,0,CGRectGetWidth(self.tableView.frame), CGRectGetHeight(self.tableView.frame)), animated: true)
}