Table scroll when changing tabs - swift

I'm wanting to scroll a table view to the top when someone clicks the tab bar icon.
My tab bar has 4 tabs, the table is on index 0.
I can currently get it to scroll to the top correctly, but I only want it to do it when on index 0, not when returning from the other items.
Currently I have
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
}
What I want is if you are on index 0, then scroll back to the top, if you are going to 0 from another tab, then leave the scroll as it is.

Edit: It dawned on me you can do the whole process on shouldSelect
You need to know which tab was previously selected. One way to do this is to move the logic to "tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController)"
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// first condition checks the selected tab is the current view controller
// second verifies you are not coming from another tab
if viewController == self && viewController == tabBarController.selectedViewController {
tableView?.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}
return true
}

Related

Swift: How would I add an action to a tab bar item using Interface Builder?

I've been able with code-only to add an action to a tab bar item, but I'm more used to coding with the Interface Builder, and am struggling to use what I know about setting up a tab bar controller with an action tab item programmatically to add an action to a tab bar set up in the IB. With a Tab Bar Controller and three View Controllers connected to said TBC in the IB, how would I go about adding an action where when the middle tab bar item is pressed, the View Controller associated would be presented modally? Thanks.
You can only set up associated view controllers in tabbar from the storyboard. You can't add an action to a tabbar item.
When you select a tab in tabbar, the tabBarController will automatically show the associated view controller. To prevent that, you have to implement tabBarController(_ tabBarController: UITabBarController, shouldSelect method of UITabBarControllerDelegate in your tabBarController subclass, where you can decide whether to show the selected tabbar item or not.
Don't forget to set the delegate in your UITabBarController subclass.
extension YourTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if (viewController is 'YourViewControllerClass') {
// code to present the viewcontroller
return false
}
return true
}
}
This code Work for me:
import UIKit
class WayTBC: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Tab Bar Customisation
tabBar.barTintColor = .systemPink
tabBar.tintColor = .systemTeal
tabBar.unselectedItemTintColor = .systemGray
tabBar.isTranslucent = false
viewControllers = [
createTabBarItem(tabBarTitle: "Search", tabBarImage: "ic_home_search", viewController: SearchVC()),
createTabBarItem(tabBarTitle: "Tab 2", tabBarImage: "ic_home_search", viewController: SearchVC()),
createTabBarItem(tabBarTitle: "Tab 3", tabBarImage: "ic_home_search", viewController: SearchVC()),
createTabBarItem(tabBarTitle: "Tab 4", tabBarImage: "ic_home_search", viewController: SearchVC())
]
// Do any additional setup after loading the view.
}
func createTabBarItem(tabBarTitle: String, tabBarImage: String, viewController: UIViewController) -> UINavigationController {
let navCont = UINavigationController(rootViewController: viewController)
navCont.tabBarItem.title = tabBarTitle
navCont.tabBarItem.image = UIImage(named: tabBarImage)
// Nav Bar Customisation
navCont.navigationBar.barTintColor = .white
navCont.navigationBar.tintColor = .white
navCont.navigationBar.isTranslucent = false
return navCont
}

Tab Bar Controller index changing?

I have 2 tabs : Calc VC and Browse VC; they are in that order and the app starts on Calc VC.
Using a print statement tabbarcontroller.selectedindex in viewwillappear of each VCs I learned that when Calc VC first appear, it shows an index of 0. When I tap on Browse VC, it shows an index of 1. So far so good.
When I tap on Calc VC, its index becomes 1 and Browse VC becomes 0. It remains like that until you quit the app.
Why it's an issue? I'm trying to disable Browse VC when Calc VC is active by using .isEnabled = false but I can't do it that way because of the changing index
At the time of viewWillAppear triggered, selectedIndex might not be changed. You should use viewDidAppear.
override func viewDidAppear(_ animated: Bool) {
print(tabBarController?.selectedIndex)
}
Or you can use tabBar(_:didSelect:) of UITabBarDelegate
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print(tabBar.items?.index(of: item))
}

Scroll UITableView up before popping UIViewController

I have a custom pop transition and it depends on the UITableView being scrolled to the top before performing the transition. The second I tap the back button of my UIViewController I want to scroll the UITableView up which will then have the correct state for my transition.
I already tried to scroll the UITableView up before the view will get dismissed:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
}
This however failed to scroll at all. How can I move the UITableView to the top before the UIViewController gets dismissed?
Overriding willMove(toParent:) and checking for parent == nil should allow you to perform the animation at the required time. See this response on intercepting nav back button.

Swift - Selected tab bar index not triggering scroll to top

I have a tab bar with five items, and I am trying to add a functionality to scroll to the top when the user taps the tab bar item again. Added the UITabBarControllerDelegate to the views where I want to trigger the event and also created a function to determine the selected tab bar index.
When I open the app, index 0 is auto-selected and works perfectly. The view auto scrolls to the top when I scroll down and tap the tab bar index. The problem occurs when I go to index 1 and trigger the scroll there. It somehow completely removes the auto-scroll from my first tab bar item.
Selecting other tab bar items without the auto scroll does not affect index 0 at all.
Home (index 0)
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
self.collectionView?.setContentOffset(CGPoint(x: 0, y: -10), animated: true)
}
}
Users (index 1)
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 1 {
self.tableView?.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}
}
Anything with a delegate property can only have one delegate assigned to it at any given time. What ever set the delegate most recently will receive the next delegate method call.
In your case you can probably reset the tab controller's delegate to self in each view controller's viewDidAppear method since you want the currently visible view controller to be the current tab controller delegate.
Add the following to each view controller that needs to be the tab controller's delegate:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.tabBarController?.delegate = self
}

