UIScrollview с UIButtons - как воссоздать трамплин?

1136 Я все еще подозреваю, что заданный вопрос - это не тот вопрос, который был задуман, но мне пришло в голову, что суть моего ответа, скорее всего, не изменится. Если вопрос обновляется, я всегда могу отредактировать этот ответ, чтобы он соответствовал (или удалял его, если он оказался неприменимым).

Отмена приоритетов оптимизации

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

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

Для задаваемого вопроса это означает, что основной проблемой должны быть не вычислительные затраты, а инкапсуляция. Почему вызывающему func() нужно выделять место для func() для работы? Это не должно происходить, если только профилировщик не определит это как узкое место в производительности. И если бы это сделал профилировщик, было бы намного проще (и надежнее!) Спросить профилировщика, помогает ли это изменение, чем запросить переполнение стека.

Почему?

Я могу придумать две основные причины для расстановки приоритетов оптимизации. Во-первых, «тест на нюх» ненадежен. Хотя может быть несколько человек, которые могут определить узкие места, глядя на код, многие, многие просто думают, что могут. Во-вторых, поэтому у нас есть оптимизирующие компиляторы. Это не неслыханно для кого-то, чтобы придумать этот сверхумный трюк оптимизации только, чтобы обнаружить, что компилятор уже делал это. Держите ваш код в чистоте, и пусть компилятор обрабатывает рутинные оптимизации. Только шаг, когда задача явно превышает возможности компилятора.

См. Также:

Выбор оптимизации

Хорошо, предположим, профилировщик идентифицировал конструкцию этого небольшого, 10- Элемент массива как узкое место. Следующий шаг - проверить альтернативу, верно? Почти. Во-первых, вам нужна альтернатива, и я считаю, что обзор теоретических преимуществ различных альтернатив будет полезен. Просто имейте в виду, что это теоретически, и что профайлер получает последнее слово. Поэтому я расскажу о плюсах и минусах альтернатив из этого вопроса, а также о некоторых других альтернативах, которые могут быть рассмотрены. Давайте начнем с худших вариантов, прокладывая себе путь к лучшим.

Пример A

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

Легко самый дорогой в вычислительном отношении вариант. Не делай этого.

Пример B

В примере B вектор создается для каждой итерации внешнего цикла while, который затем доступен по ссылке из func(). Минусы производительности здесь включают передачу параметра в func() и наличие func() доступа к вектору косвенно через ссылку. Там нет плюсов производительности, поскольку это делает все, что будет делать базовый уровень (см. Ниже) , плюс некоторые дополнительные шаги.

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

Базовая линия

Базовая линия, которую я использую, является исправлением бесконечного цикла примера А. В частности, замените «my_vec.push_back(i);» примера В на «my_vec[i] = i;». Этот простой подход соответствует тому, что я ожидал от первоначальной оценки профилировщика. Если вы не можете победить простое, придерживайтесь этого.

Пример B *

В тексте вопроса представлена ​​неточная оценка примера B. Интересно, что оценка описывает подход, который потенциально может улучшить исходные данные. Чтобы получить код, который соответствует текстовому описанию, переместите «1111» примера B в строку непосредственно перед оператором while. Это приводит к созданию вектора только один раз, а не к каждой итерации.

Минусы этого подхода те же, что и в Примере B, который изначально был закодирован. Тем не менее, теперь мы получаем усиление в том, что конструктор вектора вызывается только один раз. Если строительство обходится дороже, чем затраты на косвенное обращение, результатом должно стать чистое улучшение, если цикл while повторяется достаточно часто. (Остерегайтесь этих условий: это значительное «если», и нет никакого априорного предположения о том, сколько итераций «достаточно».) Было бы разумно попробовать это и посмотреть, что говорит профилировщик.

Получить статический

