Как обновить данные ячейки TableView во время цикла NSTimer - PullRequest
2 голосов
/ 25 марта 2011

Отказ от ответственности: я недавно перешел на iOS, поэтому, пожалуйста, не стесняйтесь говорить мне, что весь мой подход неверен.Я только прошу вас объяснить, почему - моя цель здесь состоит в том, чтобы учиться (и, конечно, также решить эту проблему).

...

Привет всем, я новичокв программировании на iOS, и после нескольких часов попыток решить эту проблему, мое невежество и неопытность по поводу какао одолевают меня.Надеюсь, я смогу найти некоторую помощь здесь.

У меня есть простое приложение TableView, над которым я работаю как песочница для более крупного приложения, которое находится в разработке.Функциональность, которую я пытаюсь реализовать прямо сейчас, - это псевдо-обновление в реальном времени текста субтитров в ячейке TableView (т.е. cell.detailTextLabel.text).

Чтобы дать вам лучшее представление о цели:Я пытаюсь сделать так, чтобы пользователь мог коснуться ячейки TableView, и таймер стиля «секундомер» начинает считать в тексте субтитров.

Но я не могу понять, как обновить cell.detailTextLabel.текст А) Изнутри моих методов таймера (он говорит, что tableView undeclared) и B) таким образом, что ячейка постоянно обновляется, так что пользователь видит «активный» таймер.

Итак, вот некоторый код (всеэто прямо сейчас в RootViewController. В конце концов я захочу создать отдельные классы для таких функций, как таймер, но сначала я просто хочу, чтобы все заработало).

Я должен также добавить, что у меня многоглобальных ("interface?") переменных в этом классе прямо сейчас.Это облегчает эксперименты, но если это может вызвать некоторые проблемы, пожалуйста, дайте мне знать.Я все еще учусь передавать объекты / переменные / и т. Д. Туда-сюда.

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

Математика и вычислениясам.В результате получается одна строка «liveTimer» в формате MM: SS.hh, которая точно «секундомерами» вверх:

- (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 для подробного текстового значения следующим образом:

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

По сути, то, как у меня сейчас обстоят дела, когда пользователь нажимает элемент списка, таймер начинает считать в фоновом режиме (я подтвердил с помощью NSLog, чтоон работает правильно - если я извлекаю liveTimer в конце targetMethod, он правильно отформатирован и имеет счет вверх.

Но как мне отразить это через псевдо-обновление в реальном времени текста ячейки?

[tableView reloadData] продолжает сбой программы (без кода ошибки), когда я помещаю ее куда угодно, но я не знаю, как получить к ней доступ из targetMethod (это говоритКогда я добавляю [tableView reloadData]), я не могу объявить tableView.

Кроме того, [tableView reloadData] продолжает сбой программы (без сообщения об ошибке), даже когда я пытаюсь экспериментально добавить ее, скажем, в didSelectRowAtIndexPath.например,

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

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    self.runTimerFunctions;

    [tableView reloadData];

По сути, я уверен, что есть простой способ сделать то, что я пытаюсь сделать, но я все еще слишком плохо знаком с языком, чтобы знать, как это сделать.Я не удивлюсь, если я перегружу систему, пытаясь вызвать reloadData во время цикла таймера.Но опять же, может быть, это что-то еще.

Любой совет будет высоко ценится.Я пытаюсь выучить язык здесь, поэтому, хотя пример кода чрезвычайно полезен, я в равной степени заинтересован в «обучении ловле», поэтому я очень признателен за объяснения почему что яделать неправильно, и какие могут быть лучшие способы сделать это и т. д.

Заранее благодарим за всю вашу помощь;Я с нетерпением жду возможности принять участие в форумах.

1 Ответ

2 голосов
/ 25 марта 2011

Во-первых, небольшая ошибка ...

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 вызывает ваш метод синтезированного сеттера вместо того, чтобы просто устанавливать переменную напрямую, что заставляет ваш объект сохранять строку при ее установке (при условии, что вы настроили свойство live timer для сохранения, например, так: @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: вернет ноль, а currentCell.detailTextLabel.text = self.liveTimer; ничего не сделает (т.е. не ошибетсяили просто буквально ничего).

Редактировать:

Я только что заметил, что tableView не является одной из переменных вашего члена объекта ...

У вас есть UITableViewControllerторчать, что вы можете получить?Если это так, просто используйте tableViewControllerVariableName.tableView везде, где вы сейчас используете tableView.Является ли ваш RootViewController (в котором весь этот код) UITableViewController?Если это так, вызовите self.tableView везде, где вы в данный момент вызываете tableView.Если нет, то где находится ваш tableView / tableViewController?Размещение вашего файла заголовка может помочь прояснить ситуацию ...

Редактировать 2:

Проблема с liveTimer и self.liveTimer связана с подсчетом удержания наobject.

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, *Метод 1068 * вызывается вместе с объектом, старый объект, если таковой имеется, освобождается, а новый объект сохраняется.

[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 = позже в своем коде, первая строка будет пропущена, потому что ничто никогда не освобождает ее. Это не всегда тривиально, решая, какой способ сделать это.)

Надеюсь, это немного прояснит ситуацию. Думаю, эта пунктирная система обозначений в target-c является наименее интуитивной частью языка. Обязательно прочитайте документ Apple по управлению памятью:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...