如何在NSTimer循环期间刷新TableView单元数据 [英] How to refresh TableView Cell data during an NSTimer loop

查看:104
本文介绍了如何在NSTimer循环期间刷新TableView单元数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

免责声明:我是一个新的转换iOS,所以请随时告诉我,我的整个方法是错误的。我只要求你解释为什么 - 我的目标是学习(当然,也解决这个问题)。



...



您好,我是新的iOS编程,并试图解决这个问题几个小时,我的无知和可可无经验是得到我最好的。希望我能在这里找到一些帮助。



我有一个简单的TableView应用程序,我正在做一个更大的应用程序的沙箱中的工作。我现在想实现的功能是在TableView单元格中的字幕文本的伪实时更新(即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中生成准确地向上秒表:

   - )targetMethod:(NSTimer *)theTimer {

//注意自我 - 现在我有全局变量的小时,分​​钟,秒等。但这可能是不必要的。

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

百ths= 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];

//现在将这些值字符串合并到单个liveTimer字符串

liveTimer = [NSString stringWithFormat:@%@:%@。%@,minutesString,secondsString,百分之百

}

好的,所以liveTimer字符串是一个接口变量,所以我可以修改它并访问它(这可能不是这样做的方式,再次我是一个noob所以请纠正我在任何必要的地方)。



在初始UITableViewCell的创建位,我使用liveTimer的细节文本值,如下:

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

实际上,我现在的方式,当用户按下列表项时,计时器开始在后台计数 (我已经确认与NSLog它的工作正常 - 如果我提取liveTimer在targetMethod结束时,它的格式正确,并向上计数。



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



[tableView reloadData]代码)当我把它放在任何地方,我可以,但我不知道如何从targetMethod(它告诉我tableView undeclared当我添加[tableView reloadData])。



另外,[tableView reloadData]即使在我试图将其放入例如didSelectRowAtIndexPath中时仍会崩溃程序(没有错误消息)。

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

[tableView deselectRowAtIndexPath:indexPath animated:YES];

self.runTimerFunctions;

[tableView reloadData];

基本上我确定有一个简单的方法来做我想做的事, 还是太新了,语言知道怎么做。如果我通过尝试在定时器循环期间调用reloadData来重载系统,我不会感到惊讶。但是再次,也许这是别的。



任何建议都会非常感激。我试图在这里学习语言,虽然示例代码是非常有帮助的,我对学习如何钓鱼同样感兴趣,所以我真的很感谢解释为什么



感谢您提供的所有帮助;我期待着参与论坛。

解决方案

首先,一个小错误...



liveTimer = [NSString stringWithFormat:@%@:%@。%@,minutesString,secondsString,hundredthsString]; p>

这可能是为什么当你调用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方法,而不是直接设置变量,这会导致对象在设置时保留字符串(假设您已经设置了live timer属性,例如: : @property(nonatomic,retain)NSString * liveTimer; 上面列出的其他两个选项都释放旧版本的字符串,然后保留新的字符串,然后设置变量直接。你喜欢的第一个和最后一个或多或少是品味的问题,第二个是一种愚蠢(它迂回地创建字符串,自动释放它,然后再保留它。)



结束时 - (void)targetMethod:假设tableCell存在于第0行的第0行:

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

//所有你已经在这里,然后...

NSIndexPath * cellIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell * currentCell = [tableView cellForRowAtIndexPath:cellIndexPath];
//返回当前在此位置的tableview中的单元格

currentCell.detailTextLabel.text = self.liveTimer;
}

这应该是...如果当前单元格不存在因为它是从屏幕上滚动的) cellForRowAtIndexPath:将返回nil和 currentCell.detailTextLabel.text = self.liveTimer;



我只是注意到tableView()没有任何错误,不是你的对象成员变量之一...



你有一个 UITableViewController 至?如果是这样,只需使用 tableViewControllerVariableName.tableView 到处你目前使用tableView。你的RootViewController(所有这些代码都在)一个UITableViewController?如果这样,调用 self.tableView 无论你当前调用tableView。如果没有,你的tableView / tableViewController在哪里被分配?发布头文件可能有助于清除...



编辑2:



