Что работает для передачи касаний (и отображения всех) подпредставлений? - PullRequest
7 голосов
/ 06 декабря 2009

У меня есть следующая цепочка подпредставлений:

UIViewController.view -+
                       |-> UIView (subclass) -+
                       |                      +-> UIToolbar 
                       |
                       +------------------------> UIWebView

В подклассе я переопределяю его метод -touchesEnded:forEvent:, чтобы скрыть и показать UIToolbar одним касанием, через CAAnimation, а также выдать NSNotification, который заставляет контроллер представления скрыть свою панель навигации.

Если я не добавлю UIWebView как подпредставление к представлению контроллера представления, то это работает правильно.

Если я затем добавлю UIWebView как подпредставление представления контроллера представления, то UIToolbar не появится, и я не получу эффект анимации. UIWebView реагирует на прикосновения, а подкласс UIView - нет. Однако уведомление действительно срабатывает, а панель навигации скрывается.

Как лучше организовать эти подпредставления так, чтобы:

  1. UIToolbar можно заставить скользить по экрану
  2. Видимый UIWebView по-прежнему может принимать обычные сенсорные события: увеличение / уменьшение и двойное касание для сброса уровня увеличения

Правильный ответ будет соответствовать обоим критериям.


EDIT

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

Я установил свойство view контроллера представления в подкласс UIView и вставил веб-представление в верхнюю часть массива -subviews с помощью [self.view insertSubview:self.webView atIndex:0].

Я добавил следующий метод в подкласс UIView:

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

    if (event.type == UIEventTypeTouches) {
        [self toggleToolbarView:self];

    // get touches
    NSSet *touches = [event allTouches];

    NSLog(@"subview: %@", subview);
    NSLog(@"touches: %@", touches);

    // individual touches
    for (UITouch *touch in touches) {
        switch (touch.phase) {
            case UITouchPhaseBegan:
                NSLog(@"UITouchPhaseBegan");
                break;
            case UITouchPhaseMoved:
                NSLog(@"UITouchPhaseMoved");
                break;
            case UITouchPhaseCancelled:
                NSLog(@"UITouchPhaseCancelled");
                break;
            case UITouchPhaseStationary:
                NSLog(@"UITouchPhaseStationary");
                break;      
            default:
                NSLog(@"other phase...");
                break;
            }
        }
    }

    return subview;
}

Метод -toggleToolbarView: вызывает NSTimer -тимированный CAAnimation, который скрывает / показывает UIToolbar*.

Проблема заключается в том, что комбинация перетаскивания приводит к срабатыванию -toggleToolbarView: (а также к увеличению или уменьшению веб-представления). Я хотел бы, чтобы -toggleToolbarView: запускался только при событии только для прикосновения.

Когда я вызываю вышеуказанный метод -hitTest:withEvent:, похоже, что UIWebView высасывает прикосновения. Два оператора NSLog показывают, что UIView*, который возвращается от родительского UIView* s -hitTest:withEvent:, является либо UIWebView, либо UIToolbar (когда он находится на экране и прикасается). Массив touches пуст, и поэтому типы событий касания не могут быть распознаны.

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


РЕДАКТИРОВАТЬ II

Я добился некоторого прогресса в том, чтобы заставить UIWebView сжимать / масштабировать и реагировать на двойное касание. Я также могу скрыть / показать панель инструментов с одним нажатием.

Вместо того чтобы связываться с приватным UIWebView экземпляром UIScroller, я получаю доступ к его внутреннему приватному UIWebDocumentView. Но веб-представление в целом не перерисовывается должным образом после увеличения.

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

В целях документирования этого я буду:

  1. Позвоните UIView (subclass) вместо ViewerOverlayView

  2. UIWebView называется ViewerWebView

Оба являются подклассами UIView и UIWebView, соответственно.

My ViewerWebView содержит новый ivar (с @property + @synthesized):

UIView *privateWebDocumentView;

(Этот UIView на самом деле является указателем на экземпляр UIWebDocumentView, но во избежание того, чтобы Apple пометила приложение отказом, я привел его к UIView для простой передачи его событиям касания.)

Реализация ViewerWebView переопределений -hitTest:withEvent:

- (UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *_subview = [super hitTest:point withEvent:event];
    self.privateWebDocumentView = _subview;
    return _subview;
}

My ViewerOverlayView содержит новые ивары (с @property + @synthesized):

ViewerWebView *webView;
UIView *webDocumentView;

В реализации наложения я переопределяю -touchesBegan:withEvent:, -touchesMoved:withEvent: и -touchesEnded:withEvent:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.touchHasMoved = NO;
    if (self.webView) {
        UITouch *touch = [touches anyObject];
        CGPoint touchPoint = [touch locationInView:self];
        self.webDocumentView = [self.webView hitTest:touchPoint withEvent:event];
    }
    [super touchesBegan:touches withEvent:event];
    [self.webDocumentView touchesBegan:touches withEvent:event];
}

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    self.touchHasMoved = YES;
    if (self.webView) {
        UITouch *touch = [touches anyObject];
        CGPoint touchPoint = [touch locationInView:self];
        self.webDocumentView = [self.webView hitTest:touchPoint withEvent:event];
    }
    [super touchesMoved:touches withEvent:event];
    [self.webDocumentView touchesMoved:touches withEvent:event];
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if(!self.touchHasMoved) 
        [self toggleToolbarView:self];
    if (self.webView) {
        UITouch *touch = [touches anyObject];
        CGPoint touchPoint = [touch locationInView:self];
        self.webDocumentView = [self.webView hitTest:touchPoint withEvent:event];
    }
    [super touchesEnded:touches withEvent:event];
    [self.webDocumentView touchesEnded:touches withEvent:event];
}

