3

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

我可以简单地淡化导航栏的 alpha,但是为了允许用户点击新获得的区域(导航栏现在已经淡出)并让它做一些事情,我要做的不仅仅是改变alpha,因为从技术上讲,导航栏仍然占据区域。

所以我用隐藏导航栏[self.navigationController setNavigationBarHidden:YES animated:NO];。我必须在动画块完成后执行此操作,否则它将在动画块中并作为块的一部分进行动画处理。所以我用 adispatch_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;
    }
}

我唯一要做的另一件事是更改我的自动布局约束上的常量,以根据导航栏和状态栏是否存在来考虑它们。

我不确定如何考虑双击确实会导致全屏过程出现故障的事实。我怎么能做到这一点,如果他们在动画过程中点击它只会取消动画并按预期执行他们想要的动作?我可以更好地完成这个过程吗?

4

8 回答 8

2

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

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

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

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

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];
    }
}
于 2013-08-07T04:42:44.420 回答
2

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

于 2013-08-06T17:11:08.747 回答
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。

于 2013-08-07T00:10:41.627 回答
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;
    }
}

我没有直接测试过这段代码,但你可以看到我在做什么,我敢肯定。

于 2013-08-06T16:54:46.337 回答
1

为什么不使用 animationCompleted 委托或块?

于 2013-08-02T16:42:32.423 回答
0

要在动画 then 子类时关闭接收触摸事件UINavigationBar并添加以下方法:

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

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

请记住,这UINavigationBar是一个UIResponder允许您覆盖此方法和许多其他方法的子类。我也经常忘记查找继承链。

于 2013-08-06T17:00:27.403 回答
0

与其弄乱导航控制器堆栈中的视图,不如使用 FullScreenViewController(来自 UIViewController)并将其作为应用程序的根。然后在上面添加 NavController(及其堆栈)。时机成熟时,只需淡化整个 NavController,露出您的 FullScreenViewController。

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

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

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

于 2013-08-07T02:44:35.603 回答
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;
        }
    }
}];

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

于 2013-08-06T16:40:57.857 回答