Navigation Bar Jumping After Transition - ios

I am making an app that uses a tab bar to navigate between view controllers. I wanted to add a transition effect that would cross dissolve between each view when a tab button was pressed. I have implemented this transition with UIView.transitionFromView, however the navigation bar is not working as expected during the transition. During a transition to a view for the first time, the navigation bar is displayed too high, but jumps back into place once the transitions is complete. However, the next time you switch to the same view, the navigation bar is in the correct place during and after the transition.
I have seen an answer here to fix the problem for a custom animation, but I could not figure out how to get it to work with my current implementation.
MY Question
I have seen answers fixing the issue by forcing the view down by a few points (44 points), but is there a way to do it without directly changing the points? This might work the first time, but the issue resolves itself when any view is transitioned to a second time, thus making the view too low if you change the points.
Here is my code for the tab bar controller and the transition:
import UIKit
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Method used to detect when a tab bar button has been tapped
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
// Creating the 'to' and 'from' views for the transition
let fromView = tabBarController.selectedViewController!.view
let toView = viewController.view
if fromView == toView {
// If views are the same, then don't do a transition
return false
}
self.view.userInteractionEnabled = false
UIView.transitionFromView(fromView, toView: toView, duration: 2.0, options: .TransitionCrossDissolve, completion: nil)
self.view.userInteractionEnabled = true
return true
}
}
And here is what the issue looks like:

You can try with this code:
import UIKit
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Method used to detect when a tab bar button has been tapped
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
// Creating the 'to' and 'from' views for the transition
let fromView = tabBarController.selectedViewController!.view
let toView = viewController.view
if fromView == toView {
// If views are the same, then don't do a transition
return false
}
self.view.userInteractionEnabled = false
if let window = fromView.window {
let overlayView = UIScreen.mainScreen().snapshotViewAfterScreenUpdates(false)
viewController.view.addSubview(overlayView)
UIView.transitionFromView(fromView, toView: toView, duration: 2.0, options: .TransitionCrossDissolve, completion: { (finish) in
window.rootViewController = viewController
UIView.animateWithDuration(0.4, delay: 0.0, options: .TransitionCrossDissolve, animations: {
overlayView.alpha = 0
}, completion: { (finish) in
overlayView.removeFromSuperview()
})
})
}
self.view.userInteractionEnabled = true
return true
}
}

In my case, call toView.layoutIfNeeded() before the transition fixed the issue.

Related

Strange navigation bar animation when transitioning to a child UINavigationController

I have created a small project to replicate this problem.
The only file is this one...
Brief bit of code
class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
showBlue()
}
#objc func showBlue() {
let vc = UIViewController()
vc.view.backgroundColor = .blue
let nvc = UINavigationController(rootViewController: vc)
vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(showGreen))
transition(to: nvc)
}
#objc func showGreen() {
let vc = UIViewController()
vc.view.backgroundColor = .green
let nvc = UINavigationController(rootViewController: vc)
vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(showBlue))
transition(to: nvc)
}
func transition(to toVC: UIViewController) {
if let fromVC = children.first {
transitionWithAnimation(fromVC: fromVC, toVC: toVC)
} else {
addWithoutAnimation(child: toVC)
}
}
func addWithoutAnimation(child toVC: UIViewController) {
addChild(toVC)
view.addSubview(toVC.view)
toVC.view.frame = view.bounds
toVC.didMove(toParent: self)
}
func transitionWithAnimation(fromVC: UIViewController, toVC: UIViewController) {
addChild(toVC)
toVC.view.frame = view.bounds
fromVC.willMove(toParent: nil)
transition(
from: fromVC,
to: toVC,
duration: 1.0,
options: .transitionCrossDissolve,
animations: nil) { _ in
fromVC.removeFromParent()
toVC.didMove(toParent: self)
}
}
}
Explaining the code
The RootViewController first does showBlue. This adds a child UINavigationController with a rootViewController with a blue background. The blue view controller has a Done button that then targets showGreen.
showGreen transitions to a UINavigationController with a green background and a Done button that targets showBlue.
What I expected
What I expected (and what I want to happen) is for the navigation bar to cross dissolve in place without resizing.
Animation of the problem
The problem is that during the animated transition the navigation bar has a strange animation to it. Which you can see here...
Apple documentation around this
All the code is followed exactly from the Apple documentation about adding child view controllers to a custom container view controller... https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
Things I tried
I have also tried by using AutoLayout constraints rather than setting the view's frame directly but this didn't change anything.
I've tried running view.setNeedsLayout and then view.layoutIfNeeded() on the new view controller's view but that doesn't seem to have fixed it either.
No strange animation if child is not UINavigationController
The really odd thing is that if you use any other type of view controller (other than UINavigationController) then this animation glitch doesn't happen. For example: If one of the view controllers is a UITabBarController then the tabs do not have this odd animation. Even stranger, if the tab contains a UINavigationController then it doesn't have this animation either. It's literally just if the direct child is a UINavigationController.
Has anyone experienced this before? And did you manage to stop the strange animation?
If you place the transition code within a CATransaction and use the kCATransactionDisableActions key to turn off implicit actions it will resolve the issue:
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey:kCATransactionDisableActions)
transition(
from: fromVC,
to: toVC,
duration: 1.0,
options: [.transitionCrossDissolve],
animations: nil) { _ in
fromVC.removeFromParent()
toVC.didMove(toParent: self)
}
CATransaction.commit()