В контроллере вида я делаю следующее:

  1. Создание экземпляра ViewerOverlayView и добавление его в качестве подпредставления свойства view контроллера представления

  2. Создание экземпляра ViewerWebView контроллера представления и вставка его в конец массива подпредставлений

  3. Установите свойство ViewerOverlayView веб-представления для веб-представления контроллера представления

Итак, теперь мой прозрачный ViewerOverlayView правильно передает события касания увеличения / растяжения / двойного касания в частный экземпляр UIWebDocumentView экземпляра ViewerWebView. UIWebDocumentView затем изменяет размер.

Однако содержимое не перерисовывается с новым масштабом. Поэтому при масштабировании векторный контент (например, PDF, SVG) выглядит размытым.

Вот как выглядит изображение без масштабирования, красивое и резкое:

UIWebView, no scaling

Here's what the view looks like when zoomed in, which is not as sharp as it would otherwise be without all this custom event munging:

UIWebView, blurry zoom

Interestingly, I can only rescale between two zoom levels. Once I stop double-tap-stretching to zoom in at any level, it then "snaps back" to the frame you see in the image above.

I tried -setNeedsDisplay on the ViewerWebView and its inner UIWebDocumentView instance. Neither method call worked.

Despite wanting to avoid private methods, because I want to eventually get this app approved, I also tried это предложение о доступе к приватному UIWebDocumentView методу -_webCoreNeedsDisplay:

[(UIWebDocumentView *)self.webDocumentView _webCoreNeedsDisplay];

Этот метод вызвал аварийное завершение работы приложения из unrecognized selector исключения. Возможно, Apple изменила название этого метода с SDK 3.0 на 3.1.2.

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

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

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


РЕДАКТИРОВАТЬ III

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

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

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

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

Ответы [ 4 ]

2 голосов
/ 09 декабря 2009

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

Если бы я пытался это сделать, я бы поместил UIToolbar непосредственно как подпредставление UIViewController.view. UIWebView также остается прямым подпредставлением.

UIViewController.view должен быть подклассом UIView, и ему необходимо переопределить

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

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

Примером может быть:

- (UIView *)hitTest:(CGPoint) point withEvent:(UIEvent *)event {
    UIView* subview = [super hitTest:point withEvent:event];
    /* Use the event.type, subview to decide what actions to perform */
    // Optionally nominate a default event recipient if your view is not completely covered by subviews
    if (subview == self) return self.webViewOutlet;
    return subview;
}
1 голос
/ 16 декабря 2009

Взять 2: то же, что и выше, но переопределить методы следующим образом:

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    hasMoved = NO;
    [[[webView subviews] objectAtIndex:0] touchesBegan:touches withEvent:event];
}

-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    hasMoved = YES;
    [[[webView subviews] objectAtIndex:0] touchesMoved:touches withEvent:event];
}

-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if(!hasMoved) [self toggleToolbar];
    [[[webView subviews] objectAtIndex:0] touchesEnded:touches withEvent:event];
}

-(void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [[[webView subviews] objectAtIndex:0] touchesCancelled:touches withEvent:event];
}

Это работает ... вроде. Это позволяет перетаскивать веб-представление вокруг. Но он не обрабатывает другие сенсорные события должным образом (переходя по ссылкам, зажимая, что угодно). Так. Вероятно, не очень полезно, но это был прогресс для меня!

1 голос
/ 11 декабря 2009

Хорошо, что по этому поводу:

-В своем пользовательском подклассе UIView добавьте указатель на UIWebView

-Добавить ivar в подкласс UIView, BOOL hasMoved;

-В подклассе UIView переопределите методы касания следующим образом:

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    hasMoved = NO;
    [webView touchesBegan:touches withEvent:event];
}

-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    hasMoved = YES;
    [webView touchesMoved:touches withEvent:event];
}

-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if(!hasMoved) [self hideToolbar];
    [webView touchesEnded:touches withEvent:event];
}

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

1 голос
/ 09 декабря 2009

Если вы начнете с добавления подкласса UIView в представление контроллера представления, а затем добавите UIWebView, как вы описываете, UIWebView будет полностью покрывать подкласс UIView (при условии, что оба представления полностью перекрываются, как вы упоминаете в комментариях к вопросу) .

Звучит так, будто вы этого не хотите - вам нужен подкласс над представлением в Интернете. Вам нужно либо добавить их в представление контроллера представления в обратном порядке (добавить веб-представление, затем добавить пользовательский UIView), либо вызвать:

[viewContoller.view insertSubview:webView belowSubview:subclass];

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

Идея состоит в том, что вы создаете подкласс UIApplication и переопределяете метод -sendEvent :. Ваш будет выглядеть примерно так, я полагаю:

- (void)sendEvent:(UIEvent *)event {
    [super sendEvent:event];

    NSSet *allTouches = [event allTouches];
    if ([allTouches count] > 0) {
        UITouch *touch = [allTouches anyObject];
        if ([allTouches count] == 1 && touch.phase == UITouchPhaseEnded && touch.tapCount == 1) {
            // Do something here
        }
    }
}

Я не ожидаю, что это даст вам 100%, но, надеюсь, это начало. В частности, вы можете поиграть с условием if, которое проверяет свойства UITouches, с которыми вы имеете дело. Я не уверен, что я включаю правильные проверки или они поймают точное условие, которое вы ищете.

Удачи!

...