如何在旋转开始之前显示UIActivityIndi​​catorView

时间:2011-08-12 14:05:58

标签: iphone ios uiviewcontroller rotation uiactivityindicatorview

我想在 willAnimateRotationToInterfaceOrientation:duration:开始的工作之前显示一个活动指示器。大部分时间在我的应用程序中,这项工作很快完成,不需要活动指示器,但偶尔(第一次轮换,即在我缓存数据之前,使用大文件时)可能会有明显的延迟。我不是重新构建我的应用程序以应对这种不常见的情况,而是在应用程序生成缓存并更新显示时,我宁愿只显示 UIActivityIndi​​catorView

问题是(或似乎是) willRotateToInterfaceOrientation:duration 和willAnimateRotationToInterfaceOrientation:duration:方法之间没有更新显示。所以要求iOS在willRotate方法中显示 UIActivityIndi​​cator 视图实际上不会影响显示,直到 willAnimateRotation 方法之后。

以下代码说明了该问题。运行时,活动指示器仅在 simulateHardWorkNeededToGetDisplayInShapeBeforeRotation 方法完成后非常短暂地出现。

我错过了一些明显的东西吗?如果没有,关于如何解决这个问题的任何聪明的想法?

更新:虽然有关将繁重的工作转移到另一个线程等的建议通常很有帮助,但在我的特定情况下,我有点确实要阻止主线程做我的提升。在应用程序中,我有一个tableView所有高度都需要重新计算。当 - 这不是一个非常常见的用例或我甚至不考虑这种方法 - 有很多行,所有新的高度都在[tableView reloadData]期间计算(然后缓存)。如果我停止提升并让旋转继续进行,那么在旋转之后和提升之前,我的tableView没有被重新加载。例如,在纵向到横向的情况下,它不占据整个宽度。当然,还有其他解决方法,例如在旋转之前用几行构建一个tableView,然后重新加载那个等等。

用于说明问题的示例代码:

@implementation ActivityIndicatorViewController

@synthesize activityIndicatorView = _pgActivityIndicatorView;
@synthesize label = _pgLabel;

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}


- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
    NSLog(@"willRotate");

    [self showActivityIndicatorView];
}


- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
    NSLog(@"willAnimateRotation");
    [self simulateHardWorkNeededToGetDisplayInShapeBeforeRotation];
}


- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
    NSLog(@"didRotate");
    [self hideActivityIndicatorView];
}


- (void) simulateHardWorkNeededToGetDisplayInShapeBeforeRotation;
{
    NSLog(@"Starting simulated work");
    NSDate* date = [NSDate date];

    while (fabs([date timeIntervalSinceNow]) < 2.0)
    {
        //
    }
    NSLog(@"Finished simulated work");
}


- (void) showActivityIndicatorView;
{
    NSLog(@"showActivity");
    if (![self activityIndicatorView])
    {
        UIActivityIndicatorView* activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        [self setActivityIndicatorView:activityIndicatorView];
        [[self activityIndicatorView] setCenter:[[self view] center]];
        [[self activityIndicatorView] startAnimating];
        [[self view] addSubview: [self activityIndicatorView]];
    }

    // in shipping code, an animation with delay would be used to ensure no indicator would show in the good cases
    [[self activityIndicatorView] setHidden:NO];
}

- (void) hideActivityIndicatorView;
{
    NSLog(@"hideActivity");
    [[self activityIndicatorView] setHidden:YES];
}


- (void) dealloc;
{
    [_pgActivityIndicatorView release];
    [super dealloc];
}


- (void) viewDidLoad;
{
    UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(50.0, 50.0, 0.0, 0.0)];
    [label setText:@"Activity Indicator and Rotate"];
    [label setTextAlignment: UITextAlignmentCenter];
    [label sizeToFit];
    [[self view] addSubview:label];
    [self setLabel:label];
    [label release];
}

@end

4 个答案:

答案 0 :(得分:1)

在主要运行循环重新获得控制权之前,应用程序不会更新屏幕以显示UIActivityIndicatorView。当旋转事件发生时,willRotate...willAnimateRotation...方法在一次通过主运行循环时被调用。因此,在显示活动指示符之前,您将阻止努力工作方法。

要使这项工作,您需要将努力工作推到另一个线程。我会调用willRotate...方法中的hard work方法。当工作完成时,该方法将回调此视图控制器,以便可以更新视图。我会在willAnimateRotation...方法中显示活动指标。我不打扰didRotateFrom...方法。我建议您阅读Threaded Programming Guide

编辑以回复评论:您可以通过willAnimateRotation...方法在屏幕上放置一个无效界面(例如显示黑色叠加层的视图)来有效阻止用户互动UIActivityIndicatorView。然后,当完成重物时,移除该叠加,并且界面再次变为活动状态。然后,绘图代码将有机会正确添加活动指示符并为其设置动画。

答案 1 :(得分:1)

更多挖掘(首先在Matt Neuberg的编程iPhone 4中)然后this helpful question on forcing Core Animation to run its thread from stackoverflow我有一个似乎运行良好的解决方案。 Neuberg和Apple都对这种方法提出了强烈的谨慎态度,因为它可能会产生不受欢迎的副作用。到目前为止,在测试中,我的具体案例似乎没问题。

按如下方式更改上面的代码可实现更改。关键的补充是 [CATransaction flush] ,迫使UIActivityIndi​​catorView开始显示,即使在 willAnimateRotationToInterfaceOrientation:duration 方法完成之后运行循环也不会结束。

- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
    NSLog(@"willRotate");

    [self showActivityIndicatorView];
    [CATransaction flush];  // this starts the animation right away, w/o waiting for end of the run loop
}

- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
    NSLog(@"willAnimateRotation");
    [self simulateHardWorkNeededToGetDisplayInShapeBeforeRotation];
    [self hideActivityIndicatorView];
}


- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
    NSLog(@"didRotate");
}

答案 2 :(得分:0)

在显示活动视图后尝试执行第二个线程。

[self showActivityIndicatorView];
[self performSelector:@selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil afterDelay:0.01];

答案 3 :(得分:0)

在后台线程中执行繁重的工作并将结果发布到前台线程中以更新UI(UIKit仅在iOS 4.0中是线程安全的):

[self performSelectorInBackground:@selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil]

或者您可以安排在轮换发生后执行繁重的方法:

[self performSelector:@selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil afterDelay:0.4]

但这些只是黑客攻击,真正的解决方案是如果您的UI需要大量处理才能获得更新的正确后台处理,可能是纵向或横向。 NSOperationNSOperationQueue是一个很好的起点。