Menu from tab bar in storyboarding

At the moment I have a standard tab bar, when tapped goes to its corresponding viewController. But I want to make a menu pop out from tab bar when more tab is selected, as shown in below image.
Is any suggestion to implement this?
Thanks in advance.
I would recommend you do so:
First, you should think of all the tab types that could be in tab bar. On your screenshot there are tabs, that present controller, and tab, that presents menu. So we could create enum with all these types:
enum TabType {
case controller
case menu
}
After that you can store array of tab types in order they are shown in tab bar, for your screenshot like so
let tabTypes: [TabType] = [.controller, .controller, .controller, .controller, .menu]
Then you should implement UITabBarControllerDelegate's func tabBarController(_:, shouldSelect:) -> Bool method, which returns true if tab bar is allowed to select the passed controller, and false otherwise.
If you return true than all other work (like presenting view controller and other stuff) tab bar controller will do for you.
In your case you want to execute custom action on tab click, so you should return false. Before returning you should present your menu.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let index = tabBarController.viewControllers?.index(of: viewController),
index < tabBarTypes.count {
let type = tabBarTypes[index]
switch type {
case .menu:
// perform your menu presenting here
return false
case .controller:
// do nothing, just return true, tab bar will do all work for you
return true
}
}
return true
}
In this implementation you can easily change tab types order or add some another tab type and handle it appropriate.
Its not good UI but although if you want.
First You have to implement delegate method of UITabbarControllerDelegate as below
func tabBarController(_ tabBarController: UITabBarController, shouldSelect
viewController: UIViewController) -> Bool {
if viewController.classForCoder == moreViewController.self
{
let popvc = MoreSubMenuViews(nibName: "MoreSubMenuViews", bundle: Bundle.main)
self.addChildViewController(popvc)
let tabbarHeight = tabBar.frame.height
let estimatedWidth:CGFloat = 200.0
let estimatedHeight:CGFloat = 300.0
popvc.view.frame = CGRect(x:self.view.frame.width - estimatedWidth, y: self.view.frame.height - estimatedHeight - tabbarHeight, width: estimatedWidth, height: estimatedHeight)
self.view.addSubview(popvc.view)
popvc.didMove(toParentViewController: self)
print("sorry stop here")
return true // if you want not to select return false here
}else{
//remove popup logic here if opened
return true
}
}
Here moreViewController is last tab controller & MoreSubMenuViews is nib/xib file which contains buttons shown in you image.
Instead of showing a menu, you could use a scrollable tabs view for a better UI. If you prefer using a library, here's a simple scrollable tab-bar you could implement.
This is an abuse of the UI patterns layout in Apples HIG. Specifically:
Use a tab bar strictly for navigation. Tab bar buttons should not be used to perform actions. If you need to provide controls that act on elements in the current view, use a toolbar instead.
This control should be used to flatten your app hierarchy. It seems that in this case you are mixing functionality of the button. Sometimes it selects a separate view controller, sometimes it displays a action list. This is a confusing situation for users and should be avoided.
A way you could achieve this and still adhere to HIG is by using navigation or tool bars. Imbed this control in a toolbar button. The most simple case would be to invoke a UIActionController with the .actionSheet style selected.
As per my understanding what you want is that, tab 1 is selected and user goes to tab 5 then background will be tab 1 & vice-versa for tab 2,3 & 4 and there should be pop up like buttons as shown in your image. please correct me if I'm wrong.
For this you have to capture the image while user navigate through tabs.
Please find below code in AppDelegate,
var currentImage: UIImage! //It will be public variable
Tabbar delegate methods,
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if tabBarController.selectedIndex == 4 {
let moreVC = tabBarController.selectedViewController as! MoreVC
moreVC.currentImage = currentImage
}
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if tabBarController.selectedIndex != 4 {
let navController = tabBarController.selectedViewController as! UINavigationController
let viewController = navController.viewControllers.last
currentImage = Common.imageWithView(inView: (viewController?.navigationController?.view)!) //UIImage(view: (viewController?.navigationController?.view)!)
}
return true
}
Here I've taken static 4 as last tab index & MoreVC will be the associated ViewController of tab index 4.
Common class to get image from view,
class Common {
static func imageWithView(inView: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(inView.bounds.size, inView.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
inView.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
}
Please find MoreVC,
#IBOutlet weak var imageView: UIImageView!
var currentImage: UIImage! {
didSet {
imageView.image = currentImage
}
}
Please find below image for MoreVC Storyboard. Look it does not contain NavigationController like all other ViewControllers.
Let me know in case of any queries.
1)
Create a "Tab5ViewController" and use this:
static func takeScreenshot(bottomPadding padding: CGFloat = 0) -> UIImage? {
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(CGSize(width: layer.frame.size.width, height: layer.frame.size.height - padding), false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
layer.render(in: context)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return screenshot
}
Use the screenshot as a background image for your "Tab5ViewController"
2) In your "Tab5ViewController" you can have the stack component (the additional tabs) and then just add/remove child view controllers based on the selection
3)
fix edge cases (like tab selection highlight and so on...)