Вариант в Примере B *, который помогает сохранить инкапсуляцию, заключается в использовании базовой линии (фиксированный Пример A), но перед объявлением вектора ключевым словом static. Это дает преимущество построения вектора только один раз, но без дополнительных затрат, связанных с созданием вектора в качестве параметра. Фактически, преимущество может быть больше, чем в примере B *, поскольку построение происходит только один раз за выполнение программы, а не каждый раз, когда запускается цикл while. Чем больше раз запускается цикл while, тем больше это преимущество.

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

Исправьте размер

Мой личный выбор для какой оптимизации здесь попробовать начать с базовой линии и переключить вектор на std::array<10,double>. Моя основная мотивация заключается в том, что необходимый размер не будет больше 10. Также важно, что построение double тривиально. Построение массива должно быть на одном уровне с объявлением 10 переменных типа double, что, как я ожидаю, будет незначительным. Так что нет необходимости в хитроумных трюках по оптимизации. Просто дайте компилятору сделать свое дело.

Ожидаемое возможное преимущество этого подхода состоит в том, что vector выделяет пространство в куче для его хранения, что имеет накладные расходы. Местный array не будет иметь этой стоимости. Однако это только возможная выгода. Векторная реализация может уже использовать это соображение производительности для небольших векторов. (Возможно, он не использует кучу, пока емкость не должна превысить какое-то магическое число, возможно, больше 10.) Я бы сослался на вас раньше, когда упомянул «супер-умный» и «компилятор уже делал это». [1153 ] 1154 Я бы запустил это через профилировщик. Если нет никакой пользы, скорее всего, нет никакой пользы от других подходов. Дайте им попытку, конечно, поскольку они достаточно просты, но, вероятно, будет лучше использовать ваше время, чтобы взглянуть на другие аспекты для оптимизации.

34
задан GingerHead 8 May 2014 в 23:38
поделиться

7 ответов

Для UIScrollView для определения различия между щелчком, который передает до его представления (представлений) содержания и касания, которое превращается в сильный удар или повышение это должно задержать касание и видеть, переместился ли палец во время той задержки. Установкой delaysContentTouches к NO в Вашем выше примера, Вы предотвращаете это. Поэтому представление прокрутки всегда передает касание кнопке, вместо того, чтобы отменить его, когда оказывается, что пользователь выполняет жест сильного удара. Попробуйте установку delaysContentTouches к YES.

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

28
ответ дан Lorenzo B 27 November 2019 в 16:25
поделиться

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

1
ответ дан Rog 27 November 2019 в 16:25
поделиться

Другой способ:
1. Замените кнопку простым пользовательским UIView
2. Ставим флаг «userInterationEnable = yes;» в методе инициализации
3. В представлении переопределения метода UIResponder "touchesEnded" здесь вы можете запустить необходимое действие, например кнопку.

0
ответ дан 27 November 2019 в 16:25
поделиться

По моему опыту, первый ответ, то есть простая установка для delaysContentTouches значения ДА , ничего не меняет в отношении проблемы. Кнопки по-прежнему не доставляют результаты отслеживания в режим прокрутки. Третий ответ прост и очень удобен. Спасибо, sieroaoj!

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

  1. Заменить кнопку простым пользовательским UIView
  2. Поставить флаг «userInterationEnable = yes;» в методе init
  3. В представлении переопределите метод UIResponder "touchesEnded" здесь Вы можете вызвать действие

Четвертое. установите delaysContentTouches на ДА

0
ответ дан 27 November 2019 в 16:25
поделиться

Хорошо, вот ваш ответ:

Подкласс UIButton. (ПРИМЕЧАНИЕ: вызовите [super ....] в начале каждого переопределения.

  • Добавьте свойство. Одно из типа BOOL (называется enableToRestore)
  • Добавьте свойство.Один из типов CGPoint (называемый startTouchPosition)
  • в awakeFromNib и initWithFrame, установите enableToRestore в свойство isEnabled )
  • Override " touchesBegan: withEvent: " для сохранения позиции начала касания .
  • Переопределите "touchesMoved: withEvent:", чтобы проверить, есть ли горизонтальное движение.
  • Если ДА, установите для параметра «Включено» значение «НЕТ», а для выберите значение «НЕТ».