How to change tab bar programmatically with animation?

I'm trying to change the tab bar in my app programmatically with an animation.
In my tab bar delegate class, I currently have this, which I obtained from this thread.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let fromView = selectedViewController?.view, let toView = viewController.view else {
return false
}
UIView.transition(from: fromView, to: toView, duration: 0.3, options: [.transitionCrossDissolve], completion: nil)
return true
}
The above animates tab bar changes when the user taps, but does not work for when the tab bar is changed programmatically, like in this case:
// code in another class
self.tabBarController?.selectedIndex = 2 // does not animate
I've read this thread that poses a similar question, but it's written in objective-c and from 4 years ago.
Is there any method that could animate programmatic tab bar changes?
As a workaround you could trigger your animation manually. I don't know if it is recommended but it is working for me.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
animateTabBarChange(tabBarController: tabBarController, to: viewController)
return true
}
func animateTabBarChange(tabBarController: UITabBarController, to viewController: UIViewController) {
let fromView: UIView = tabBarController.selectedViewController!.view
let toView: UIView = viewController.view
// do whatever animation you like
}
Then you call it like this:
let index = 2
animateTabBarChange(tabBarController: self.tabBarController!, to: self.tabBarController!.viewControllers![index])
self.tabBarController?.selectedIndex = index

UINavigationController Custom transition issue

