如果我淡化导航栏,然后实际以编程方式隐藏它,如果我撤消这个没有时间问题,我怎么能这样做呢?

时间:2013-08-02 16:13:42

标签: ios objective-c uinavigationcontroller autolayout nslayoutconstraint

长话短说,我有一个视图控制器,用户可以点击self.view(导航栏以外的任何地方),它将进入全屏模式,底部的控件淡出,导航和状态栏淡出。与iBooks类似。

我可以简单地淡化导航栏的alpha,但是为了允许用户点击新获得的区域(现在导航栏已经消失了)并让它做某事,我必须做更多而不是更改alpha,因为导航栏在技术上仍占用区域。

所以我用[self.navigationController setNavigationBarHidden:YES animated:NO];隐藏了导航栏。我必须在动画块完成后执行此操作,否则它将在动画块中并作为块的一部分进行动画处理。所以我使用dispatch_after在动画完成后完成(延迟0.35秒)。

然而,这会导致这样的问题:如果用户在0.35秒的时间段内任意时间点动画,并且等待完成,则会导致另一个块开始出现故障行为,即使它仍在等待0.35秒另一个完成。它会导致一些毛病行为并导致导航栏保持隐藏状态。毛。

正在发生的视频:http://cl.ly/2i3H0k0Q1T0V

这是我的代码,用于演示我正在做的事情:

- (void)hideControls:(BOOL)hidden {
    self.navigationController.view.backgroundColor = self.view.backgroundColor;
    int statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;

    [UIView animateWithDuration:0.35 animations:^{
        [[UIApplication sharedApplication] setStatusBarHidden:hidden withAnimation:UIStatusBarAnimationFade];

        if (hidden) {
            self.navigationController.navigationBar.alpha = 0.0;
            self.instructionsLabel.alpha = 0.0;
            self.backFiftyWordsButton.alpha = 0.0;
            self.forwardFiftyWordsButton.alpha = 0.0;
            self.WPMLabel.alpha = 0.0;
            self.timeRemainingLabel.alpha = 0.0;
        }
        else {
            self.navigationController.navigationBar.alpha = 1.0;
            self.instructionsLabel.alpha = 1.0;
            self.backFiftyWordsButton.alpha = 1.0;
            self.forwardFiftyWordsButton.alpha = 1.0;
            self.WPMLabel.alpha = 1.0;
            self.timeRemainingLabel.alpha = 1.0;
        }

        [self.view layoutIfNeeded];
    }];

    // Perform an "actual" hide (more than just alpha changes) after the animation finishes in order to regain that touch area
    if (hidden) {
        double delayInSeconds = 0.35;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
            [self.navigationController setNavigationBarHidden:YES animated:NO];
            self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE + self.navigationController.navigationBar.frame.size.height + statusBarHeight;
        });
    }
    else {
        [self.navigationController setNavigationBarHidden:NO animated:NO];
        self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE;
    }
}

我正在做的唯一另一件事是更改我的自动布局约束的常量,以考虑导航栏和状态栏,具体取决于它们是否在那里。

我不确定如何考虑双击可以真正解决整个屏幕过程这一事实。我怎么能这样做,如果他们在动画过程中点击它只会取消动画并按预期执行他们想要的动作?我可以更好地完成这个过程吗?

8 个答案:

答案 0 :(得分:2)

其他答案很有帮助,但您应该做的一件事是将动画持续时间硬编码为0.35,尝试使用UINavigationControllerHideShowBarDuration。这将使您的应用程序对UIKit行为的更改更具弹性。

答案 1 :(得分:2)

我认为你可以在不使用这些原则调整任何框架或约束的情况下做到这一点:

1)使窗口的背景颜色与视图的背景颜色相同

2)向窗口添加点击手势识别器。这允许点击屏幕上的任何位置(当alpha不为0时状态栏除外)导航栏是否可见。这样您就不必将导航栏设置为隐藏,这会导致视图调整大小。

3)使用hitTest:在tapper的action方法中检查用户是否点击了导航栏,如果点击那里则不淡出。

