2

好吧,在深入深入 iOS 私有类的深渊与追求终极可重用代码之间有一条很好的界限。

我想在 UINavigationBar 下方有一个大约 40 像素高度的任意 UIView。现在,通常这可以扔在封闭的视图中,但我希望这个 UIView与 UINavigationBar保持一致。因此,当用户向下钻取不同的视图时,导航栏下方的 UIView 将保持存在。

我尝试构建一个容器 UIViewController,它将作为 RootViewController 放置在 UINavigationController 中,但这无济于事,因为当用户推动新视图时,我的容器视图滑出,并且我的持久栏随之而来。

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

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

但是在玩了 ObjC 运行时类之后,我找不到可以调整的魔法变量来让它工作(UINavigationController 的 _containerView 就是这样一个变量)。

最好的方法是什么?有没有比复制和粘贴代码更好的方法来使这个 Bar 进入将显示它的所有 ViewController 中?

4

1 回答 1

5

我猜您正在寻找显示在导航栏下方的上传进度条或类似的东西,无论您去哪个视图,它都会与您同在。几周前我刚刚实现了这个,但有一个额外的警告——我们使用的是 IIViewDeck,所以我们还必须能够处理整个 UINavigationController 的变化。

我所做的是将视图添加为导航栏的子视图。我的导航栏和导航控制器都是子类(并且应用程序已顺利提交到 App Store)。起初我这样做是因为我需要在没有帮助的情况下自定义导航栏,UIAppearance但现在它似乎是不断给予的礼物,因为它为我提供了相当多的灵活性来完成诸如此类的任务。

  1. 创建自定义 UINavigationController 和 UINavigationBar
  2. 如果您需要处理触摸,则必须实现hitTest:withEvent- 我也在下面包含了该代码。
  3. 如果您有一个具有更改UINavigationController视图的应用程序(例如选项卡栏应用程序,或者具有可让您更改UINavigationController位于中心的侧边菜单),我实现了 KVO 来监视中心视图控制器,以便进度视图可以“跟随”它周围。代码也在下面。

这就是我创建自定义 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;
}

如果我误解了您的问题,那么我只是输入了世界上最长的 SO 答案,但徒劳无功。我希望这会有所帮助,如果有任何部分需要澄清,请告诉我!

于 2013-03-27T02:42:10.110 回答