I followed many tutorials about how to create custom transitions but most of them apply to the presentation and dismissing of the view controllers. I just want to apply custom transitions to the UINavigationController for the push/pop actions.
After reading apple documentation I created simple project to test if it works.
Here is all my code for custom Navigation Controller:
class CustomNC: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension CustomNC: UINavigationControllerDelegate {
func navigationController(
navigationController: UINavigationController,
animationControllerForOperation operation: UINavigationControllerOperation,
fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("Creating UIViewControllerAnimatedTransitioning for operation: \(operation.rawValue)")
return self
}
}
extension CustomNC: UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
print("Returning duration for transition")
return 1.0
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
print("Animating transition")
let container = transitionContext.containerView()!
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// set up from 2D transforms that we'll use in the animation
let offScreenRight = CGAffineTransformMakeTranslation(container.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(-container.frame.width, 0)
// start the toView to the right of the screen
toView.transform = offScreenRight
// add the both views to our view controller
container.addSubview(toView)
container.addSubview(fromView)
// get the duration of the animation
// DON'T just type '0.5s' -- the reason why won't make sense until the next post
// but for now it's important to just follow this approach
let duration = self.transitionDuration(transitionContext)
// perform the animation!
// for this example, just slid both fromView and toView to the left at the same time
// meaning fromView is pushed off the screen and toView slides into view
// we also use the block animation usingSpringWithDamping for a little bounce
UIView.animateWithDuration(
duration,
delay: 0.0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.8,
options: [],
animations: {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
}
Then I simply create two default view controllers First and Second. First has UIButton with show action to the Second. First is embedded in NavigationController and this NavigationController has Custom Class set to CustomNC.
After running I see V1 controller but when I tap UIButton application crash without any error message. In Logs I see only those lines:
Creating UIViewControllerAnimatedTransitioning for operation: 1
Returning duration for transition
(lldb)
So as I understand the delegate is visible and navigation controller is using this delegate properly. When transition starts it gets the animation duration time interval but animateTransition method is never called because I don't see "Animating transition" message in log.
Can somebody point me where I have problem?

Custom UIViewController transition bug in iOS 9

I encountered a strange bug. I am just using iOS's custom transitioning method for UIViewControllers using UIViewControllerTransitioningDelegate together with an implementation of UIViewControllerAnimatedTransitioning. It all seems to work fine, until I do exactly the following:
open the app
present another view controller with my custom transition
rotate to landscape
dismiss the just presented view controller
That's all! What happens now is the following: I see a large black bar on the right side of the initial view controller (as if that controller's view wasn't rotated to landscape).
The funny thing is this only goes wrong in iOS 9, in iOS 8 everything seems to work just fine. Did anything change with custom transition API I don't know of? Or is this simply a really nasty iOS 9 bug? If anyone can tell me what I did wrong or if anyone can provide me with a workaround I would really appreciate that!
These classes reproduce the problem:
import UIKit
class ViewController: UIViewController, UIViewControllerTransitioningDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tap")
view.addGestureRecognizer(tapGestureRecognizer)
}
func tap() {
let controller = ModalViewController()
controller.transitioningDelegate = self
presentViewController(controller, animated: true, completion: nil)
}
func animationControllerForPresentedController(presented: UIViewController,
presentingController presenting: UIViewController,
sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Transitioning()
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Transitioning()
}
}
The presented view controller:
import UIKit
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.redColor()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tap")
view.addGestureRecognizer(tapGestureRecognizer)
}
func tap() {
dismissViewControllerAnimated(true, completion: nil)
}
}
And finally the UIViewControllerAnimatedTransitioning implementation:
import UIKit
class Transitioning: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
let containerView = transitionContext.containerView()
if let fromView = fromView, toView = toView {
containerView?.addSubview(fromView)
containerView?.addSubview(toView)
toView.alpha = 0
UIView.animateWithDuration(0.5, animations: {
toView.alpha = 1
}, completion: {
finished in
transitionContext.completeTransition(true)
})
}
}
}
I generally use the following in animateTransition:
toView.frame = fromView.frame
FYI, you don't have to add fromView to the hierarchy, as it's already there.

Tap tab bar to scroll to top of UITableViewController

