为什么popViewController只能每隔一段时间工作一次

时间:2009-05-04 02:25:47

标签: iphone cocoa-touch uiviewcontroller uinavigationcontroller

我完全被难过了,情况如下:

我的应用使用核心位置框架来获取用户的当前位置,然后在TrailBehind将我的服务器ping到附近有趣的地方并将其显示为列表。没问题。

为了节省电池,我从服务器获取数据后关闭了GPS服务。如果用户在使用应用程序时四处移动并想要一个新列表,则单击导航控制器上的“刷新”并再次激活CLLocation服务,将从服务器检索新的一批数据并重新绘制该表。

当应用程序从我的服务器抓取数据时,我加载了一个带有旋转地球仪的加载屏幕,上面写着“正在加载,请稍候”,我隐藏了导航栏,因此它们不会“返回”。

因此,从服务器获取的初始数据可以完美无缺。

第一次点击刷新所有代码执行以获取新位置,再次ping服务器以获取新的数据列表并更新单元格。但是,它应该恢复表视图的导航控制器栏,而不是加载表视图,但仍然在主窗口中显示我的加载视图。这只适用于设备,一切都在模拟器中完全正常。

第二次点击刷新功能正常。

我点击刷新的第三次失败如上所述。

我点击刷新的第四次正常工作。

我点击刷新的第五次时间如上所述失败。

等等,甚至刷新成功,奇怪的刷新失败。我逐行遍历所有代码,一切似乎正常执行。我实际上继续踩过核心指令,经过大量点击“跳过”我发现表格视图实际上在CFRunLoopRunSpecific的某个点上显示在屏幕上,但我点击了“继续”并且我的加载视图接管了屏幕。

我绝对感到困惑。请帮忙!!非常感谢您的见解。

Video of the strange behavior:

相关守则:

RootViewControllerMethods(这是此TableView项目的基本视图)

- (void)viewDidLoad {
    //Start the Current Location controller as soon as the program starts.  The Controller calls delegate methods
    //that will update the list and refresh
    [MyCLController sharedInstance].delegate = self;
    [[MyCLController sharedInstance].locationManager startUpdatingLocation];
    lv = [[LoadingViewController alloc] initWithNibName:@"Loading" bundle:nil];
    [self.navigationController pushViewController:lv animated:YES];
    [super viewDidLoad];
}



- (void)updateClicked {
    //When the location is successfully updated the UpdateCells method will stop the CL manager from updating, so when we want to update the location
    //all we have to do is start it up again.  I hope.
    [[MyCLController sharedInstance].locationManager startUpdatingLocation];
    [self.navigationController pushViewController:lv animated:YES];
    //LV is a class object which is of type UIViewController and contains my spinning globe/loading view.
}



-(void)updateCells {
    //When the Core Location controller has updated its location it calls this metod.  The method sends a request for a JSON dictionary
    //to trailbehind and stores the response in the class variable jsonArray.  reloadData is then called which causes the table to
    //re-initialize the table with the new data in jsonArray and display it on the screen.  

    [[MyCLController sharedInstance].locationManager stopUpdatingLocation];

    if(self.navigationController.visibleViewController != self) {
        self.urlString = [NSString stringWithFormat:@"http://www.trailbehind.com/iphone/nodes/%@/%@/2/10",self.lat,self.lon];
        NSURL *jsonURL = [NSURL URLWithString:self.urlString];
        NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
        NSLog(@"JsonData = %@ \n", jsonURL);
        self.jsonArray = [jsonData JSONValue];
        [self.tableView reloadData];
        [self.navigationController popToRootViewControllerAnimated:YES];
        [jsonData release];
    }
}

CLController方法:基本上只是将所有数据直接发送回RootViewController

// Called when the location is updated
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation
{
    NSLog(@"New Location: %@ \n", newLocation);
    NSLog(@"Old Location: %@ \n", oldLocation);
    @synchronized(self) {
        NSNumber *lat = [[[NSNumber alloc] init] autorelease];
        NSNumber *lon = [[[NSNumber alloc] init] autorelease];
        lat = [NSNumber numberWithFloat:newLocation.coordinate.latitude];
        lon = [NSNumber numberWithFloat:newLocation.coordinate.longitude];
        [self.delegate noteLat:lat];
        [self.delegate noteLon:lon];
        [self.delegate noteNewLocation:newLocation];
        [self.delegate updateCells];
    }
}

5 个答案:

答案 0 :(得分:1)

首先想到的是,在推送加载视图之前,您可能不希望将startUpdatingLocation发送到CLLocationManager。通常,第一个-locationManager:didUpdateToLocation:fromLocation:消息将立即显示缓存的GPS数据。这仅在您对每条消息进行操作而不是过滤GPS数据时才有意义,如此处的示例代码所示。但是,这不会导致您描述的情况 - 这会导致加载屏幕卡住。

在不同的情况下,我遇到了类似这种奇怪的行为,当我切换到不同的标签时,我试图弹出到根视图控制器,并且没有在正确的位置进行调用。我相信popToRootViewController正在为我调用两次。我怀疑你的加载视图是被推两次还是弹出两次。

