UIDatePicker внутри UIScrollView со страницами - PullRequest
4 голосов
/ 01 июня 2009

У меня есть UIScrollView с 2 страницами, и я могу прокручивать их по горизонтали. Однако на одной из моих страниц у меня есть UIDatePicker, и представление прокрутки перехватывает события вертикального касания, поэтому я больше не могу манипулировать средством выбора даты (за исключением щелчка или нажатия). Есть ли какой-нибудь способ сказать ScrollView отправлять вертикальные события касания в средство выбора даты, но отправлять горизонтальные события касания в представление прокрутки для переключения страниц?

Ответы [ 3 ]

6 голосов
/ 21 апреля 2010

На самом деле, есть гораздо более простая реализация, чем предлагал Боб. Это прекрасно работает для меня. Вам нужно будет создать подкласс вашего UIScrollview, если вы этого еще не сделали, и включить этот метод: -

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView* result = [super hitTest:point withEvent:event];

    if ([result.superview isKindOfClass:[UIPickerView class]])
    {
         self.canCancelContentTouches = NO;  
         self.delaysContentTouches = NO;
    }
    else 
    {
         self.canCancelContentTouches = YES; // (or restore bool from prev value if needed)
         self.delaysContentTouches = YES;    // (same as above)
    }
    return result;
}

Причина, по которой я использую result.superview, заключается в том, что представление, которое получает прикосновения, на самом деле будет UIPickerTable, который является частным API.

Приветствия

5 голосов
/ 02 июля 2009

Я думаю, что есть две части этой проблемы. Первый - определение намерения пользователя, а второй - получение правильного элемента управления для ответа на это намерение.

Определение намерения

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

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

Осуществление

Чтобы реализовать это, вам нужно обработать события до того, как это сделает UIScrollView или средство выбора даты. Вероятно, есть несколько способов сделать это, но на ум приходит один: создать пользовательский UIView с именем ScrollingDateMediatorView. Установите UIScrollView как дочерний элемент этого представления. Переопределите методы hitTest: withEvent: и pointInside: withEvent: в ScrollingDateMediatorView. Эти методы должны выполнять тот же тип тестирования попаданий, который обычно происходит, но если результатом является выбор даты, вместо этого верните self. Это эффективно перехватывает любые события касания, предназначенные для средства выбора даты, что позволяет ScrollingDateMediatorView обрабатывать их в первую очередь. Затем вы реализуете алгоритм, описанный выше, различными способами. В частности:

В методе touchesBegan: withEvent сохраните начальную позицию.

В touchesMoved: withEvent, если намерение пользователя еще не известно, определите, достаточно ли он коснулся исходной позиции. Если это так, определите, собирается ли пользователь прокрутить или изменить дату, и сохраните это намерение. Если намерение пользователя уже известно и оно должно изменить дату, отправьте средство выбора даты сообщению touchedMoved: withEvent, в противном случае отправьте UIScrollView сообщение touchesMoved: withEvent. Вам нужно будет выполнить некоторую аналогичную работу в рамках touchesEnded: withEvent и touchesCancelled: withEvent, чтобы другие представления получили соответствующие сообщения. Оба эти метода должны сбросить сохраненные значения.

После того, как вы правильно распространили события, вам, вероятно, придется попробовать пользовательское тестирование, чтобы настроить порог движения.

1 голос
/ 21 августа 2011

Великолепная помощь, Сэм! Я использовал это, чтобы создать простую категорию, которая использует метод (поскольку я делал это в UITableViewController и, следовательно, должен был сделать некоторые действительно грязные вещи, чтобы создать подкласс прокрутки).

#import <UIKit/UIKit.h>

@interface UIScrollView (withControls)

+ (void) swizzle;

@end

И основной код:

#import </usr/include/objc/objc-class.h>
#import "UIScrollView+withControls.h"

#define kUIViewBackgroundImageTag 6183746
static BOOL swizzled = NO;

@implementation UIScrollView (withControls)

+ (void)swizzleSelector:(SEL)orig ofClass:(Class)c withSelector:(SEL)new;
{
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);

    if (class_addMethod(c, orig, method_getImplementation(newMethod),
                        method_getTypeEncoding(newMethod))) {
        class_replaceMethod(c, new, method_getImplementation(origMethod),
                            method_getTypeEncoding(origMethod));
    } else {
        method_exchangeImplementations(origMethod, newMethod);
    }
}

+ (void) swizzle {
    @synchronized(self) {
        if (!swizzled) {

            [UIScrollView swizzleSelector:@selector(hitTest:withEvent:)
                                  ofClass:[UIScrollView class]
                             withSelector:@selector(swizzledHitTest:withEvent:)];
            swizzled = YES;
        }
    }
}

- (UIView*)swizzledHitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView* result = [self swizzledHitTest:point withEvent:event]; // actually calling the original hitTest method

    if ([result.superview isKindOfClass:[UIPickerView class]]) {
        self.canCancelContentTouches = NO;  
        self.delaysContentTouches = NO;
    } else {
        self.canCancelContentTouches = YES; // (or restore bool from prev value if needed)
        self.delaysContentTouches = YES;    // (same as above)
    }
    return result;
}

@end

Затем в моем методе viewDidLoad я только что вызвал

[UIScrollView swizzle];
...