liveTimer vs. 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]; //这是旧值。它可能是零,这是罚款。在objective-c中对nil对象的调用方法只是一个noop,而不是崩溃。
liveTimer = [newLiveTimer retain];
}

- (NSTimer *)liveTimer
{
return liveTimer;
}

(这是一个过度简化,但这是重要的东西。



因此,当你调用 self.liveTimer 时, setLiveTimer:



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



我们可以告诉 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 days - 在90年代初写的代码,对某人来说似乎是一个好主意。)



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



希望清除一些东西。这个虚线符号的东西在objective-c是语言中最不直观的部分,我想。请务必阅读关于内存管理的苹果文档:



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


Disclaimer: I'm a new convert to iOS, so please feel free to tell me that my whole approach is wrong. I only ask that you explain why -- my goal here is to learn (and, of course, also to solve this problem).

...

Hi all, I'm new to iOS programming and after trying to solve this problem for several hours, my ignorance and cocoa inexperience is getting the best of me. Hoping I can find some help here.

I've got a simple TableView App I'm working on as a sandbox for a larger app that's in the works. The functionality I'm trying to achieve right now is a pseudo real-time update of the subtitle text in a TableView Cell (i.e. cell.detailTextLabel.text ).

To give you a better idea of the goal: I'm trying to make it so the user can tap the TableView cell and a "stopwatch" style timer starts counting up in the subtitle text.

But I can't figure out how to update cell.detailTextLabel.text either A) From inside my timer methods (it says tableView undeclared) and B) In a way that refreshes the cell repeatedly so the user sees an "active" timer.

So, here's some code (all of this is in RootViewController right now. Eventually I'll want to make separate classes for functions like the timer, but I just want to get things working first).

I should also add that I've got a lot of global ("interface?") variables in this class right now. It makes it easier to experiment, but if that could be causing some of the problems please let me know. I'm still learning how to pass objects/variables/etc back and forth.

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

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    self.runTimerFunctions;
}

Then the timer function (those are interface variables):

- (void) runTimerFunctions {

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

The math and calculation itself. Results in a single string "liveTimer" of the format MM:SS.hh that accurately "stopwatches" upward:

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

}

