I have encountered a very awkward autolayout issue. I have a Navigation controller, i have set its rootviewcontroller as tabbarview controller for some purpose. See Image bellow:
In above image you can see there are 6 viewcontroller connected with tabbarviewcontroller.
In 3rd 4th 5th and 6th viewcontroller a white view is added and autolayout as given below:
Now, after run when i select tab 3 or 4, the added view shows perfectly as i wanted and according to autolayout it should be show as it is showing. i.e given below:
Now when I select "more" then it showing a tableviewcontroller as it should be according to its default behaviour, is given below:
Here is the problem occurred, now if you select any item from the list and go to respected view controller, the white view added goes down from navigation bar. I don't understand why this happening. please see in the pic:
Whole day I had tried to fix this but nothing happens, some how got one solution but it was not what I needed here. i.e see Image below:
If uncheck "Under Top Bars" for that view controller then its works fine, but it affects the navigation bar, which I not wanted here. please see the image:
Can somebody here who had faced the similar kind of problem and found a perfect solution and I'm looking for help.
Note: No class have been taken in any view controller, all are in storyboard.
Thanks in advance.
This is the problem there is a two navigation bar....you can track view hierarchy.....can you explain me what you want ?
Took a UiTbaBarControllerClass for my tabbarcontroller and in that that controller i set delegate self and follow my code, now its working as i wanted. very thanks to #KKRocks
Code:
#import "TabBarController.h"
#interface TabBarController ()<UITabBarControllerDelegate>
#end
#implementation TabBarController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.delegate = self;
}
#pragma mark UITabBarController Delegate
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
if ([viewController.title isEqual:#"More"]) {
[self.navigationController setNavigationBarHidden: YES animated: NO];
}
}
And in Swift:
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
}
// MARK: UITabBarController Delegate
func tabBarController(_ tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
print("controller class: \(String(describing: viewController.self))")
print("controller title: \(String(describing: viewController.title))")
if viewController.title?.isEqual("More") {
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
}
Now it working very fine. :)
Related
I am trying to implement code that makes it so when I tap the tab bar item that is already selected, my tableview will scroll to the first row. I got this functionality to work most of the time by looking at other stack overflow answers; however, I noticed a weird behavior. For some reason if I never deselect the tab in the first place, the code does not work.
For example, when I first load up the app, tab 0 is selected. If I scroll down and tap tab 0 again, it does not scroll to the top. However, if I select tab 1 and then tab 0, then proceed to scroll down and press tab 0 again, it scrolls up. Why is my code not working when the tab is selected for the first time?
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
if viewController is UINavigationController {
if navigationController == viewController {
let topIndexPath = NSIndexPath(forRow: 0, inSection: 0)
tableView.scrollToRowAtIndexPath(topIndexPath, atScrollPosition: .Top, animated: true)
}
}
}
I don't know everything about your current implementation but you can check the equality of currently selected view controller and the view controller that will be selected next by overriding the methods of UITabBarControllerDelegate responsible for tab selection.
Here is the partial solution;
#interface AppDelegate () <UITabBarControllerDelegate>
#property (nonatomic, weak) UIViewController *previousViewController;
#end
#implementation AppDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
self.previousViewController = tabBarController.selectedViewController;
return YES;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
if (self.previousViewController == viewController) {
[viewController.tableView setContentOffset:CGPointZero animated:YES];
}
}
#end
Hope this helps.
I have 3 tabs in my UITabbarController, that I created in my Appdelegate.
When I open the app, I have made the selected tabbarItem the third tabbarItem.
The user can only select the UITabBarItem at Index 0, when he is logged in.
I tried every thing to restrict the user from going to TabBarItem_0 when he is at TabBarItem_2.
But nothing worked. I used
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
}
But it's not working as I desired. I checked the stackoverflow and found almost the same question, where I found this delegate. But this is not working for me as desired. I googled, but couldn't find any solution other than stackoverflows links, which didn't help this time.
On the click of that disabled TabBar Item, I have to show a pop up. How can I implement that, too?
Try something like this,
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController;
{
if (tabBarController.selectedIndex == 0) {
if(isUserLoggedIn)
return YES;
else
return NO;
}
return YES;
}
If this does not work then,
Add this after you create the bar bar in app delegate,
[[[[self.tabBarController tabBar]items]objectAtIndex:0]setEnabled:FALSE];
once you log in enable it again
[[[[self.tabBarController tabBar]items]objectAtIndex:0]setEnabled:TRUE];
Quick hack that i used to present a popup on top of the current view when tapped on a tab bar item.
In your TabBarViewController class, implement the UITabBarControllerDelegate and remember to set self.delegate = self.
After that
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
if viewController.title == "Unique_title" //set an unique title for the view controller in storyboard or the view controller class.
{
performSegueWithIdentifier("YourModalViewIdentifier", sender: nil)
return false
} else {
return true
}
}
That should help you display a modal view when tap is received on the uitabbaritem. I know using title as an unique identifier is bad practice, but just a quick solution to achieve what you want.
Hope that helps.
You can do this in your code
- (void)viewDidLoad {
...
[self checkLogin];
...
}
- (void)checkLogin {
if (!loggedIn) {
[[[[self.tabBarController tabBar]items]objectAtIndex:0]setEnabled:NO];
} else {
[[[[self.tabBarController tabBar]items]objectAtIndex:0]setEnabled:YES];
}
}
- (void)tapLogin {
// Do the login action
}
- (void)processLoginResult {
// Process the result of the login action
// If the result is success, set 'loggedIn = YES'
// Otherwise, set 'loggedIn = NO'
...
[self checkLogin];
...
}
If you want to do it with the Storyboard, simply selected the TabBarItem in the destination view controller scene and uncheck the Enabled box.
This is what I did in Swift 2.1:
self.tabBarController!.tabBar.items![0].enabled = false
Here is how you disable a tabbar item in Swift 3 and 4
tabBarController.tabBar.items![0].isEnabled = false
How can I prevent a tab from being clicked, depending on conditions ? I tried several things and I probably missed some basics.
I'm having an UITabBarCustomController implementing UITabBarController. In the viewDidLoad, depending on a condition, i force the selectedIndex to 1, which works.
Nevertheless, i'm not able to prevent users leaving this tab. I tried to set a UITabBarControllerDelegate on the other ViewController's linked with my UITabBar, where I implemented - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController, and finally i also tried to implement this delegate and method on my custom UITabBarCustomController. But in both cases, when i click on another tab, nothing happens and users are able to access the tab (NSLog in those methods don't show in debug).
Any ideas ? thanks.
// Edit : Test code in delegate method :
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
NSLog(#"test");
return NO;
}
This is how you can stop/prevent Tabbar items to switch your tab on tabbar item click
For Swift 3.0
Make sure you have implemented UITabBarControllerDelegate and set UITabbarController's delegate to self
then override this delegate in your controller
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController == tabBarController.viewControllers?[2] {
return false
} else {
return true
}
}
I have a Tab Bar Controller with four navigation controllers added to it. The navigation controllers appear as Tab Bar Items in the Tab Bar Controller. Now I want to add a fifth button to the tab bar, that does not open another view, but triggers some custom code. I want to display an overlaying "share menu" when clicking that Tab Bar Item regardless on which of the four pages the user is. How can I do that?
I can suggest to add dummy UIViewController to the last index and handle UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if ([viewController == ...your dummy view controller...]) {
//Your custom action
return NO;
}
return YES;
}
Krivoblotsky has given the right answer! I'd like to elaborate a little more for anyone who is confused because for the full implementation there are a couple more moving parts. Let's say you have the app below. As it is when you click the home or profile icon the respective view will display. Let's say instead of the profile view to display, you want to add your custom transition / behavior.
To do this:
1. Given ProfileViewController class, you want include the UITabBarControllerDelegate in your ProfileViewController
#interface ProfileViewController : ViewController <UITabBarControllerDelegate> #end
2. Access your tabBarcontroller's delegate and set this as yourself in your ProfileViewController.m's viewDidLoad
self.tabBarController.delegate = self;
Essentially what this does is say hey, you know the tabBarController's delegate? (The guy that handles events) I know a guy and let this guy (self) handle those events instead. Like in English, you DELEGATE work to other people (you are the delegating object). The thing that handles the work, is the DELEGATE.
3. Implement the custom needed behavior
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:
if ([viewController isKindOfClass:[ProfileViewController class]]){
NSLog(#"It's a profile");
return NO };
};
else{ return YES; }
The NO return says, when ProfileViewController is selected, do not do default behavior and display it's view.
Excellent explanation of delegates
In Storyboard, add a UIVIewController and connect it to the tab button you want to perform your custom action.
Give that UIViewController a unique title. e.g. "for custom action". It really doesn't matter, as nobody will ever see that title. It is just for you to use in the code below to identify that tab was tapped.
Create the class below and assign it to your UITabBarController in Storyboard
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
delegate = self
}
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
if viewController.title == "for custom action" {
//do your custom actions
return false
}
return true
}
}
You should simply implement the following UITabBarDelegate method:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item;
There are several similar questions which got no answers but were describe vaguely. I have reduced the problem into a very thin application, and added detailed screenshots. I would highly appreciate a solution for this!
The only involved code is one line added to viewDidLoad of the root VC. The purpose of this line is to make the navigation controller opaque:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
}
A critical information for this question is that 'Title1' has a prompt in its navigation item, while 'Title2' has not prompt.
I have a storyboard with one navigation controller, one root VC called "Title1", with a segue button which takes to a second VC called "Title2"
When pressing the button here:
I'm getting this strange screen:
When pressing back (Title1), it gets worse (i.e.: the original label of Title1 was pushed up and now not being seen anymore!!!):
Anyone please??
Late answer but I stumbled across this problem today and found your question and it doesn't have an accepted answer yet.
I got this error while going from a prompted viewController to a non prompted viewController in storyboard.
I got that black bar just like you.
And to fix:
// In prompted vc
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
UIView.setAnimationsEnabled(false)
self.navigationItem.prompt = nil
UIView.setAnimationsEnabled(true)
}
This will remove the prompt instantly before switching viewcontroller.
UPDATE
func prompt() -> String? {
return nil
}
override func viewWillAppear(animated: Bool) {
let action = { self.navigationItem.prompt = self.prompt() }
if self.navigationController?.viewControllers.count <= 1 {
UIView.performWithoutAnimation(action)
}
else {
action()
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
UIView.performWithoutAnimation {
self.navigationItem.prompt = (segue.destinationViewController as? ViewController)?.prompt()
}
}
It appeared as translucent property of UINavigationBar appeared to be messed up with frame other view controllers.
I would recommend following approach.
Create a base view controller from which other view controllers will inherit as follows,
#import "BaseViewController.h"
#interface BaseViewController ()
#end
#implementation BaseViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
}
other view controllers will inherit above BaseViewController
// interface
#import <UIKit/UIKit.h>
#import "BaseViewController.h"
#interface ViewController : BaseViewController
#end
// implementation
#import "ViewController.h"
#implementation ViewController
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Here translucent property is enabled when the view is about to be disappeared.
// However note that, translucent property needs to be enabled only on those view controllers which has prompt set on their navigation items.
self.navigationController.navigationBar.translucent = YES;
}
Other view controllers without prompt implementation will work as usual however they also needs to inherit from BaseViewController.
The best way to solve this issue is setting the background of the window during push i.e,
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window?.backgroundColor = UIColor.white
Seems like Xcode has some issues when changing the navigationBar height since main controller view is not resized accordingly.
I found a solution to do this, not sure it is the best... but it's working.
Just inherit your viewWillAppear and viewWillDisappear methods in your first view controller (the one with a prompt):
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationItem.prompt = #"Prompt1";
[UIView animateWithDuration:UINavigationControllerHideShowBarDuration
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
[self.view setFrame:CGRectMake(0, 94, 320, 386)];
}
completion:^(BOOL finished){
}];
}
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Sets prompt to nil
self.navigationItem.prompt = nil;
[UIView animateWithDuration:UINavigationControllerHideShowBarDuration
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
[self.view setFrame:CGRectMake(0, 64, 320, 416)];
}
completion:^(BOOL finished){
}];
}
I didn't focus on frame size (it's for 3,5" iPhone frame sizes). You must calculate this size or you might have some issues with larger screens.
The top answer on this thread just solved this problem for me, thank you.
Here's the thing, the app I'm working on uses coordinators and autolayout, so looking at this showed the animation need to be shut off. I had to change the navigation controller's push view controller function's animate parameter to false.
Ex:
navigationController?.pushViewController(view, animated: false)