Tapping the tab bar icon for the current navigation controller already returns the user to the root view, but if they are scrolled way down, if they tap it again I want it to scroll to the top (same effect as tapping the status bar). How would I do this?
A good example is Instagram's feed, scroll down then tap the home icon in the tab bar to scroll back to top.
The scrolling back to the top is easy, but connecting it to the tab bar controller is what I'm stuck on.
Implement the UITabBarControllerDelegate method tabBarController:didSelectViewController: to be notified when the user selects a tab. This method is also called when the same tab button is tapped again, even if that tab is already selected.
A good place to implement this delegate would probably be your AppDelegate. Or the object that logically "owns" the tab bar controller.
I would declare and implement a method that can be called on your view controllers to scroll the 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;
}
SWIFT 3
Here goes..
First implement the UITabBarControllerDelegate in the class and make sure the delegate is set in 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
}
}
Next, put this delegate function somewhere in your class.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
print(tabBarIndex)
if tabBarIndex == 0 {
self.collectionView.setContentOffset(CGPoint.zero, animated: true)
}
}
Make sure to select the correct index in the "if" statement. I included the print function so you can double check.
Swift 5: no need for stored properties in the UITabBarController.
In MyTabBarController.swift, implement tabBarController(_:shouldSelect) to detect when the user re-selects the tab bar item:
protocol TabBarReselectHandling {
func handleReselect()
}
class MyTabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
func tabBarController(
_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController
) -> Bool {
if tabBarController.selectedViewController === viewController,
let handler = viewController as? TabBarReselectHandling {
// NOTE: viewController in line above might be a UINavigationController,
// in which case you need to access its contents
handler.handleReselect()
}
return true
}
}
In MyTableViewController.swift, handle the re-selection by scrolling the table view to the top:
class MyTableViewController: UITableViewController, TabBarReselectHandling {
func handleReselect() {
tableView?.setContentOffset(.zero, animated: true)
}
}
Now you can easily extend this to other tabs by just implementing TabBarReselectHandling.
You can use shouldSelect rather than didSelect, which would omit the need for an external variable to keep track of the previous view controller.
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if ([viewController isEqual:self] && [tabBarController.selectedViewController isEqual:viewController]) {
// Do custom stuff here
}
return YES;
}
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)
}
}
This is my answer in Swift 3. It uses a helper function for recursive calls and it automatically scrolls to top on call. Tested on a UICollectionViewController embedded into a UINavigationController embedded in a UITabBarController
I was using this View hierarchy.
UITabBarController > UINavigationController > UIViewController
I got a reference to the UITabBarController in the UIViewController
tabBarControllerRef = self.tabBarController as! CustomTabBarClass
tabBarControllerRef!.navigationControllerRef = self.navigationController as! CustomNavigationBarClass
tabBarControllerRef!.viewControllerRef = self
Then I created a Bool that was called at the correct times, and a method that allows scrolling to top smoothly
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)
}
Then in my UITabBarController Custom Class I called this
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()
}
}
}
The Result is identical to Instagram and Twitter's feed :)
Swift 3 approach::
//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
}
A few notes here::
"shouldSelect" instead of "didSelect" because the latter is taking place after transition, meaning viewController local var already changed.
2. We need to handle the event before changing controller, in order to have the information of navigation's view controllers regarding scrolling (or not) action.
Explanation:: We want to scroll to top, if current view is actually a List/Table view controller. If navigation has advanced and we tap same tab bar, desired action would be to just pop one step (default functionality) and not scroll to top. If navigation hasn't advanced meaning we are still in table/list controller then and only then we want to scroll to top when tapping again. (Same thing Facebook does when tapping "Feed" from a user's profile. It only goes back to feed without scrolling to top.
In this implementation you no need static variable and previous view controller state
If your UITableViewController in UINavigationController you can implement protocol and function:
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)
}
}
Then in your UITableViewController:
class MyTableViewController: UITableViewController: ScrollableToTop {
func scrollToTop() {
if isViewLoaded {
tableView.scrollToTop(true)
}
}
}
Then in 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
}
}
I have a collection view embedded in a navigation controller, in Swift this works.
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;
}
I've implemented a plug & play UITabBarController that you can freely re-use in your projects. To enable the scroll-to-top functionality, you should just have to use the subclass, nothing else.
Should work out of the box with Storyboards also.
Code:
/// 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
}
}
Edit (09.08.2016):
After attempting to compile with the default Release configuration (archiving) the compiler would not allow the possibility of creating a large number of closures that were captured in a recursive function, thus it would not compile. Changed out the code to return the first found UIScrollView with "scrollsToTop" set to true without using closures.
I tried the solution given by #jsanabria. This worked well on a fixed tableview, but it wouldn't work for my infinite scroll tableview. It only came up the table view about halfway after loading the new scrolling data.
Swift 5.0+
self.tableView.scrollToRow(at: IndexPath.init(row: 0, section: 0), at: UITableView.ScrollPosition(rawValue: 0)!, animated: true)
TESTED SOLUTION IN SWIFT
STEP 1
In your main tabbarcontroller class declare
weak static var previousController: UIViewController?
STEP 2
In viewdidLoad() set
MainTabBarViewController.previousController = viewControllers?[0]
STEP 3
extension MainTabBarViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if MainTabBarViewController.previousController == viewController {
/// here comes your code
}
MainTabBarViewController.previousController = viewController
}
}
I found the scrollRectToVisible method works better than the setContentOffset.
Swift:
After you catch the click on the tab bar from the delegate, something like below:
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
if (viewController.isKindOfClass(SomeControllerClass) && tabBarController.selectedIndex == 0)
{
viewController.scrollToTop()
}
}
Now for the scrollToTop function inside the controller:
func scrollToTop()
{
self.tableView.scrollRectToVisible(CGRectMake(0,0,CGRectGetWidth(self.tableView.frame), CGRectGetHeight(self.tableView.frame)), animated: true)
}

Resources