代码之家  ›  专栏  ›  技术社区  ›  Tim Bowen

为什么popViewController每隔一段时间才工作

  •  6
  • Tim Bowen  · 技术社区  · 17 年前

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

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

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

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

    因此,从服务器抓取的初始数据是完美的。

    我第一次点击refresh时,所有代码都会执行以获取新位置,再次ping服务器以获取新的数据列表并更新单元格。但是,它没有像应该的那样加载表视图,而是恢复了表视图的导航控制器栏,但仍然在主窗口中显示我的加载视图。这只在设备上是真实的,在模拟器中一切都很好。

    第二次点击刷新时,该功能正常工作。

    第三次按刷新时,它失败,如上所述。

    我第四次按“刷新”时,它工作正常。

    第五次按刷新时,它失败,如上所述。

    以此类推,偶数刷新成功,奇数刷新失败。我一行一行地检查了我所有的代码,似乎一切都在正常执行。实际上,我继续浏览核心指令,在大量单击“逐步浏览”之后,我发现在CFRunLoopRunSpecific中的某个点上,表视图确实显示在屏幕上,但我随后单击了“继续”,我的加载视图接管了屏幕。

    Video of the strange behavior:

    相关代码:

    - (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 回复  |  直到 17 年前
        1
  •  1
  •   phatblat Dave DeLong    17 年前

    第一个想法是,在推送加载视图之前,您可能不想将startUpdatingLocation发送到CLLocationManager。通常,第一条-locationManager:didUpdateToLocation:fromLocation:消息会与缓存的GPS数据一起立即出现。只有当您处理每条消息,而不是如示例代码所示过滤GPS数据时,这才重要。但是,这不会导致您描述的情况-它会导致加载屏幕卡住。

    在另一种情况下,我也经历过类似的奇怪行为,在切换到其他选项卡时,我试图弹出根视图控制器,但调用没有在正确的位置进行。我相信popToRootViewController为我打了两次电话。我怀疑你的加载视图不是被推了两次就是被弹出了两次。

    我建议在加载ViewController时只需最少的日志记录即可实现-viewWillAppease:、-viewDidAppease:、-viewWillEveal:和-viewDidEveal:。

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

    另一种想法是,尽管@synchronized块是个好主意,但它只会阻止其他线程执行这些语句,直到第一个线程退出该块。我建议将-stopUpdatengLocation消息移动到@synchronized块中的第一条语句。这样,一旦您决定对一些新的GPS数据采取行动,您会立即通知CLLocationManager停止发送新数据。

        2
  •  0
  •   lostInTransit    17 年前

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

    确保在LoadingViewController类中没有内存警告。如果出现内存警告,并且正在释放RootViewController的视图,则在弹出RootViewController时,将再次调用viewDidLoad。

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

        3
  •  0
  •   Tim Bowen    17 年前

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

    我的解决方法是构建一个自定义加载视图,每次由于访问互联网而出现延迟时,都将屏幕翻转到该视图。我的方法使用一个布尔变量yes或no-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
  •  0
  •   Rob Napier    17 年前

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

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

    viewDidLoad 每次加载NIB时都会调用,这种情况可能会发生多次,特别是当内存不足时(考虑到您的评论,这种情况似乎只发生在设备上)。我建议你实施 -didReciveMemoryWarning ,然后在调用super之后,至少打印一个日志,这样您就可以看到它是否发生在您身上。

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

        5
  •  0
  •   jkira    14 年前

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

    在interface builder中将两个iAction指定给同一个UIButton时,可能会导致此错误。结果是,我用来将视图控制器推到堆栈上的按钮被分配给了两个iAction,每个iAction都在将不同的控制器推到navigationController的堆栈上(尽管您最终只能看到其中一个,可能是最后一个被调用的)。因此,不管怎样,按下最上面视图上的后退按钮并不会真正关闭它(或者可能会关闭第二个未显示的控制器),您必须按下两次才能返回。

    无论如何,请检查您的按钮,并确保它们仅分配给单个iAction。那帮我修好了。