【问题标题】:Why does popViewController only work every other time为什么popViewController每隔一段时间才工作一次
【发布时间】:2010-10-23 13:08:13
【问题描述】:

我完全被难住了,情况是这样的:

我的应用程序使用 Core Location 框架来获取用户的当前位置,然后在 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];
    }
}

【问题讨论】:

  • 您是否在调试器中逐步完成了这些方法?在其上运行 Instruments 以查看对象创建/删除是否符合您的预期?抱歉,如果我侮辱了您的智商,但由于我没有快速查看问题,所以接下来我要做的就是开始逐步解决......
  • 离开应用是否会弹出?您似乎没有足够的时间离开应用程序来获取视频中的 GPS 定位。尝试在 locationManager 方法中设置断点。

标签: iphone cocoa-touch uiviewcontroller uinavigationcontroller


【解决方案1】:

首先想到的是,在您推送加载视图之前,您可能不想将 startUpdatingLocation 发送到 CLLocationManager。通常第一个 -locationManager:didUpdateToLocation:fromLocation: 消息会立即与缓存的 GPS 数据一起出现。仅当您对每条消息都采取行动并且不过滤 GPS 数据(如此处的示例代码中所示)时,这才重要。但是,这不会导致您描述的情况 - 它会导致加载屏幕卡住。

我在不同的情况下也遇到过类似的奇怪行为,当我在切换到不同的选项卡时尝试弹出到根视图控制器并且没有在正确的位置进行调用。我相信 popToRootViewController 被我调用了两次。我怀疑您的加载视图要么被推送两次,要么被弹出两次。

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

- (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 停止发送新数据。

【讨论】:

    【解决方案2】:

    您能否尝试调试您的应用程序以查看调用 updateCells 时控件的位置?该应用似乎没有任何明显问题。

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

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

    【讨论】:

    • 我在这两个函数中都设置了断点,并且在弹出加载屏幕时不会调用 viewDidLoad。我的 didRecieveMemoryError 函数也没有被调用。调用 updateCells 后,控制返回到核心堆栈,我认为那里的某些东西可能会惹恼我。
    【解决方案3】:

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

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

    - (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];
    }
    

    }

    【讨论】:

      【解决方案4】:

      看你原来的代码,我很怀疑这个块:

      - (void)viewDidLoad {
          ...
          lv = [[LoadingViewController alloc] initWithNibName:@"Loading" bundle:nil];
          [self.navigationController pushViewController:lv animated:YES];
          [super viewDidLoad];
       }
      

      viewDidLoad 每次加载 NIB 时都会被调用,这可能会发生多次,尤其是当您的内存不足时(这似乎很可能是因为您说它只发生在设备上)。我建议你实现-didReciveMemoryWarning,在调用super之后,至少打印一个日志,这样你就可以看到它是否发生在你身上。

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          猜你喜欢
          • 2021-08-16
          • 1970-01-01
          • 2015-10-13
          • 2015-11-14
          • 2011-01-25
          • 1970-01-01
          • 1970-01-01
          • 2020-05-02
          • 2017-11-17
          相关资源
          最近更新 更多