推送 UIViewController 时显示 UITabBar

人气:329 发布:2022-10-16 标签: iphone show uinavigationcontroller uitabbar drilldown

问题描述

这是我的情况:我在 UITabBarController 中有一个 UINavigationController.当我向下钻取导航控制器时,有时我必须隐藏 UITabBar 因为我希望视图有尽可能多的空间.我通过在推送的 UIViewController 中使用 self.hidesBottomBarWhenPushed = YES 来做到这一点,并且效果很好.但是,我想在以下推送的控制器中显示 UITabBar.我尝试将 self.hidesBottomBarWhenPushed = NO 放在其他控制器中,但 UITabBar 不会回来.

Here's my situation : I have a UINavigationController inside a UITabBarController. When I drill down the navigation controller, at some point I have to hide the UITabBar because I want the view to have as much space as possible. I do that by using self.hidesBottomBarWhenPushed = YES inside the pushed UIViewController, and it works quite well. However, I want to show the UITabBar back in the following pushed controllers. I've tried to put self.hidesBottomBarWhenPushed = NO in the other controllers, but the UITabBar won't come back.

这似乎是正常的,如文档所述:

It seems to be normal as the documentation states :

hidesBottomBarWhenPushed

如果是,屏幕底部的栏被隐藏;否则,否.如果是,底部栏保持隐藏,直到视图控制器从堆栈中弹出.

确实,当这个属性设置为 yes 的控制器被弹出时,标签栏确实回来了.

And indeed, when the controller with this property set to yes is popped, the tabbar does come back.

当控制器被隐藏时,是否有任何适当的方法来显示 UITabBar?

提前致谢

推荐答案

好吧,我们还有很长的路要走.

Okay, here have we an awfully long way to go.

当您从文档中阅读时,默认行为是明确的:一旦视图控制器的 hides... 属性为 YES,标签栏将被隐藏,直到视图控制器被弹出.您想要的与此直接矛盾,出于各种原因,我首先建议不要采用这种方法.

As you read from the document, the default behavior is clear: once a view controller's hides... property is YES, the tab bar is hidden until the view controller is popped. What you want directly contradicts this, and for various reasons, I would first recommend not to take this approach.

但是,并不代表无法实现.

However, it doesn't mean it is impossible to implement.

hides... 属性设置回 NO Setting hides... property back to NO

您不能修改默认行为.要显示标签栏,堆栈中的 所有 视图控制器必须将其 hides... 属性设置为 NO.因此,从隐藏标签栏的视图中,如果您想在推送新视图时再次显示该栏,则必须将前一个视图控制器的 hides... 属性设置回 再次.

You cannot modify the default behavior. To show the tab bar, all view controllers in the stack must set their hides... property to NO. So from the view where the tab bar is hidden, if you want to show the bar again when a new view is pushed, you have to set the previous view controller's hides... property back to NO again.

就在你推送新的视图控制器之前,将属性设置回 NO.

Just before you push a new view controller, set the property back to NO.

// ... prepare viewControllerToPush

self.hidesBottomBarWhenPushed = NO;
[self.navigationController pushViewController:viewControllerToPush animated:YES];
[viewControllerToPush release];

通过这样做,您将再次拥有标签栏.但是,您会发现标签栏是从左侧推入的,而新视图是从右侧推入的.这显然是不可取的,所以我们需要解决这个问题.

By doing this, you will have the tab bar again. However, you will recognize the tab bar is pushed in from the left, while the new view is pushed from the right. This is clearly not desirable, so we need to fix this.

覆盖标签栏的图层操作

问题是,标签栏再次出现时使用的默认图层动作(动画)是从左侧推动的过渡动画.UITabBar 实现了 - (id < CAAction >)actionForLayer:(CALayer *)layer forKey:(NSString *)key 方法,该方法告诉从左边开始使用动画.我们需要重写此方法,以从右侧返回动画.

The thing is, the default layer action (the animation) used when the tab bar appears again, is a push transition animation from the left. UITabBar implements - (id < CAAction >)actionForLayer:(CALayer *)layer forKey:(NSString *)key method that tells to use the animation from the left for the case. We need to override this method, to return an animation from the right instead.

为了将标签栏显示回来,Cocoa 修改了其层的 position 属性.因此,我们的新方法应该为 position 键从右侧返回一个动画,而对于所有其他键,它应该使用默认实现.请注意,Apple 没有记录标签栏使用 position 的情况,因此在以下版本中可能会发生更改,恕不另行通知.无论如何,我们正在实施与 Apple 的规范直接相矛盾的东西,所以不能抱怨太多.

