Double tap bar button item to scroll to the top - ios

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
}

Related

getting current active viewController in UITabBarControllerDelegate

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
}

Swift scroll to top not working after load more

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)

Detect Double Tap on UITabbarControler Item to scroll table view up

I using TableView under Tab Bar
My requirement is to scroll table view up when user double tap on tab bar item for selected view controller
this is my code
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool
{
if tabBarController.viewControllers!.index(of: viewController) == 0 {
if let navigationController = viewController as? UINavigationController{
if let streamController = navigationController.viewControllers.last as? StreamViewController
{
streamController.tableViewStream.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
}
}
return true
}
}
But this is causing to scroll table up when user even single tap on item, due to this I am note able to retain scroll position.
So what I want is
1.If user scroll the table and go on another tab and come back again by single tap scroll position should remain same
2.If user tap double any time on tab tab bar item the list should be scroll to top
In your code i would add checking if this view controller is already selected
if tabBarController.viewControllers!.index(of: viewController) == 0 {
if self.selectedIndex != 0 { return true }
if let navigationController = viewController as? UINavigationController{
if let streamController = navigationController.viewControllers.last as? StreamViewController
{
streamController.tableViewStream.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
}
}
return true
}

Presenting View controller disappears after animation

I've a TabBarController in which presents a view controller with custom animations with UIViewControllerAnimatedTransitioning;
The animations run normally without issues, but after the animationController(forPresented) function runs, the Presenting view controller disappears.
I've found a question around here with people having the same issues but none of those tries solved my issue.
I've read that there is a bug in iOS and we should had again the 'vanished' view controller to the stack, but adding this with UIApplication.shared.keyWindow?.addSubview(presentingView) makes the view added on top of the presentedView and I don't know it adding it again, adds another one to the stack, because it could only be a graphical bug and the view is still part of the container.
Here's some code:
// Global var
var transition = Animator()
// Present a VC modally using tab bar
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is NewPostVC {
if let newVC = tabBarController.storyboard?.instantiateViewController(withIdentifier: "newPostVC") as? NewPostVC {
newVC.transitioningDelegate = self
newVC.interactor = interactor // new
tabBarController.present(newVC, animated: true)
return false
}
}
return true
}
// Handles the presenting animation
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitioningMode = .Present
return transition
}
// Handles the dismissing animation
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitioningMode = .Dismiss
return transition
}
// interaction controller, only for dismissing the view;
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactor.hasStarted ? interactor : nil
}
//*****************************
/// On the Animator class:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
// Animates to present
if transitioningMode == .Present {
// Get views
guard
let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to),
let presentingView = transitionContext.view(forKey: UITransitionContextViewKey.from)
else {
print("returning")
return
}
// Add the presenting view controller to the container view
containerView.addSubview(presentingView)
// Add the presented view controller to the container view
containerView.addSubview(presentedView)
// Animate
UIView.animate(withDuration: presentDuration, animations: {
presentingView.transform = CGAffineTransform.identity.scaledBy(x: 0.85, y: 0.85);
presentingView.layer.cornerRadius = 7.0
presentingView.layer.masksToBounds = true
presentedView.transform = CGAffineTransform.identity.scaledBy(x: 0.6, y: 0.6);
presentedView.layer.masksToBounds = true
}, completion: { _ in
// On completion, complete the transition
transitionContext.completeTransition(true)
//UIApplication.shared.keyWindow?.addSubview(presentingView)
})
}
// Animates to dismiss
else {
// TODO: - Implement reverse animation
}
}
Note that the animations itself are just tests I'm doing, just scaling them around.
Thx.
The idea is that you don't need to add subview .from during presentation and .to during dismissal to the contatiner's view hierarchy. And if you want to see background underneath, just set the contatiner's view background color to .clear.
After reading Apple's related documentation here I found that it's not a bug that the presentingViewController disappears from the screen, it's just how the API works.
Anyone using transmission animation read the documentation, which was updated and you'll find really interesting and solid explanations there.

Implementation of UIPageViewController with UICollectionViewControllers (Swift)

I am trying to implement a UIPageViewController, where you can slide between two UICollectionViewControllers (much like the iOS 8.4 music App). This is also supposed to use one instance of UINavigationController.
This is what I've tried so far:
Using Storyboard, I've added a new PageViewController, in which I have conformed to the UIPageViewControllerDataSource as well as the UIPageViewControllerDelegate protocols.
Using Storyboard identifiers, I have successfully been able to get this to the point where you can swipe between each view.
Here is some sample code to be more specific:
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UICollectionViewController! {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("AlbumsController") as! UICollectionViewController
}
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SubscriptionsController") as! UICollectionViewController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
if index == identifiers.count - 1 {
return nil
}
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
if index == 0 {
return nil
}
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
The problem arises in the gestures being lost/overwritten when swiped to the next view. Initially I can scroll the first collection view and tap on the status bar to scroll to the top, but as soon as the pageviewcontroller swipes over to the next collection view, this gesture stops working and now neither collection view responds to the "scroll to top" gesture.
I have also heard using a scrollview is possible, but I wasn't convinced that this implementation would be more appropriate that using a UIPageViewController.
Current research suggests that the gestures are affected by the 'data source' of the PageViewController and must be altered in some way.
I have also embedded the UIPageViewController inside of a UINavigationController to achieve a shared navigation bar for both views. Now of course the navigation bar overlaps the UICollectionViews as they are not themselves embedded within a UINavigationController. To work my way around this problem, I've used this code below:
collectionView.contentInset = UIEdgeInsets(top: 64, left: 0, bottom: 0, right: 0)
Would anyone be able to give me some advice on how to properly re-create the 'swiping between two views' effect you get in the new Music App?
With my current implementation, I feel as it is not the correct way to achieve my goal, so I would greatly appreciate any help.

Resources