Okay, so the liveTimer string is an interface variable so I can modify it and access it throughout (this may not be the way to do this, again I'm a noob so please correct me wherever necessary).

In the initial UITableViewCell creation bit, I use liveTimer for the detail text value, as follows:

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

So essentially, the way I have things right now, when the user presses the list item, the timer starts counting in the background (I've confirmed with NSLog that it's working correctly -- if I extract liveTimer at the end of targetMethod it's correctly formatted and counting upward.

But how do I reflect this via a pseudo real-time update of the cell text?

[tableView reloadData] keeps crashing the program (without error code) when I put it anywhere I can, but I don't know how to access it from targetMethod (it tells me tableView undeclared when I add [tableView reloadData] ).

Further, [tableView reloadData] keeps crashing the program (without error message) even when I try to drop it experimentally into, say, didSelectRowAtIndexPath. e.g.

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

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    self.runTimerFunctions;

    [tableView reloadData];

Essentially I'm sure there's a simple way to do what I'm trying to do, but I'm still too new to the language to know how to do it. I wouldn't be surprised if I'm overloading the system by trying to call reloadData during the timer loop. But then again, maybe it's something else.

Any advice would be much appreciated. I'm trying to learn the language here so, although sample code is extremely helpful, I'm equally interested in "learning how to fish", so I'd really appreciate explanations of why what I'm doing is wrong and what the better ways to do it might be, etc.

Thanks in advance for all your help; I'm looking forward to getting involved on the forums.

解决方案

First, a small bug...

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

This is probably why it was crashing when you called reload tableView.

You actually want:

either:

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

(if you've configured liveTimer to be a property and not just a member variable)

or

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

or

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

stringWithFormat:... returns an auto-released string, which means that it gets released as at the end of the current spin through the run loop. This will result in liveTimer pointing at undefined memory essentially after your function finishes. Using self.liveTimer instead of liveTimer causes your synthesized setter method to be called instead of just setting the variable directly, which causes your object to retain the string as it's being set (assuming you've setup the live timer property to retain, like so: @property (nonatomic, retain) NSString *liveTimer; The other two alternatives listed above both release the old version of the string and then retain the new string and set then set the variable directly. Which one you prefer between the first and the last is more or less a matter of taste, the second one is kind of silly (it roundaboutly creates the string, autoreleases it, and then retains it again.)

Now, on to your question.

At the end of - (void)targetMethod: (NSTimer *)theTimer, you'll want to actually update the current table cell, if it exisits. Assume the tableCell exists at row 0 of section 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;
}

That should do it... if the current cell doesn't exist (because it's scrolled off the screen) cellForRowAtIndexPath: will return nil, and currentCell.detailTextLabel.text = self.liveTimer; will do nothing (i.e. no error, just literally nothing).

Edit:

I just noticed that tableView is not one of your object member variables...

Do you have a UITableViewController hanging around that you can get to? If so, just use tableViewControllerVariableName.tableView everywhere you presently use tableView. Is your RootViewController (which all this code is in) a UITableViewController? If so call self.tableView wherever you currently call tableView. If not, where is your tableView/tableViewController being allocated? Posting your header file might help clear things up...

Edit 2:

The issue with liveTimer vs. self.liveTimer is all about the retain count on the object.

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

does just what it looks like--it sets your liveTimer variable to the new string. Whereas

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

is a little bit magic. Using the self. prefix (or, for that matter anyObject.) tells the compiler "what I really want to do here is call the accessor method for the liveTimer property". The compiler essentially replaces the above line with:

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

Because you presumably have @synthesize liveTimer; somewhere in your class file, the compiler also creates the -(void)setLiveTimer:(NSTimer *)timer and -(NSTimer *)liveTimer methods for you automatically. They looks something like this:

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

(This is a bit of an over-simplification, but that's the important stuff.)

So, when you call self.liveTimer, the setLiveTimer: method gets called with the object, the old object, if any, gets released, and the new object gets retained.

[NSString stringWithFormat:...] returns an autoreleased object. Autoreleased objects get released after control returns from the program's code to the apple-supplied run-loop code. They are more or less a convenience so we don't have to release all the little objects that we use once or twice here and there. (For example, imagine how tedious it would be if you had to release every string you created with the @"" syntax...)

We can tell stringWithFormat: returns an autoreleased object because, by convention, methods who's names don't start with alloc or copy always return auto-released objects. Methods like this are said to "vend" an object. We can use these objects in the immediate future, but we don't "own" it (i.e. we can't count on it being there after we return control to the system.) If we want to take ownership of a vended object, we have to call [object retain] on it, and then it will be there until we explicitly call [object release] or [object autorelease], and if we don't call release or autorelease on it before we lose our reference to it by changing the variable to something else, we will leak it.

Contrast with [[NSString alloc] initWithFormat:. This method "creates" an object. We own it. Again, it will be there until we explicitly call [object release].

So, you were setting the member variable liveTimer to a new string, but that string was auto-released, and as soon as your - (void)targetMethod: (NSTimer *)theTimer returned, control returned to the system's run-loop code, and that string was getting released. That meant that liveTimer pointed to some random spot in memory that at one point had contained your string object, but now contained something else--something undefined. When you called a method on that random spot in memory it, it crashed.

(To confuse things further, [NSTimer scheduledTimerWithTimeInterval:target:selector:] is a special case. The timer retains itself until it fires, which is why that part of your code works. Nobody really knows why NSTimer gets an exception; it's confusing as hell. It's left over from the NExT days--code written in the early 90s when it seemed like a good idea to someone.)

So, do you want to use self.liveTimer or self.timer, or self.whatever everywhere? More or less. You normally want to retain the new object and release the old. The only One of the many exceptions is that if you "create" an object with [[Object alloc] init], that object comes to you with a retain count of 1 already, in that case, using the self. syntax will retain the object a second time, which is probably not what you want. (Although, if you've set self.liveTimer = before and you then use liveTimer = later in your code, the first string will be leaked, because nothing ever released it. It's not always trivial, deciding which way to do it.)

Hope that clears things up a little. This dotted notation stuff in objective-c is the least intuitive part of the language, I think. Be sure to read apple's document on memory management:

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

这篇关于如何在NSTimer循环期间刷新TableView单元数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