To show the tab bar back, Cocoa modifies its layer's position property. Therefore, our new method should return an animation from the right for the key position, and for all the other keys, it should use the default implementation. Note that using position for the tab bar is not documented by Apple, so it's subject to change without a notice in the following versions. We are implementing something directly contradicts Apple's specification anyway, so can't complain much.

但是,您不能只使用子类化来覆盖该方法.因为即使你有一个自定义的 UITabBar 子类,你也不能修改 UITabBarController 类来使用它来代替默认的 UITabBar.

However, you cannot just use subclassing to override the method. Because even if you have a custom subclass of UITabBar, you cannot modify UITabBarController class to use it instead of the default UITabBar.

所以,它变得有点复杂.为了将您自己的逻辑植入到 UITabBar 类中,您必须交换"消息 actionForLayer: forKey: 的实现.

So, it gets a bit more complex. In order to implant your own logic to UITabBar class, you have to 'swap' the implementation for the message actionForLayer: forKey:.

首先,使用 category 向 UITabBar 类添加一个新方法.

First, add a new method to UITabBar class using category.

@interface UITabBar(customAction)
@end

@implementation UITabBar(customAction)

- (id < CAAction >)customActionForLayer:(CALayer *)layer forKey:(NSString *)key {
    if ([key isEqualToString:@"position"]) {
        CATransition *pushFromRight = [[CATransition alloc] init];
        pushFromRight.duration = 0.25; 
        pushFromRight.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; 
        pushFromRight.type = kCATransitionPush; 
        pushFromRight.subtype = kCATransitionFromRight;
        return [pushFromRight autorelease];
    } 
return [self defaultActionForLayer:layer forKey:key];
}
@end

并在标签栏控制器的viewDidAppear方法中,插入如下代码.

And in the viewDidAppear method of the tab bar controller, insert the following codes.

Method original = class_getInstanceMethod([UITabBar class], @selector(actionForLayer:forKey:));
Method custom = class_getInstanceMethod([UITabBar class], @selector(customActionForLayer:forKey:));

class_replaceMethod([UITabBar class], @selector(actionForLayer:forKey:), method_getImplementation(custom), method_getTypeEncoding(custom));
class_addMethod([UITabBar class], @selector(defaultActionForLayer:forKey:), method_getImplementation(original), method_getTypeEncoding(original));

您希望在 viewDidAppear 而不是 viewDidLoad 中执行此操作,否则标签栏将在应用程序第一次显示时从右侧滑入.

You want to do this in viewDidAppear rather than viewDidLoad, because otherwise the tab bar will slide in from the right in the first time the application shows up.

现在,当 UITabBar 实例收到消息 actionsForLayer forKey: 时,将调用自定义方法 customActionForLayer forKey:.它拦截关键 position,并从右侧返回一个动画.如果是另一个键,它会调用消息的原始实现,该实现现在连接到消息 defaultActionsForLayer forKey:.

Now when a UITabBar instance gets a message actionsForLayer forKey:, the custom method customActionForLayer forKey: is invoked. It intercepts the key position, and returns an animation from the right. If it's for another key, it invokes the original implementation of the message, which is now connected to the message defaultActionsForLayer forKey:.

好的,我们到了.请记住,在弹出视图时,您可能必须将 hides... 属性设置回 YES,因为您在推送新视图时将其设置为 NO(并执行一些类似的技巧来正确设置动画).

Okay, there we are. Remember when popping back the views, you may have to set the hides... property back to YES, because you set it to NO when pushing a new view (and do some similar tricks to animate it properly).

我在这方面花了一些时间,但具有讽刺意味的是,我不得不说*不要再次使用这个,因为它使用了 Cocoa 类的未记录信息(标签栏动画的位置"键),与记录在案的行为相矛盾,并且违反了 Apple 的人机界面指南.您可能会发现您的应用程序与以下 SDK 版本无法正常工作,用户无法轻松采用该界面,甚至 Apple 拒绝您在 App Store 上的应用程序.

I've spent some time on this but ironically, I have to say *Do not use this again, because it uses an undocumented information of Cocoa classes ("position" key for tab bar animation), contradicts the documented behaviors, and is against Apple's human interface guideline. You may find out that your application wouldn't work the same with the following SDK versions, that the users cannot easily adopt the interface, or even that Apple rejects your application on App store.

那么我的答案到底是为了什么?好吧,我猜想是关于 iOS 开发的一些有趣主题的示例(以及证明我今天的工作效率非常低的证据:P).

Then what on earth is my answer for? Well, an example of some interesting topics on the iOS development, I guess (and a proof showing that I'm terribly unproductive today :P).

209