Пример кода:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{
    UITouch *touch = [touches anyObject];

    [super touchesBegan:touches withEvent:event];
    [self setStartTouchPosition:[touch locationInView:self]];
}


//
// Helper Function
//
- (BOOL)isTouchMovingHorizontally:(UITouch *)touch 
{
    CGPoint currentTouchPosition = [touch locationInView:self];
    BOOL      rValue = NO;

    if (fabsf([self startTouchPosition].x - currentTouchPosition.x) >= 2.0) 
    {
        rValue = YES;
    }

    return (rValue);
}

//
// This is called when the finger is moved.  If the result is a left or right
// movement, the button will disable resulting in the UIScrollView being the
// next responder.  The parrent ScrollView will then re-enable the button
// when the finger event is ended of cancelled.
//
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
{
    [super touchesMoved:touches withEvent:event];
    if ([self isTouchMovingHorizontally:[touches anyObject]]) 
    {
        [self setEnabled:NO];
        [self setSelected:NO];
    } 
}

Это активирует UIScrollView.

Подкласс UIScrollView. (ПРИМЕЧАНИЕ: вызовите [super ....] в начале каждого переопределения.

  • Переопределите как «touchesEnded: withEvent:», так и «touchesCancelled: withEvent:»
  • В переопределении сбросьте все подвиды (и их подвиды) ) включен флаг.
  • ПРИМЕЧАНИЕ: используйте категорию и добавьте метод в UIView:

.

- (void) restoreAllEnables
{
    NSArray   *views = [self subviews];

    for (UIView *aView in views)
    {
        if ([aView respondsToSelector:@selector(restoreEnable)])
        {
            [aView restoreEnable];
        }
    }
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    [self restoreAllEnables];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    [self restoreAllEnables];
}
  • в категории:

.

-(void) restoreEnable
{
    NSArray   *views = [self subviews];

    if ([self respondsToSelector:@selector(enableToRestore)])
    {
        [self setEnabled:[self enableToRestore]];
    }

    for (UIView *aView in views)
    {
        if ([aView respondsToSelector:@selector(restoreEnable)])
        {
            [aView restoreEnable];
        }
    }
}

EDIT Примечание: я так и не получил ответа 3 для работы. Аналогично: setDelaysContentTouches: NO (установленный в контроллере представления или где-то еще) должен быть установлен для достижения наилучших результатов в ответе 4. Это обеспечивает очень быстрый отклик на кнопки. влияет (150 мс) на время отклика на кнопки и делает легкое и быстрое нажатие невозможным.

1
ответ дан 27 November 2019 в 16:25
поделиться

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


@interface MyPhotoButton : UIButton {
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
@end

@implementation MyPhotoButton

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesMoved:touches withEvent:event];
    [self setEnabled:NO];
    [self setSelected:NO];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    [self setEnabled:YES];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    [self setEnabled:YES];
}

@end
5
ответ дан 27 November 2019 в 16:25
поделиться

Решение, которое сработало для меня, включало:

  1. Настройка canCancelContentTouches в UIScrollView на ДА .
  2. Расширение UIScrollView для переопределения touchShouldCancelInContentView: (UIView *) представление для возврата ДА , когда представление является UIButton .

Согласно документации touchShouldCancelInContentView возвращает « ДА , чтобы отменить дальнейшие сенсорные сообщения для просмотра, НЕТ , чтобы просмотр продолжал получать эти сообщения. Возвращаемое значение по умолчанию равно ДА , если представление не является объектом UIControl ; в противном случае оно возвращает НЕТ . "

Поскольку UIButton является ] UIControl расширение необходимо для того, чтобы canCancelContentTouches вступил в силу, разрешающую прокрутку.

44
ответ дан 27 November 2019 в 16:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: