【问题标题】:How to refresh TableView Cell data during an NSTimer loop如何在 NSTimer 循环期间刷新 TableView 单元格数据
【发布时间】:2011-03-25 00:19:40
【问题描述】:

免责声明:我是 iOS 的新手,所以请随时告诉我,我的整个方法都是错误的。我只要求您解释原因——我的目标是学习(当然,也是为了解决这个问题)。

...

大家好,我是 iOS 编程新手,在尝试解决这个问题几个小时后,我的无知和可可缺乏经验正在成为我的最佳选择。希望我能在这里找到一些帮助。

我正在开发一个简单的 TableView 应用程序,作为一个正在开发中的大型应用程序的沙盒。我现在想要实现的功能是对 TableView Cell 中的字幕文本进行伪实时更新(即 cell.detailTextLabel.text )。

为了让您更好地了解目标:我正在努力做到这一点,以便用户可以点击 TableView 单元格,并且“秒表”样式的计时器开始在字幕文本中计数。

但我也不知道如何更新 cell.detailTextLabel.text A)从我的计时器方法内部(它说 tableView 未声明)和 B)以反复刷新单元格的方式,以便用户看到“活动" 计时器。

所以,这里有一些代码(现在所有这些都在 RootViewController 中。最终我想为像计时器这样的函数创建单独的类,但我只想让事情先工作)。

我还应该补充一点,我现在在这个类中有很多全局(“接口?”)变量。它使实验更容易,但如果这可能导致一些问题,请告诉我。我仍在学习如何来回传递对象/变量/等。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    self.runTimerFunctions;
}

然后是定时器函数(那些是接口变量):

- (void) runTimerFunctions {

    start = CFAbsoluteTimeGetCurrent();
    timerRunning = YES;
    timer = [NSTimer scheduledTimerWithTimeInterval: 0.01 target:self selector:@selector(targetMethod:) userInfo:nil repeats:YES];  
}

数学和计算本身。生成格式为 MM:SS.hh 的单个字符串“liveTimer”,该字符串准确地向上“秒表”:

- (void)targetMethod: (NSTimer *)theTimer {

    // NOTE TO SELF -- right now I've got hours, minutes, seconds, etc as global variables. But that may be unnecessary.

    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    CFAbsoluteTime timeDelta = end-start;
    int integerSecs = timeDelta;
    int integerCentiSecs = timeDelta * 100;

    hundredths = integerCentiSecs % 100;
    NSString *hundredthsString = [NSString stringWithFormat:@"%02i", hundredths];

    seconds = integerSecs % 60;
    NSString *secondsString = [NSString stringWithFormat:@"%02i", seconds];

    minutes = (integerSecs / 60) % 60;
    NSString *minutesString = [NSString stringWithFormat:@"%02i", minutes];

    // Now combine these value strings into the single liveTimer string

    liveTimer = [NSString stringWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString];

}

好的,所以 liveTimer 字符串是一个接口变量,所以我可以修改它并在整个过程中访问它(这可能不是这样做的方法,我还是个菜鸟,所以请在必要时纠正我)。

在最初的UITableViewCell创建位中,我使用liveTimer作为detail text value,如下:

cell.textLabel.text = @"Name";
NSLog(@"liveTimer value is %@", liveTimer);
cell.detailTextLabel.text = liveTimer;