4)在动画块中使用UIViewAnimationOptionBeginFromCurrentState和UIViewAnimationOptionAllowUserInteraction,以便在再次触摸时可以平滑地反转淡入或淡出。

5)将所有底部控件包含在一个清晰的UIView中,这样你就可以淡出UIView而不是所有单独的控件。

以下是有效的代码:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    self.view.window.backgroundColor = self.view.backgroundColor;
    UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(fadeInFadeOut:)];
    [self.view.window addGestureRecognizer:tapper];

}


-(void)fadeInFadeOut:(UITapGestureRecognizer *)sender {
    static BOOL hide = YES;
    id hitView = [self.navigationController.view hitTest:[sender locationInView:self.navigationController.view] withEvent:nil];

    if (! [hitView isKindOfClass:[UINavigationBar class]] && hide == YES) {
        hide = ! hide;
        [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
        [UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction animations:^{
            self.navigationController.navigationBar.alpha = 0;
            self.bottomView.alpha = 0;
        } completion:nil];

    }else if (hide == NO){
        hide = ! hide;
        [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
        [UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction animations:^{
            self.navigationController.navigationBar.alpha = 1;
            self.bottomView.alpha = 1;
        } completion:nil];
    }
}

答案 2 :(得分:1)

为什么不使用animationCompleted委托或阻止?

答案 3 :(得分:1)

我这样做的方法就是创建一个BOOL标志,我称之为isTransitioning,这样一旦隐藏/取消隐藏过程开始,hideControls方法如果正在进行转换,则立即返回。那样你就不会搞乱触摸事件;你是直接阻止不必要的故障,而不会在其他地方造成副作用(显然你需要在方法之外声明isTransitioning作为属性/ ivar):

- (void)hideControls:(BOOL)hidden {

    //Check there isn't a hide/unhide already in progress:
    if(self.isTransitioning == YES) return;

    //if there wasn't already a transition in progress, set
    //isTransitioning to YES and off we go:
    self.isTransitioning = YES;

    self.navigationController.view.backgroundColor = self.view.backgroundColor;
    int statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;

    [UIView animateWithDuration:0.35 animations:^{
        [[UIApplication sharedApplication] setStatusBarHidden:hidden withAnimation:UIStatusBarAnimationFade];

        if (hidden) {
            self.navigationController.navigationBar.alpha = 0.0;
            self.instructionsLabel.alpha = 0.0;
            self.backFiftyWordsButton.alpha = 0.0;
            self.forwardFiftyWordsButton.alpha = 0.0;
            self.WPMLabel.alpha = 0.0;
            self.timeRemainingLabel.alpha = 0.0;
        }
        else {
            self.navigationController.navigationBar.alpha = 1.0;
            self.instructionsLabel.alpha = 1.0;
            self.backFiftyWordsButton.alpha = 1.0;
            self.forwardFiftyWordsButton.alpha = 1.0;
            self.WPMLabel.alpha = 1.0;
            self.timeRemainingLabel.alpha = 1.0;
        }

        [self.view layoutIfNeeded];
    }];

    // Perform an "actual" hide (more than just alpha changes) after the animation finishes in order to regain that touch area
    if (hidden) {
        double delayInSeconds = 0.35;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
            [self.navigationController setNavigationBarHidden:YES animated:NO];
            self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE + self.navigationController.navigationBar.frame.size.height + statusBarHeight;

        //Unset isTransitioning now we're finished:
        self.isTransitioning = NO;
        });
    }
    else {
        [self.navigationController setNavigationBarHidden:NO animated:NO];
        self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE;

        //Unset isTransitioning now we're finished:
        self.isTransitioning = NO;
    }
}

我没有直接测试过这段代码,但你可以看到我得到的东西,我敢肯定。

答案 4 :(得分:1)

编辑#2

查看文档,hitTest:withEvent:只需在视图的所有子视图上调用pointTest:withEvent:即可。它明确指出alpha级别小于0.01的视图会被忽略。我认为我们正走在正确的道路上,只需要进一步探索。我确信有一种方法可以让alpha == 0.0f的视图通过触摸到其下方的任何视图。希望你(或其他人)能得到它。如果我有时间,我会深入研究一些代码并尝试进一步提供帮助。

