如何在UINavigationBar下面添加一个视图

时间:2013-03-26 23:32:08

标签: iphone ios ios6 uinavigationcontroller uinavigationbar

好吧,在深入深渊(即iOS私人课程)和最终可重复使用代码的讨伐之间存在着很好的界限。

我希望在UINavigationBar下面有一个高度约为40像素的任意UIView。现在,通常这可能会在随附的视图中抛出,但我希望这个UIView能够保留和UINavigationBar。因此,当用户向下钻取到不同视图时,导航栏下方的UIView将保持存在。

我已经尝试构建一个容器UIViewController,它将作为UINavigationController中的RootViewController放置,但这没有用,因为当用户推送新视图时,我的容器视图被滑出,并且我坚持不懈的酒吧。

如果我在UIViewController中放置一个UINavigationController,我可以覆盖我想要的UIView栏,但是内部视图似乎仍然没有正确地改变它们的框架,以及当视图被推或弹出时导航堆栈,视图动画效果不佳(更改其Y位置以及移出屏幕,因为UINavigationController不知道屏幕下方的新Y位置。)

所以,我研究了UINavigationController的子类化。我知道,它说“这个类不是用于子类化。”,但总有例外。这个子类可以简单地在导航栏下面添加持久性UIView,调整内容部分的大小(普通视图将出现的位置),一切都会正常工作。

但是在使用ObjC运行时类之后,我无法找到调整的魔术变量来使其工作(UINavigationController的_containerView就是这样一个变量)。

最好的方法是什么?有没有比复制和粘贴代码更好的方法,使这个Bar成为将显示它的所有ViewControllers?

1 个答案:

答案 0 :(得分:5)

我猜你是想放置一个上传进度条或类似的东西显示在导航栏下方,无论你去哪个视图,它都会留在你身边。我刚刚在几周前实现了这一点,并提出了一个额外的警告 - 我们正在使用IIViewDeck,因此我们必须能够处理整个UINavigationController的更改。