基本上,就我现在的情况而言,当用户按下列表项时,计时器开始在后台计数(我已经通过 NSLog 确认它工作正常——如果我在 targetMethod 的末尾提取了 liveTimer,它的格式正确并向上计数。

但是如何通过单元格文本的伪实时更新来反映这一点?

[tableView reloadData] 当我将它放在任何我可以的地方时,它会不断使程序崩溃(没有错误代码),但我不知道如何从 targetMethod 访问它(它告诉我 tableView 在我添加 [tableView reloadData] 时未声明) .

此外,[tableView reloadData] 一直在使程序崩溃(没有错误消息),即使我尝试将其放入didSelectRowAtIndexPath 中也是如此。例如

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    self.runTimerFunctions;

    [tableView reloadData];

基本上我确信有一种简单的方法可以做我想做的事情,但我对这门语言还太陌生,不知道该怎么做。如果我通过在计时器循环期间尝试调用 reloadData 来使系统超载,我不会感到惊讶。但话又说回来,也许是别的东西。

任何建议将不胜感激。我正在尝试在这里学习语言,虽然示例代码非常有帮助,但我对“学习如何钓鱼”同样感兴趣,所以我非常感谢为什么我的解释我做错了,还有什么更好的方法,等等。

提前感谢您的帮助;我期待参与论坛。

【问题讨论】:

    标签: objective-c cocoa-touch xcode ios


    【解决方案1】:

    首先,一个小错误...

    liveTimer = [NSString stringWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString];

    这可能是调用 reload tableView 时崩溃的原因。

    你真的想要:

    要么:

    self.liveTimer = [NSString stringWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString];
    

    (如果您已将 liveTimer 配置为属性而不仅仅是成员变量)

    或

    [liveTimer release];
    liveTimer = [[NSString stringWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString] retain];
    

    或

    [liveTimer release];
    liveTimer = [[NSString alloc] initWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString];
    

    stringWithFormat:... 返回一个自动释放的字符串,这意味着它在当前运行循环结束时被释放。这将导致 liveTimer 在函数完成后基本上指向未定义的内存。使用 self.liveTimer 而不是 liveTimer 会导致调用合成的 setter 方法,而不是直接设置变量,这会导致对象在设置字符串时保留字符串(假设您已将实时计时器属性设置为保留,如下所示:@property (nonatomic, retain) NSString *liveTimer; 上面列出的另外两个替代方案都是释放旧版本的字符串然后保留新的字符串并设置然后直接设置变量。在第一个和最后一个之间你喜欢哪个或多或少是一个口味问题,第二个有点傻(它迂回地创建字符串,自动释放它,然后再次保留它。)

    现在,回答你的问题。

    在- (void)targetMethod: (NSTimer *)theTimer 的末尾,您需要实际更新当前表格单元格(如果存在)。假设 tableCell 存在于第 0 部分的第 0 行:

    -(void)targetMethod: (NSTimer *)theTimer {    
    
        // Everything you already had here, then...
    
        NSIndexPath *cellIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
        UITableViewCell *currentCell = [tableView cellForRowAtIndexPath:cellIndexPath];
        // Returns the cell that's currently in the tableview at this location
    
        currentCell.detailTextLabel.text = self.liveTimer;
    }
    

    应该这样做...如果当前单元格不存在(因为它已从屏幕上滚动)cellForRowAtIndexPath: 将返回 nil,currentCell.detailTextLabel.text = self.liveTimer; 将什么也不做(即没有错误,实际上什么也没有)。

    编辑:

    我刚刚注意到 tableView 不是您的对象成员变量之一...

    你有一个可以联系到的UITableViewController吗?如果是这样,只需在您当前使用 tableView 的任何地方使用tableViewControllerVariableName.tableView。您的 RootViewController(所有这些代码都在其中)是 UITableViewController 吗?如果是这样,请在您当前调用 tableView 的任何地方调用 self.tableView。如果没有,您的 tableView/tableViewController 分配在哪里?发布您的头文件可能有助于清除问题...

    编辑 2:

    liveTimer 与 self.liveTimer 的问题在于对象的保留计数。

    liveTimer = [NSString stringWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString];
    

    就像它看起来的那样——它将你的 liveTimer 变量设置为新的字符串。而

    self.liveTimer = [NSString stringWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString];
    

    有点神奇。使用self. 前缀(或者,就此而言anyObject.)告诉编译器“我在这里真正想做的是调用liveTimer 属性的访问器方法”。编译器实质上将上述行替换为:

    [self setLiveTimer:[NSString stringWithFormat:@"%@:%@.%@", minutesString, secondsString, hundredthsString]];
    

    因为您可能在类文件中的某处有@synthesize liveTimer;,编译器还会自动为您创建-(void)setLiveTimer:(NSTimer *)timer 和-(NSTimer *)liveTimer 方法。它们看起来像这样:

    -(void)setLiveTimer:(NSTimer *)newLiveTimer
    {
        [liveTimer release];  // This is the old value.  It may be nil, which is fine.  Calling methods on nil objects in objective-c is just be a noop, not a crash.
        liveTimer = [newLiveTimer retain];
    }
    
    -(NSTimer *)liveTimer
    {
        return liveTimer;
    }
    

    (这有点过于简化了,但这是重要的东西。)

    因此,当您调用 self.liveTimer 时,setLiveTimer: 方法会随对象一起调用,旧对象(如果有)会被释放,而新对象会被保留。

    [NSString stringWithFormat:...] 返回一个自动释放的对象。在控制从程序代码返回到苹果提供的运行循环代码后,自动释放的对象被释放。它们或多或少是一种方便,因此我们不必release 我们在这里和那里使用一次或两次的所有小对象。 (例如,想象一下,如果您必须释放使用 @"" 语法创建的每个字符串,那将是多么乏味......)

    我们可以告诉stringWithFormat: 返回一个自动释放的对象,因为按照惯例,名称不以alloc 或copy 开头的方法总是返回自动释放的对象。像这样的方法被称为“出售”一个对象。我们可以在不久的将来使用这些对象,但我们不“拥有”它(即,在我们将控制权交还给系统后,我们不能指望它存在。)如果我们想获得一个出售的对象的所有权,我们必须在上面调用[object retain],然后它会一直存在,直到我们明确调用[object release]或[object autorelease],如果我们不调用release或autorelease,在我们失去对它的引用之前通过将变量更改为其他内容,我们将泄漏它。

    对比[[NSString alloc] initWithFormat:。此方法“创建”一个对象。我们拥有它。同样,它会一直存在,直到我们明确调用 [object release]。

    所以,您将成员变量 liveTimer 设置为一个新字符串,但该字符串是自动释放的,并且一旦您的 - (void)targetMethod: (NSTimer *)theTimer 返回,控制返回到系统的运行循环代码,并且该字符串被释放了。这意味着 liveTimer 指向内存中的某个随机点,该点曾经包含您的字符串对象,但现在包含其他东西——未定义的东西。当你在内存中的那个随机位置调用一个方法时,它崩溃了。

    (为了进一步混淆,[NSTimer scheduledTimerWithTimeInterval:target:selector:] 是一种特殊情况。计时器会一直保留自己直到它触发,这就是为什么您的代码的这部分工作。没有人真正知道为什么 NSTimer 会出现异常;它令人困惑。它是NExT 时代遗留下来的代码——在 90 年代初编写的代码,当时对某些人来说这似乎是个好主意。)

    那么,您想在任何地方使用self.liveTimer 或self.timer,还是self.whatever?或多或少。您通常希望保留新对象并释放旧对象。 唯一的许多例外之一是,如果您使用[[Object alloc] init]“创建”一个对象,那么该对象的保留计数已经为1,在这种情况下,使用self.语法将第二次保留该对象,这可能不是您想要的。 (虽然,如果你之前设置了self.liveTimer =,然后在你的代码中使用liveTimer =,第一个字符串将被泄露,因为没有任何东西释放它。这并不总是微不足道的,决定采用哪种方法。)

    希望这能把事情弄清楚一点。我认为,objective-c 中的这种点符号是该语言中最不直观的部分。请务必阅读苹果关于内存管理的文档:

    http://developer.apple.com/library/ios/#documentation/general/conceptual/DevPedia-CocoaCore/MemoryManagement.html

    【讨论】:

    • 嗨,彼得,感谢您抽出宝贵时间回复。今天晚些时候我会看看这个,并发布我的头文件。再次感谢!
    • 好的,我查看了您的反馈 Peter,现在一切正常。谢谢!我仍然不完全理解我做了什么,所以我有几个问题。首先,我是否将“liveTimer”替换为“self.liveTimer”literally 出现的所有地方?我已经这样做了,但不确定我是否应该这样做。它虽然有效。其次,liveTimer 是一个属性,所以您的第一条评论在那里有效。
    • 最后,关于您的 viewController 问题。这是一个 UITableViewController,所以(如前所述)self.tableView 似乎已经解决了问题。我不明白的是为什么我能够做到这一点。我在 XCode 中使用 TableView 模板,所以“免费赠品”意味着我可以做一些我不知道如何实现的事情。假设我在委托(如果合适的话?)或其他类中实现此代码,那么我将如何访问 tableView?再次感谢!
    • @Murdock 刚刚添加了对答案的编辑以(尝试)回答您的第一个问题。至于为什么 self.tableView 有效。模板为您创建的对象是 UITableViewController 的子类,它自动处理创建实际 UITableView 并将其 tableView 属性设置为它创建的 UITableView。因为您的视图控制器继承自 UITableViewController,所以您可以使用 self 变量访问它的属性,就像您可以访问自己的任何属性一样...
    • ... 如果你在另一个对象中,但你在一个变量中有一个 UITableViewController,这样说:(UITableViewController *mainTVC = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain]),你会得到通过调用 mainTVC.tableView 到由该 UITableViewController 创建的 UITableView。这一切都清楚了吗?
    猜你喜欢
    • 2013-06-11
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-21
    • 2016-05-19
    • 1970-01-01
    相关资源
    最近更新 更多