Почему в Cocoa текстовое поле не будет отображаться до тех пор, пока IBAction не будет полностью выполнен?

У меня есть IBAction с простым кодом внутри:

-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    [self dolengthyaction];
}

'textfield' - это NSTextField в файле пера, а -'dolengthyaction' - это функция, выполнение которой занимает около минуты.

Мой вопрос: почему текстовое поле не отображается до тех пор, пока ПОСЛЕ выполнения «dolongyaction» не будет выполнено? Я хочу, чтобы это было раскрыто до того, как начнется долговременное действие. Это врожденная проблема или что-то не так с моим кодом? (или в другой части моего кода?)

Я все еще не очень хорош в программировании, поэтому прошу прощения, если я что-то плохо сформулировал и что-то неправильно отформатировал.

РЕДАКТИРОВАТЬ: кроме этого IBAction и -dolengthyaction больше ничего нет...

-(void)doLengthyAction {
    sleep(10);
}
-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    [self doLengthyAction];
    [textfield setHidden:YES];
}

Все, что я действительно хочу сделать, это отобразить метку, когда действие выполняется, и скрыть его, когда оно будет выполнено.

По сути, это означает, что сейчас он вообще не отображается.

На самом деле в -doLengthyAction это не sleep(10), а скорее операция NSFileManager, которая копирует около 50 Мб материала. Код был довольно длинным, но если вы хотите, чтобы я его опубликовал, я могу. Я тестировал его с помощью sleep(), но он тоже не работает.


person Vervious    schedule 04.05.2010    source источник
comment
Я не уверен, какой ответ выбрать в качестве решения, поскольку все они объясняют это...   -  person Vervious    schedule 04.05.2010
comment
Либо из ответов drawonward, либо wbyoung. Пример wbyoung тоже очень хорош, но он будет работать только на 10.6.   -  person JeremyP    schedule 04.05.2010


Ответы (6)


Все операции рисования (включая скрытие и отображение видов) запускаются из цикла выполнения. Цикл выполнения не может выполнить следующее событие до тех пор, пока ваша функция не вернется.

Если у вас есть операция, выполнение которой занимает больше секунды, вы должны выполнять ее в потоке. После его завершения используйте PerformSelectorOnMainThread, чтобы внести изменения в пользовательский интерфейс в основном потоке.

person drawnonward    schedule 04.05.2010
comment
Приведенный выше ответ немного вводит в заблуждение. Вы можете заставить рисование происходить вне цикла выполнения, просто вызвав [window display]. - person Leibowitzn; 11.05.2010
comment
@Liebowitzn Я думал об iPhone, который ограничивает вас пассивным рисованием. Хороший улов. - person drawnonward; 11.05.2010

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

Вы действительно должны обрабатывать вещи в фоновом потоке, если они собираются работать в течение длительного времени. Если вы этого не сделаете, пользовательский интерфейс будет играть в пляжный мяч, пока ваша операция выполняется.

Если вы нацелены на 10.6+, вы можете использовать GCD и блоки следующим образом, чтобы легко запускать вещи в фоновом режиме (и вам не нужно определять несколько методов для выполнения задач):

-(void)doLengthyAction {
    sleep(10);
}
-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self doLengthyAction];
        dispatch_async(dispatch_get_main_queue(), ^{
            [textfield setHidden:YES];
        });
    });
}

Конечно, используя это, вам нужно убедиться, что то, что происходит в длительном действии, является потокобезопасным, но в конечном итоге вы получите лучший пользовательский опыт. Для получения дополнительной информации об этом типе кода CGD вы можете захотеть прочитать это.

person wbyoung    schedule 04.05.2010

Я думаю, что что-то не так с остальной частью вашего кода. Этого не должно происходить.

Опубликовать больше?

person Chris Cooper    schedule 04.05.2010

Не могу сказать, что именно происходит, не глядя на остальную часть кода, но хакерским способом было бы попробовать:

-(IBAction)change:(id)sender {
   [textfield setHidden:NO];
   [self performSelector:@selector(doLenghtyAction) withObject:nil afterDelay:0.1]; --> or maybe even 0.0
 }
person Mihir Mathuria    schedule 04.05.2010
comment
Это хорошее предложение, которое я бы использовал в будущем, но я пытаюсь скрыть текстовое поле, как только выполняется длинная вещь. Я попробовал ваше предложение, и оно почти сразу же снова скрывает текстовое поле, если я вставлю [текстовое поле setHidden: YES] в конце. :) - person Vervious; 04.05.2010
comment
О верно. Плевать на мой комментарий. У Горация был аналогичный ответ, который решил проблему моего первого комментария. +1 однако :D - person Vervious; 04.05.2010

Попробуй это:

-(IBAction)change:(id)sender {
    [textfield setHidden:NO];
    [[textfield window] display]; // force the window to update
    [self doLengthyAction];
    [textfield setHidden:YES];
}
person Leibowitzn    schedule 04.05.2010

person    schedule
comment
+1 Хороший обходной путь, хотя было бы неплохо узнать, почему оригинал не работает. - person Vervious; 04.05.2010
comment
оригинал: setHidden › doLengthyAction › setHidden не разрешает обновление пользовательского интерфейса во временном интервале. состояние текстового поля действительно изменилось дважды. однако первое изменение не обновлялось на экране. когда у пользовательского интерфейса есть возможность обновить экран, текстовое поле уже находится во втором состоянии. - person ohho; 04.05.2010
comment
Я понимаю. Так что это не моя вина, это просто свойство Cocoa. Я понимаю. :Д спасибо! - person Vervious; 04.05.2010