Pull to refresh

Comments 28

Наблюдал ещё одну подобную интересную ситуацию: если во время анимации перехода на новый экран push-нуть ещё один новый экран… Симулятор просто выдаст ошибку в лог, мол, не могу. И всё. Никаких крэшей, падений… никаких проблем. Просто перешел на экран, на который переходил изначально, а не на следующий. Получалось это, когда переход помещал в ViewDidLoad. Решение — вынести код в например, ViewDidAppear.
А что если перед пушем отключать userInteraction на текущем скрине?
Есть у вас готовый тестовый проект, на котором можно добиться краша?
Мы одумали про userInteraction, но в случае двойного нажатия это не спасает.
Готового проекта нет, но его совсем не трудно сделать самостоятельно. Первый пример кода показывает как это сделать.
Тем временем мне в скайп постучался товарищ ASkvortsov:
Привет, хочу поделиться по поводу статьи с хабра

у меня просто r/o акк, поэтому не могу откомментить

developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instp/UIView/exclusiveTouch — то, что нужно использовать

нужно проставить его в YES обоим кнопкам


И этот метод работает.
Видимо нам нужно учить матчасть :)
Любопытная штука. Спасибо. Плюсую. Правда, не соглашусь с тем, что ситуация «самостоятельно скажи каждому контролу, что его нельзя нажимать параллельно с остальными» — это не корявость системы и костыль, а «важная фича, которую нужно откопать в документации и запомнить».
Если выбирать из двух решений (кастомный навигейшн или метод контрола), то я за стандартный ;)
А я — за кастомный. Потому что его надо один раз сделать — и он становится твоим стандартом, а про «стандартный» надо помнить всякий раз, когда в проект добавляется новая кнопка.
Ваше право. Я предпочитаю стандартный, потому что его проще поддерживать. Да и предложенный вариант выглядит костыльно, потому как вряд ли пользователь желает открыть два контроллера сразу.
Не то чтобы он выглядит костыльно, скорее странно. Я бы лучше сделал не очередь переходов, а запрет перехода во время перехода — потому что пользователь и правда два контроллера сразу не открывает, это — ситуация-ошибка с неопределенным ожидаемым поведением.
Сделайте себе кастомную кнопку, это намного проще и лучше кастомного навигейшен контроллера.
Проблема множественных нажатий не ограничивается навигейшен контроллером как в статье. Серчбар + пуш, поповер + пуш, модальное окно + поповер, поповер + поп, и т.д. Все сильно зависит от дизайна и архитектуры приложения, во многих случаях, такие действия к крешу не приведут, но могут вылезти различные UI баги. Поэтому, все кнопки, ячейки, барбаттонитемы которые как-то меняют не данные, а представления на экране, должны иметь exclusiveTouch, это самая простая и железная защита от багов такого рода.
mshershnev Предлагаю добавить это решение в конец поста :)
Вы серьёзно считаете, что из-за button.exclusiveTouch = NO стоит писать целую статью? Это всё тянулось как минимум с iOS 6, где можно было развлекаться, роняя системные приложения подобным образом. Другой вопрос, почему Apple решили, что UButton должен по умолчанию иметь .exclusiveTouch = NO, и почему не исправляли это так долго.

Ну и вы лукавите, что «В сети описание этой ошибки встречается очень редко, а решение было найдено всего одно, и оно не работает»: раз, два (аж 2012 год).
При чем здесь UIButton? Запустить пуши можно кучей способов, а маскировать таким образом ошибку в UINavigationController не лучшая практика.
При том, что если вы стремитесь выстрелить себе в ногу, пуша два контроллера одновременно и анимировано, это полностью ваша проблема, а не «ошибка» UINavigationController. Если нужно запушить в стек два и более контроллера, правильнее сделать так:

UIViewController* firstViewController = [[UIViewController alloc] init];
UIViewController* secondViewController = [[UIViewController alloc] init];
UIViewController* thirdViewController = [[UIViewController alloc] init];

[self.navigationController pushViewController:firstViewController animated:NO];
[self.navigationController pushViewController:secondViewController animated:NO];
[self.navigationController pushViewController:thirdViewController animated:YES];

[firstViewController release];
[secondViewController release];
[thirdViewController release];

Хотя я не могу представить сценария, где надо пушить контроллеры одновременно: пуш должен быть инициирован пользователем, и сразу после пуша пользователь должен иметь возможность вернуться назад, совершив ровно одно действие. Если же действие пользователя инициирует двойной, тройной и т.д. пуш, пользователь слегка удивится тому, что кнопка «назад» возвращает его не на тот контроллер, из которого он сюда попал.
Ну не все используют эту технологию. :)
Вам плюс за попытку улучшить мир. Однако практическая польза от этого, к сожалению, никакая. Написание своей обертки для UINavigationController ради такой экзотической ошибки неоправданно. Я имею ввиду, что небольшая вероятность внести собственную (другую) ошибку в свой код оказывается выше (на мой субъективный взгляд) микроскопической вероятности, что пользователь наткнется на описанную проблему.
«Непонятно, почему Apple не считает это проблемой и не занимается её решением.» — думаю примерно по той же причине. К тому же ошибок такого низкого класса я думаю в iOS не одна и не две. Просто это та, которую вы заметили. В баг-трекере разработчика любого сколько-нибудь серьезного софта копятся такие ошибки. И если они не приводят к уязвимостям в системе их очень редко исправляют.
Первое, что я говорю нашим тестировщикам, это тестить такие вот кейсы с множественными нажатиями. Тот мегакостыль, который вы тут описали просто взорвал мне мозг. И то, он полностью не решает проблему двойных нажатий, только внутри навигейшен контроллера. А если у вас две кнопки фильтруют/меняют датасорс таблички, одновременное их нажатие, скорее всего, убъет контроллер. И для этого, у UIView есть пропертя exclusiveTouch, плохо, что она по дефолту не YES. И ее нельзя поставить глобально через UIAppearance, например.

В общем, горе от ума. Плохо, что большинство iOS разработчиков не читают документацию, а предпочитают StackOverflow Driven Development.
чаще всего, если ответ есть в документации, то на SO просто дают ссылку :)
Где-нибудь пятым ответом, а на первом месте почти всегда заплюсованный говнокод, который продолжают плюсовать по инерции.
Я предпочитаю проверять перед вызовом pushViewController а не равен ли topViewController текущему, и если вдруг нет то и пушить не надо.
Ровно такая же проблема существует при использовании сторибордов. Если есть сегвей на новую сцену и в этой сцене что-то внезапно долго будет выполняться (конструктор, didload, ватевер) и тут же вызвать другой сегвей — будет ровно такая же история.
Но тут, к сожалению, своим NavigationController не обойдёшься, поэтому тут рекомендация одна — не выполнять потенциально длинный код в основной очереди
Кроме описанного коллегами до меня, у вас еще и тут опасненькая ситуация:
@property (nonatomic, strong) NSMutableArray *tasks;
           
void (^task)(void) = ^{
       [self pushViewController:viewController animated:animated];
};
            
[self.tasks addObject:task];


Пока не будут завершены все ваши анимации, навконтроллер не умрет, что не очень-то хорошо. Такой себе локальный ретейн луп.
Sign up to leave a comment.