我建议实现-viewWillAppear:, - viewDidAppear:, - viewWillDisappear:和-viewDidDisappear:在LoadingViewController中使用最少的日志记录。

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"[%@ viewWillAppear:%d]", [self class], animated);
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated {
    NSLog(@"[%@ viewDidAppear:%d]", [self class], animated);
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"[%@ viewWillDisappear:%d]", [self class], animated);
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated {
    NSLog(@"[%@ viewDidDisappear:%d]", [self class], animated);
    [super viewDidDisappear:animated];
}

然后,在您的设备上运行测试,看看它们是否总是被发送到您的视图控制器以及频率。您可以将一些日志记录添加到-updateClicked以显示双击。

另一个想法,虽然你的@synchronized块是一个好主意,它只会阻止其他线程执行这些语句,直到第一个线程退出块。我建议将-stopUpdatingLocation消息移动到@synchronized块中的第一个语句。这样,一旦你决定采取一些新的GPS数据,你立即告诉CLLocationManager停止发送新数据。

答案 1 :(得分:0)

您可以尝试调试应用程序,以便在调用updateCells时查看控件的位置吗?该应用程序似乎没有任何明显错误。

确保在LoadingViewController类中没有内存警告。如果存在内存警告且正在释放RootViewController的视图,那么当您对RootViewController进行弹出时,将再次调用viewDidLoad。

在viewDidLoad和updateCells中保留断点。你确定你没有在其他任何地方调用LoadingViewController吗?

答案 2 :(得分:0)

所以,我从来没有让这个工作。我每次以编程方式调用popViewController而不是允许导航控制器上的默认后退按钮进行弹出时,我都会在设备上观察到这种行为。

我的解决方法是构建自定义加载视图,并在每次因访问互联网而出现延迟时将屏幕翻转到该视图。我的方法采用是或否的布尔变量 - 是切换到加载屏幕,没有切换回正常视图。这是代码:

- (void)switchViewsToLoading:(BOOL)loading {
// Start the Animation Block  
CGContextRef context = UIGraphicsGetCurrentContext();  
[UIView beginAnimations:nil context:context];  
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.tableView cache:YES];  
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];  
[UIView setAnimationDuration:.75];  

// Animations  
if(loading) { 
    if (lv == nil) { lv = [[LoadingViewController alloc] initWithNibName:@"Loading" bundle:nil]; }
    [self.view addSubview:lv.view];
    [self.view sendSubviewToBack:self.tableView];
    self.title = @"TrailBehind";
}
else {
    [lv.view removeFromSuperview];

}
// Commit Animation Block  
[UIView commitAnimations];  
//It looks kind of dumb to animate the nav bar buttons, so set those here
if(loading) {
    self.navigationItem.rightBarButtonItem = nil;
    self.navigationItem.leftBarButtonItem = nil;
    self.title = @"TrailBehind";
}
else {
    UIBarButtonItem *feedback = [[UIBarButtonItem alloc] initWithTitle:@"Feedback" style:UIBarButtonItemStylePlain target:self action:@selector(feedbackClicked)];
    self.navigationItem.rightBarButtonItem = feedback;
    UIBarButtonItem *update = [[UIBarButtonItem alloc] initWithTitle:@"Move Me" style:UIBarButtonItemStylePlain target:self action:@selector(updateClicked)];
    self.navigationItem.leftBarButtonItem = update;
    [feedback release];
    [update release];
}

}

答案 3 :(得分:0)

查看原始代码,我非常怀疑这块:

- (void)viewDidLoad {
    ...
    lv = [[LoadingViewController alloc] initWithNibName:@"Loading" bundle:nil];
    [self.navigationController pushViewController:lv animated:YES];
    [super viewDidLoad];
 }
每次加载NIB时都会调用

viewDidLoad,这可能会多次发生,特别是如果你的内存不足(看起来很有可能你的评论只发生在设备上)。我建议你实现-didReciveMemoryWarning,在调用super之后,至少打印一个日志,这样你就可以看到它是否正在发生在你身上。

让我困扰上述代码的是你几乎肯定会泄漏lv,这意味着可能会有越来越多的LoadingViewControllers在运行。你说它是一个类变量。你真的是说它是一个实例变量吗? ivars应始终使用访问者(self.lv[self lv]而不是lv)。不要直接分配给他们;你几乎总是做错了(因为你很可能在这里)。

答案 4 :(得分:0)

我在搜索完全相同的问题时遇到了这个问题,所以虽然我确定你现在已经解决了你的问题,但我想我会发布我的解决方案以防其他人遇到它... < / p>

当您在界面构建器中将两个IBActions分配给同一个UIButton时,似乎会导致此错误。事实证明,我用来将视图控制器推到堆栈上的按钮被分配给两个IBActions,每个按钮都将不同的控制器推送到navigationController的堆栈上(尽管你最终只会看到其中一个 - 也许是最后一个一个被称为)。所以无论如何,按下最顶层视图上的后退按钮并没有真正解除它(或者它可能会解雇第二个看不见的控制器),你必须按两次才能返回。

无论如何,检查您的按钮并确保它们仅分配给单个IBAction。这为我解决了。