我做的是将视图添加为导航栏的子视图。我的导航栏和导航控制器都是子类(并且应用程序已经提交到App Store而没有发生任何事故)。起初我这样做是因为我需要在没有UIAppearance帮助的情况下自定义导航栏,但现在它似乎是继续给予的礼物,因为它为我提供了相当多的灵活性。< / p>

  1. 创建自定义UINavigationController和UINavigationBar
  2. 如果您需要处理触摸,则必须实施hitTest:withEvent - 我也在下面提供了该代码。
  3. 如果您有一个更改UINavigationController视图的应用(例如标签栏应用,或者使用侧边菜单可以更改位于中心的UINavigationController),我实施了KVO来监控中心视图控制器,以便进度视图可以“跟随”它。代码如下。
  4. 这就是我创建自定义UINavigationController和UINavigationBar的方式:

    + (ZNavigationController *)customizedNavigationController
    {
     //This is just a regular UINavigationBar subclass - doesn't have to have any code but I personally override the pop/push methods to call my own flavor of viewWill/Did/Appear/Disappear methods - iOS has always been a bit finicky about what gets called when
        ZNavigationController *navController = [[ZNavigationController alloc] initWithNibName:nil bundle:nil];
    
        // Ensure the UINavigationBar is created so that it can be archived. If we do not access the
        // navigation bar then it will not be allocated, and thus, it will not be archived by the
        // NSKeyedArchvier.
        [navController navigationBar];
    
        // Archive the navigation controller.
        NSMutableData *data = [NSMutableData data];
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
        [archiver encodeObject:navController forKey:@"root"];
        [archiver finishEncoding];
    
        // Unarchive the navigation controller and ensure that our UINavigationBar subclass is used.
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        [unarchiver setClass:[ZNavigationBar class] forClassName:@"UINavigationBar"];
        ZNavigationController *customizedNavController = [unarchiver decodeObjectForKey:@"root"];
        [unarchiver finishDecoding];
    
        // Modify the navigation bar to have a background image.
        ZNavigationBar *navBar = (ZNavigationBar *)[customizedNavController navigationBar];
    
        [navBar setTintColor:kZodioColorBlue];
        [navBar setBackgroundImage:[UIImage imageNamed:@"Home_TopBar_RoundCorner_withLogo"] forBarMetrics:UIBarMetricsDefault];
    
        return customizedNavController;
    }
    

    既然你有自己的自定义ZNavigationBar(这些是我代码中的实际名称 - 很难通过并改变它们)你实现了触摸处理 - 变量名称非常自我解释 - 我想要检测此进度条的“左侧附件视图”,“主要区域”或“右侧附件视图”中的触摸:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        DDLogVerbose(@"Got a touch at point X: %f Y: %f", point.x, point.y);
    
     //First check if the progress view is visible and touch is within that frame
        if ([[JGUploadManager sharedManager] progressViewVisible] &&
            CGRectContainsPoint([[JGUploadManager sharedManager] uploadProgressView].frame, point))
        {
     //Modified frame - have to compensate for actual position of buttons in the view from the standpoint of the UINavigationBar. Can probably use another/smarter method to do this.
            CGRect modifiedCancelButtonFrame = [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryFrame;
            modifiedCancelButtonFrame.origin.y += [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryView.superview.frame.origin.y;
    
     //Do the same thing for the right view
            CGRect modifiedRetryButtonFrame = [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryFrame;
            modifiedRetryButtonFrame.origin.y += [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryView.superview.frame.origin.y;
    
    
     //Touch is on the left button
            if ([[JGUploadManager sharedManager] progressViewVisible] &&
                [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryView &&
                CGRectContainsPoint(modifiedCancelButtonFrame, point))
            {
                return [[JGUploadManager sharedManager] uploadProgressView].leftAccessoryView;
            }
    
    
     //Touch is on the right button
            else if ([[JGUploadManager sharedManager] progressViewVisible] &&
                     [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryView &&
                     CGRectContainsPoint(modifiedRetryButtonFrame, point))
            {
                return [[JGUploadManager sharedManager] uploadProgressView].rightAccessoryView;
            }
    
     //Touch is in the main/center area
            else
            {
                return [[JGUploadManager sharedManager] uploadProgressView];
            }
    
        }
    
     //Touch is not in the progress view, pass to super
           else
        {
            return [super hitTest:point withEvent:event];
        }
    }
    

    现在KVO部分“跟随”导航控制器。在某个地方实现KVO - 我使用单独的“上传管理器”类并将其放在该类的init中,但这取决于您的应用程序的设计:

    ZRootViewController *rootViewController = ((JGAppDelegate*)[UIApplication sharedApplication].delegate).rootViewDeckController;
    [rootViewController addObserver:sharedManager
                         forKeyPath:@"centerController"
                            options:NSKeyValueObservingOptionNew
                            context:nil];
    

    然后当然实现这个:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        DDLogVerbose(@"KVO shows something changed: %@", keyPath);
    
        if ([keyPath isEqualToString:@"centerController"])
        {
            DDLogVerbose(@"Center controller changed to: %@", object);
            [self centerViewControllerChanged];
        }
    
    }
    

    最后是centerViewControllerChanged函数:

    - (void)centerViewControllerChanged
    {
        ZRootViewController *rootViewController = ((JGAppDelegate*)[UIApplication sharedApplication].delegate).rootViewDeckController;
    
        ZNavigationController *centerController = (ZNavigationController *)rootViewController.centerController;
    
        //Check if the upload progress view is visible and/or active
        if (self.uploadProgressView.frame.origin.y > 0 && self.uploadProgressView.activityIndicatorView.isAnimating)
        {
            [centerController.navigationBar addSubview:self.uploadProgressView];
        }
    
        //Keep weak pointer to center controller for other stuff
        DDLogVerbose(@"Setting center view controller: %@", centerController);
        self.centerViewController = centerController;
    }
    

    如果我误解了你的问题,那么我只是输入世界上最长的答案,徒劳无益。我希望这有帮助,让我知道是否有任何部分需要澄清!