I want to implement a scrollToTop method on all of my viewControllers in my UITabBarController. The following is a method in the UITabBarControllerDelegate and triggers, when I select a tab.
The problem is, that I only want to scroll to the top of the viewController, when the viewController is active. So that the user can switch tabs without losing the scroll position, but when he touches the tab in the tabBar of the currently active tab, it should scroll to the top.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if viewControllerThatIsCurrentlyActiveInTabBar == viewController {
scrollToTop()
}
}
Basically, I need that condition of the if statement above.
I tried: viewController.isViewLoaded, tabBarController.selectedViewController == viewController, viewController.isBeingPresented. None of those conditions worked. It would either not trigger scrollToTop() or it would trigger always so that you lose the scroll position when you change tabs because it would immediately scroll to the top.
You need to make a code in should select instead of didselect. As it is unable to find the previous controller after selection. below is the example code for it.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if tabBarController.selectedViewController == viewController {
print("Same viewcontroller")
}
return true
}
Can you use below extension for getting top viewcontroller of tabbarcontroller.
extension UIViewController {
var top: UIViewController? {
if let controller = self as? UINavigationController {
return controller.topViewController?.top
}
if let controller = self as? UISplitViewController {
return controller.viewControllers.last?.top
}
if let controller = self as? UITabBarController {
return controller.selectedViewController?.top
}
if let controller = presentedViewController {
return controller.top
}
return self
}
}
You can use above extension below
if let rootViewController = UIApplication.top() {
//do with Active view controller
}
Related
I have a Swift application with 4 bottom tabs. In the home tab I have a video running. In the 4th tab I have a favorites list.
When the user changes tabs, if he's on the home screen, the video should stop. Also, when the user taps on the 4th tab, the favorites list should update so that the user can see the recent additions.
I have the following on the home tab view controller:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
print("TAB CHANGED")
if let jwp = jwPlayer {
jwp.stop()
}
}
and this on the favorites tab view controller:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 3 {
let userId = UserDefaults.standard.integer(forKey: "userId")
favoritesVC.updateData(with: userId)
}
}
Both conform to UITabBarControllerDelegate. Both include:
self.tabBarController?.delegate = self
When I launch the app, the video starts playing, I tap on any tab and the video stops. No matter which tab I tap on I see the message "TAB CHANGED". But as soon as I tap on the favorites tab and I move to another, I stop seeing the "TAB CHANGED" message. If I then move to the home screen and play the video and then move to a different tab, the video no longer stops.
The didSelect on the 4th tab is cancelling the didSelect on the 1st tab.
How can I get both of them to work? I have placed them on both view controllers because on the first one I need to reference the video and on the 2nd one I need to reference the list view controller (the 4th view controller actually has two top tabs which switch between a favorites vc and a downloads vc).
UPDATE:
I moved the delegate to the TabBarController subclass. I added the following:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
let hvc = HomeViewController()
if tabBarIndex != 0 {
hvc.stopPlayer()
}
}
I added
delegate = self
and
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
In the HomeViewController I added this:
func stopPlayer() {
print("TAB CHANGED")
if let jwp = jwPlayer {
jwp.stop()
}
}
however when I change tabs, jwPlayer is always nil.
It seems like you are handling the UITabBarControllerDelegate calbacks in multiple places and for that to happen, you are also changing following multiple times -
self.tabBarController?.delegate = self
Here's what you should do -
Handle UITabBarControllerDelegate in one place.
Dispatch the necessary work calls from there to relevant screens.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if tabBarIndex == 0 {
homeVC.startPlayer()
}
else {
homeVC.stopPlayer()
if tabBarIndex == 3 {
let userId = UserDefaults.standard.integer(forKey: "userId")
favoritesVC.updateData(with: userId)
}
}
}
This can be handled anywhere, inside your TabBarController subclass if you have one, or any other object that is guaranteed to exist for the application lifetime.
TabBarController’s delegate does not have to be either of the view controllers. It could be some other object (not even a view controller) that could hold weak references to both view controllers, for example, or post a notification etc
I have a tableview which has load more. I'd like to add scroll to top function when user press tabbar item twice like twitter, instagram.
This is my code when user tap twice to tab bar item.
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if previousController == viewController {
if let navVC = viewController as? UINavigationController, let vc = navVC.viewControllers.first as? AssistantMainViewController {
if vc.isViewLoaded && (vc.view.window != nil) {
// viewController is visible
vc.tableView.setContentOffset(CGPoint(x: 0, y: -173), animated: true)
}
}
}
previousController = viewController
}
It works fine when tableview first load. The problem is when I load more cell to my tableview and scroll to top, tableview not scrolls to top it stuck in middle of somewhere. Other weird thing is if I scroll manually to top after load more, tab bar item scroll tableview properly until load more cells.
In load more action basicly app get more content from server and add them to array and call tableview.reloadData()
adviceDataSource.loadMoreContent(beforeDay: beforeDate, success: {feedCount in
if feedCount == 0 {
self.didGetLastPage = true
}
self.tableView.reloadData()
self.tableView.layoutSubviews()
self.isRunningRequest = false
}, error: { (errorString, statusCode) in
self.isRunningRequest = false
}) { (MoyaError) in
self.isRunningRequest = false
}
Probably the problem is when I load more cell tableview doesn't know its new content size.By the way when tap status bar tableview scroll to top perfectly. If you know the func that called when user tap status bar, it would be perfect for me.
I've been working and searching on it 2 days, any help?
Thank you!
call UITabBarControllerDelegate in your viewcontroller tableView class.
in viewdidload Method call
self.tabBarController?.delegate = self
implement tab bar didselect in your viewcontroller class
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
self.tableView.setContentOffset(CGPoint.zero, animated: true)
}
}
Replace tableView.layoutSubviews() with tableView.layoutIfNeeded(), it layout the tableview immediately.
And use the tabbarcontrollerdelegate:
The viewcontroller(which owns the tableview) will listen to it. and use this method
self.primaryTableView.scrollToRow(at: IndexPath, at: UITableViewScrollPosition, animated: Bool)
I have an item on my tabBar which I don't want to actually move to its view controller but instead when that item is clicked something happens (a popup dialog appears over the current view controller).
I currently have the current code:
class TabViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// tell our UITabBarController subclass to handle its own delegate methods
self.delegate = self
}
// called whenever a tab button is tapped
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if viewController is PostTabViewController {
... code here ...
}
}
}
The code at ..code here... runs just fine however the PostTabViewController is still shown. How would I go about stopping it?
You should do your checks in tabBarController(_, shouldSelect:)
func tabBarController(UITabBarController, shouldSelect: UIViewController) -> Bool {
guard viewController is PostTabViewController else {
return true
}
... code here ...
return false
}
I want to scroll to the top when i double tap on any BarButtonItem.
I saw a lot of answers on stackOverflow but none of them worked for me.
Maybe I'm using it wrong? Where do i put the code in the AppDelegate or the TableViewControllers i want to add this functionality specifically?
anyway, I'm using Swift 2.3 and Xcode 8 and would love to get some help.
Thank you.
Do you know about scrollsToTop? I think it is what you need. Description from iOS SDK related to scrollsToTop property in UIScrollView:
When the user taps the status bar, the scroll view beneath the touch which is closest to the status bar will be scrolled to top, but only if its scrollsToTop property is YES, its delegate does not return NO from shouldScrollViewScrollToTop, and it is not already at the top.
// On iPhone, we execute this gesture only if there's one on-screen scroll view with scrollsToTop == YES. If more than one is found, none will be scrolled.
At first setup UITabBarControllerDelegate delegate. Delegate can be easily set from Storyboard or via code with UITabBarController's delegate property.
myTabBar.delegate = myNewDelegate // Have to conform to UITabBarControllerDelegate protocol
You can even subclass UITabBarController and implement UITabBarControllerDelegate for it so it can become delegate for itself.
mySubclassedTabBar.delegate = mySubclassedTabBar
When you have delegate you can try out this method.
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool
{
guard let tabBarControllers = tabBarController.viewControllers
else
{
// TabBar have no configured controllers
return true
}
if let newIndex = tabBarControllers.indexOf(viewController) where newIndex == tabBarController.selectedIndex
{
// Index won't change so we can scroll
guard let tableViewController = viewController as? UITableViewController // Or any other condition
else
{
// We are not in UITableViewController
return true
}
tableViewController.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: .Top, animated: true)
}
return true
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if tabBarController.viewControllers!.firstIndex(of: viewController) == 0 {
if self.selectedIndex != 0 { return true }
if let navigationController = viewController as? UINavigationController{
if let streamController = navigationController.viewControllers.last as? HomeVC
{
streamController.tableView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}
}
return true
}
return true
}
Normally when a UINavigationController is placed in a UITabBarController the navigation controller pops to root with the respective tab it is in is double tapped. How do I achieve the same effect with a UISplitViewController is between the tab bar controller and the navigation controller? Ideally it would recurse through the view controller's child view controllers and calling popToRootViewController on all navigation controllers that it finds. Do I have to add my own gesture recognizer to the tab bar since it doesn't look like there is a hook for knowing when a user has double tapped a tab?
In Swift 4, it can go something like this:
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
private var shouldSelectIndex = -1
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
shouldSelectIndex = tabBarController.selectedIndex
return true
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if shouldSelectIndex == tabBarController.selectedIndex {
if let splitViewController = viewController as? UISplitViewController {
if let navController = splitViewController.viewControllers[0] as? UINavigationController {
navController.popToRootViewController(animated: true)
}
}
}
}
}
Instead of setting up a UIGestureRecognizer I simply keep track of the selected index in shouldSelectViewController and popped to root on my master navigation controller in didSelectViewController if the old selected index was the same as the new one.