编辑#1:尝试覆盖pointInside:withEvent:

对不起,意识流在这里回答。通常我会测试这个 我自己或从生产应用程序粘贴代码,但我现在太忙了。

我认为覆盖pointInside:withEvent会起作用:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha == 0.0f) {
        return NO;
    }
    else {
        return [super pointInside:point withEvent:event];
    }
}

原始答案: 我会尝试子类化UINavigationBar并覆盖hitTest:withEvent:,以便导航栏在隐藏时忽略触摸。我没有测试这个,但应该是这样的:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // if alpha == 0.0f, the nav bar should ignore touches
    if (self.alpha == 0.0f) {
        return nil;
    }

    // else hitTest as normal
    else {
        return [super hitTest:point withEvent:event];
    }
}

如果测试alpha == 0.0f不是您想要忽略触摸的方式,您也可以添加自己的属性并按此方式执行。

@property (nonatomic) BOOL ignoreTouches;

hitTest:withEvent:执行检查中if (ignoreTouches)并返回nil。

答案 5 :(得分:0)

您可以使用animateWithDuration:animations:completion:。动画完成后执行完成块(source)。它还有一个额外的好处;如果你决定在将来改变动画的时间,你不必担心在两个地方改变时间。

[UIView animateWithDuration:0.35 animations:^{
    [[UIApplication sharedApplication] setStatusBarHidden:hidden withAnimation:UIStatusBarAnimationFade];

    if (hidden) {
        self.navigationController.navigationBar.alpha = 0.0;
        self.instructionsLabel.alpha = 0.0;
        self.backFiftyWordsButton.alpha = 0.0;
        self.forwardFiftyWordsButton.alpha = 0.0;
        self.WPMLabel.alpha = 0.0;
        self.timeRemainingLabel.alpha = 0.0;
    }
    else {
        self.navigationController.navigationBar.alpha = 1.0;
        self.instructionsLabel.alpha = 1.0;
        self.backFiftyWordsButton.alpha = 1.0;
        self.forwardFiftyWordsButton.alpha = 1.0;
        self.WPMLabel.alpha = 1.0;
        self.timeRemainingLabel.alpha = 1.0;
    }

    [self.view layoutIfNeeded];
}
completion:^(BOOL finished){
    // Perform an "actual" hide (more than just alpha changes) after the animation finishes in order to regain that touch area
    if ( finished ) {
        if (hidden) {
            [self.navigationController setNavigationBarHidden:YES animated:NO];
            self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE + self.navigationController.navigationBar.frame.size.height + statusBarHeight;
        }
        else {
            [self.navigationController setNavigationBarHidden:NO animated:NO];
            self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE;
        }
    }
}];

根据评论,您可以在动画开始之前禁用导航栏,并在完成块中重新启用,但这取决于您测试哪种方法最适合您..

答案 6 :(得分:0)

要在为UINavigationBar设置动画时关闭接收触摸事件,然后将其子类化并添加以下方法:

-(BOOL) canBecomeFirstResponder{
    NSLog(@"Decide if to allow the tap through");
    if (self.hiding) return NO;
    return YES;
}

然后您可以控制是否以及何时允许触摸进行响应。

请记住UINavigationBarUIResponder的子类,它允许您覆盖此方法和许多其他方法。我也经常忘记查找继承链。

答案 7 :(得分:0)

为什么不使用FullScreenViewController(来自UIViewController)而不是弄乱导航控制器堆栈中的视图,并将其作为应用程序的根目录。然后在其上添加NavController(及其堆栈)。到时候,只需淡化整个NavController,暴露你的FullScreenViewController。

(你也可以将这个想法颠倒过来 - 就像这样(在浏览器中输入 - 很多语法错误!):

UIViewController *vc = // ... FullScreenViewController
vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[navController presentViewController: vc animated: YES completion: nil];

注意:您还可以使用childViewControllers来创建一个包含VC的抽象类,它将是全屏和非全屏版本,然后根据需要窃取它的视图。