Прокрутка двумя пальцами с помощью UIScrollView

У меня есть приложение, в котором мой основной вид принимает оба touchesBegan и касается touchesMoved , и поэтому берет в касание одним пальцем и тянет. Я хочу реализовать UIScrollView , и у меня он работает, но он переопределяет перетаскивания, поэтому мой контент-контент никогда не получает их. Я хотел бы реализовать UIScrollview , где drag and drop с двумя пальцами указывает на прокрутку, а событие перетаскивания одного пальца переходит к моему представлению содержимого, поэтому оно выполняется нормально. Нужно ли создавать собственный подclass UIScrollView ?

Вот мой код из моего приложения appDelegate где я реализую UIScrollView .

 @implementation MusicGridAppDelegate @synthesize window; @synthesize viewController; @synthesize scrollView; - (void)applicationDidFinishLaunching:(UIApplication *)application { // Override point for customization after app launch //[application setStatusBarHidden:YES animated:NO]; //[window addSubview:viewController.view]; scrollView.contentSize = CGSizeMake(720, 480); scrollView.showsHorizontalScrollIndicator = YES; scrollView.showsVerticalScrollIndicator = YES; scrollView.delegate = self; [scrollView addSubview:viewController.view]; [window makeKeyAndVisible]; } - (void)dealloc { [viewController release]; [scrollView release]; [window release]; [super dealloc]; } 

Вам нужно подclassифицировать UIScrollView (конечно!). Затем вам необходимо:

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

  • сделать события с двумя пальцами прокрутки прокрутки (может быть легко, может быть трудно, может быть невозможно).

Предложение Патрика в целом прекрасное: пусть ваш подclass UIScrollView знает о вашем представлении контента, а затем в обработчиках событий касания проверяет количество пальцев и направляет событие соответственно. Просто убедитесь, что (1) события, отправленные на просмотр контента, не возвращаются в UIScrollView через цепочку ответчиков (т. Е. Не обрабатывают их все), (2) уважают обычный stream событий касания (например, touchsBegan, чем некоторое количество {touchBegan, touchsMoved, touchesEnded}, завершено с помощью touchEnded или touchsCancelled), особенно при работе с UIScrollView. # 2 может быть сложно.

Если вы решите, что это событие для UIScrollView, еще один трюк – заставить UIScrollView поверить, что ваш жест с двумя пальцами – это жест одного пальца (потому что UIScrollView нельзя прокручивать двумя пальцами). Попробуйте передать только данные для одного пальца в супер (путем фильтрации аргумента (NSSet *)touches – обратите внимание, что он содержит только измененные штрихи и вообще игнорирует события для неправильного пальца).

Если это не сработает, у вас проблемы. Теоретически вы можете попытаться создать искусственные штрихи для подачи в UIScrollView, создав class, похожий на UITouch. В базовом C-коде не проверяются типы, поэтому возможно литье (YourTouch *) в (UITouch *) будет работать, и вы сможете обмануть UIScrollView для обработки штрихов, которые на самом деле не произошли.

Вероятно, вы захотите прочитать мою статью о продвинутых трюках UIScrollView (и см. Некоторые совершенно несвязанные примеры кода UIScrollView).

Конечно, если вы не можете заставить его работать, всегда есть возможность либо вручную управлять движением UIScrollView, либо использовать полностью написанное на основе прокрутки. В библиотеке Three20 есть class TTScrollView ; он не чувствует себя хорошо для пользователя, но хорошо себя чувствует программисту.

В SDK 3.2 сенсорная обработка для UIScrollView обрабатывается с помощью распознавателей жеста.

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

 for (UIGestureRecognizer *gestureRecognizer in scrollView.gestureRecognizers) { if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer; panGR.minimumNumberOfTouches = 2; } } 

Для iOS 5+ установка этого свойства имеет тот же эффект, что и ответ Майка Лоуренса:

 self.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2; 

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

В iOS 3.2+ вы можете легко выполнить прокрутку с двумя пальцами. Просто добавьте распознаватель жесты панорамы в режим прокрутки и установите максимальное значение NumberNumberOfTouches равным 1. Он будет требовать все прокрутки с одним пальцем, но позволяет прокручивать свитки с двумя палочками, чтобы передать цепочку в встроенный распознаватель жестов в виде прокрутки (и, таким образом, разрешить нормальное поведение прокрутки).

 UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(recognizePan:)]; panGestureRecognizer.maximumNumberOfTouches = 1; [scrollView addGestureRecognizer:panGestureRecognizer]; [panGestureRecognizer release]; 

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

Синтезирующий, это работает

  // makes it so that only two finger scrolls go for (id gestureRecognizer in self.gestureRecognizers) { if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { UIPanGestureRecognizer *panGR = gestureRecognizer; panGR.minimumNumberOfTouches = 2; panGR.maximumNumberOfTouches = 2; } } 

Для этого требуется два пальца для свитка. Я сделал это в подclassе, но если нет, просто замените self.gestureRecognizers на myScrollView.gestureRecognizers и вам хорошо идти.

Единственное, что я добавил, это использовать id чтобы избежать уродливого броска 🙂

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

нам удалось реализовать аналогичную функциональность в нашем приложении для рисования iPhone путем подclassа UIScrollView и фильтрации событий в зависимости от количества касаний простым и грубым способом:

 //OCRScroller.h @interface OCRUIScrollView: UIScrollView { double pass2scroller; } @end //OCRScroller.mm @implementation OCRUIScrollView - (id)initWithFrame:(CGRect)aRect { pass2scroller = 0; UIScrollView* newv = [super initWithFrame:aRect]; return newv; } - (void)setupPassOnEvent:(UIEvent *)event { int touch_cnt = [[event allTouches] count]; if(touch_cnt<=1){ pass2scroller = 0; }else{ double timems = double(CACurrentMediaTime()*1000); pass2scroller = timems+200; } } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self setupPassOnEvent:event]; [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [self setupPassOnEvent:event]; [super touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { pass2scroller = 0; [super touchesEnded:touches withEvent:event]; } - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view { return YES; } - (BOOL)touchesShouldCancelInContentView:(UIView *)view { double timems = double(CACurrentMediaTime()*1000); if (pass2scroller == 0 || timems> pass2scroller){ return NO; } return YES; } @end 

ScrollView настроен следующим образом:

 scroll_view = [[OCRUIScrollView alloc] initWithFrame:rect]; scroll_view.contentSize = img_size; scroll_view.contentOffset = CGPointMake(0,0); scroll_view.canCancelContentTouches = YES; scroll_view.delaysContentTouches = NO; scroll_view.scrollEnabled = YES; scroll_view.bounces = NO; scroll_view.bouncesZoom = YES; scroll_view.maximumZoomScale = 10.0f; scroll_view.minimumZoomScale = 0.1f; scroll_view.delegate = self; self.view = scroll_view; 

простое нажатие ничего не делает (вы можете справиться с ним так, как вам нужно), коснитесь двумя пальцами прокрутки / масштабирования, как ожидалось. GestureRecognizer не используется, поэтому работает с iOS 3.1

У меня есть дальнейшее усовершенствование кода выше. Проблема заключалась в том, что даже после того, как мы установили setCanCancelContentTouches:NO У нас возникла проблема, что жест увеличения будет прерываться с содержимым. Он не отменяет касание контента, но позволяет масштабировать тем временем. Чтобы предотвратить это, я блокирую масштабирование, устанавливая минимальное значение ZoomScale и maximumZoomScale одинаковыми значениями каждый раз, срабатывает таймер.

Довольно странное поведение заключается в том, что когда одно пальцевое событие отменяется жестом двух пальцев в течение разрешенного периода времени, таймер будет отложен. Он активируется после вызова touchCanceled Event. Поэтому у нас есть проблема, что мы пытаемся заблокировать масштабирование, хотя событие уже отменено и поэтому отключить масштабирование для следующего события. Чтобы справиться с этим поведением, метод обратного вызова таймера проверяет, были ли ранее вызваны вызовыCanceled. @implementation JWTwoFingerScrollView

 #pragma mark - #pragma mark Event Passing - (id)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { for (UIGestureRecognizer* r in self.gestureRecognizers) { if ([r isKindOfClass:[UIPanGestureRecognizer class]]) { [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2]; [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2]; zoomScale[0] = -1.0; zoomScale[1] = -1.0; } timerWasDelayed = NO; } } return self; } -(void)lockZoomScale { zoomScale[0] = self.minimumZoomScale; zoomScale[1] = self.maximumZoomScale; [self setMinimumZoomScale:self.zoomScale]; [self setMaximumZoomScale:self.zoomScale]; NSLog(@"locked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale); } -(void)unlockZoomScale { if (zoomScale[0] != -1 && zoomScale[1] != -1) { [self setMinimumZoomScale:zoomScale[0]]; [self setMaximumZoomScale:zoomScale[1]]; zoomScale[0] = -1.0; zoomScale[1] = -1.0; NSLog(@"unlocked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale); } } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"began %i",[event allTouches].count); [self setCanCancelContentTouches:YES]; if ([event allTouches].count == 1){ touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO]; [touchesBeganTimer retain]; [touchFilter touchesBegan:touches withEvent:event]; } } //if one finger touch gets canceled by two finger touch, this timer gets delayed // so we can! use this method to disable zooming, because it doesnt get called when two finger touch events are wanted; otherwise we would disable zooming while zooming -(void)firstTouchTimerFired:(NSTimer*)timer { NSLog(@"fired"); [self setCanCancelContentTouches:NO]; //if already locked: unlock //this happens because two finger gesture delays timer until touch event finishes.. then we dont want to lock! if (timerWasDelayed) { [self unlockZoomScale]; } else { [self lockZoomScale]; } timerWasDelayed = NO; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // NSLog(@"moved %i",[event allTouches].count); [touchFilter touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"ended %i",[event allTouches].count); [touchFilter touchesEnded:touches withEvent:event]; [self unlockZoomScale]; } //[self setCanCancelContentTouches:NO]; -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"canceled %i",[event allTouches].count); [touchFilter touchesCancelled:touches withEvent:event]; [self unlockZoomScale]; timerWasDelayed = YES; } @end 

Плохая новость: iPhone SDK 3.0 и выше, не передавайте прикосновения к -touchesBegan: and- touchesEnded: ** UIScrollview **. Вы можете использовать touchesShouldBegin и touchesShouldCancelInContentView методы, которые не то же самое.

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

В вашем подclassе UIScrollView переопределите метод hitTest следующим образом:

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *result = nil; for (UIView *child in self.subviews) if ([child pointInside:point withEvent:event]) if ((result = [child hitTest:point withEvent:event]) != nil) break; return result; } 

Это передаст вам подclass, который коснется, однако вы не можете отменить касания к суперclassу UIScrollView .

Что я делаю, так это то, что мой controller просмотра настроил вид прокрутки:

 [scrollView setCanCancelContentTouches:NO]; [scrollView setDelaysContentTouches:NO]; 

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

 - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // Hand tool or two or more touches means a pan or zoom gesture. if ((selectedTool == kHandToolIndex) || (event.allTouches.count > 1)) { [[self parentScrollView] setCanCancelContentTouches:YES]; [firstTouchTimer invalidate]; firstTouchTimer = nil; return; } // Use a timer to delay first touch because two-finger touches usually start with one touch followed by a second touch. [[self parentScrollView] setCanCancelContentTouches:NO]; anchorPoint = [[touches anyObject] locationInView:self]; firstTouchTimer = [NSTimer scheduledTimerWithTimeInterval:kFirstTouchTimeInterval target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO]; firstTouchTimeStamp = event.timestamp; } 

Если второе касаниеBegan: событие приходит с несколькими пальцами, просмотр прокрутки позволяет отменить касания. Поэтому, если пользователь кастрюли использует два пальца, это представление получит сообщение touchesCanceled: сообщение.

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

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

Следующий процесс обеспечит:

  • UIScrollView содержащий пользовательский вид

  • Масштабирование и панорамирование двумя пальцами (через UIPinchGestureRecognizer )

  • Обработка событий вашего представления для всех других штрихов

Во-первых, предположим, что у вас есть controller вида и его представление. В IB сделайте представление subview для scrollView и настройте правила изменения размера вашего представления так, чтобы оно не изменялось. В атрибутах scrollview включите все, что говорит «отскок», и отключите « delaysContentTouches ». Также вы должны установить масштаб min и max на значение, отличное от значения по умолчанию 1.0, поскольку, как говорят документы Apple, это необходимо для масштабирования для работы.

Создайте пользовательский подclass UIScrollView и создайте этот прокрутка в этом пользовательском подclassе. Добавьте выход к controllerу вида для прокрутки и подключите их. Теперь вы полностью настроены.

Вам нужно будет добавить следующий код в подclass UIScrollView чтобы он прозрачно проходил события касания (я подозреваю, что это можно сделать более элегантно, возможно, даже в обход всего подclassа):

 #pragma mark - #pragma mark Event Passing - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesEnded:touches withEvent:event]; } - (BOOL)touchesShouldCancelInContentView:(UIView *)view { return NO; } 

Добавьте этот код в свой controller:

 - (void)setupGestures { UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; [self.view addGestureRecognizer:pinchGesture]; [pinchGesture release]; } - (IBAction)handlePinchGesture:(UIPinchGestureRecognizer *)sender { if ( sender.state == UIGestureRecognizerStateBegan ) { //Hold values previousLocation = [sender locationInView:self.view]; previousOffset = self.scrollView.contentOffset; previousScale = self.scrollView.zoomScale; } else if ( sender.state == UIGestureRecognizerStateChanged ) { //Zoom [self.scrollView setZoomScale:previousScale*sender.scale animated:NO]; //Move location = [sender locationInView:self.view]; CGPoint offset = CGPointMake(previousOffset.x+(previousLocation.x-location.x), previousOffset.y+(previousLocation.y-location.y)); [self.scrollView setContentOffset:offset animated:NO]; } else { if ( previousScale*sender.scale < 1.15 && previousScale*sender.scale > .85 ) [self.scrollView setZoomScale:1.0 animated:YES]; } 

}

Обратите внимание, что в этом методе есть ссылки на ряд свойств, которые вы должны определить в файлах classов controllerа вида:

  • CGFloat previousScale;
  • CGPoint previousOffset;
  • CGPoint previousLocation;
  • CGPoint location;

Хорошо, вот и все!

К сожалению, я не мог заставить scrollView показывать свои скроллеры во время жестов. Я пробовал все эти страtagsи:

 //Scroll indicators self.scrollView.showsVerticalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES; [self.scrollView flashScrollIndicators]; [self.scrollView setNeedsDisplay]; 

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

Проверьте мое решение :

 #import “JWTwoFingerScrollView.h” @implementation JWTwoFingerScrollView - (id)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { for (UIGestureRecognizer* r in self.gestureRecognizers) { NSLog(@“%@”,[r class]); if ([r isKindOfClass:[UIPanGestureRecognizer class]]) { [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2]; [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2]; } } } return self; } -(void)firstTouchTimerFired:(NSTimer*)timer { [self setCanCancelContentTouches:NO]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self setCanCancelContentTouches:YES]; if ([event allTouches].count == 1){ touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo: nil repeats:NO]; [touchesBeganTimer retain]; [touchFilter touchesBegan:touches withEvent:event]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [touchFilter touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@“ended %i”,[event allTouches].count); [touchFilter touchesEnded:touches withEvent:event]; } -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@“canceled %i”,[event allTouches].count); [touchFilter touchesCancelled:touches withEvent:event]; } @end 

Он не задерживает первое касание и не останавливается, когда пользователь прикасается двумя пальцами после его использования. Тем не менее он позволяет отменить только что начатое одно касание с использованием таймера.

Да, вам нужно подclassифицировать UIScrollView и переопределить его – touchesBegan: и -touchesEnded: методы для передачи касаний «вверх». Это, вероятно, также связано с подclassом, имеющим UIView член UIView чтобы он знал, для чего он предназначен для передачи касаний.

Я помещаю это в метод viewDidLoad, и это выполняет представление прокрутки, обрабатывающее поведение двух сенсорных панелей, а другой обработчик жеста кастрюли, управляющий движением одно касание ->

 scrollView.panGestureRecognizer.minimumNumberOfTouches = 2 let panGR = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePan(_:))) panGR.minimumNumberOfTouches = 1 panGR.maximumNumberOfTouches = 1 scrollView.gestureRecognizers?.append(panGR) 

и в методе handlePan, который является функцией, прикрепленной к ViewController, есть просто оператор печати для проверки того, что метод вводится ->

 @IBAction func handlePan(_ sender: UIPanGestureRecognizer) { print("Entered handlePan numberOfTuoches: \(sender.numberOfTouches)") } 

НТН

Ответ Кенши в Swift 4

 for gestureRecognizer: UIGestureRecognizer in self.gestureRecognizers! { if (gestureRecognizer is UIPanGestureRecognizer) { let panGR = gestureRecognizer as? UIPanGestureRecognizer panGR?.minimumNumberOfTouches = 2 } } 
  • Какова максимальная длина объекта NSString?
  • Как включить и использовать новые шрифты в iPhone SDK?
  • Как я могу перебирать все подпункты UIView, а также их подзоны и их подпрограммы
  • Автоматически запускать локальные уведомления ежедневно в динамическое время, заданное в массивах. objective ios
  • Как установить жирный шрифт и курсив на UILabel iPhone / iPad?
  • Центральное содержимое UIScrollView при меньшем
  • Нажмите ViewController справа налево с помощью UINavigationController
  • Создание пользовательского UIKeyBoard для iPhone
  • Есть ли способ удалить разделительную линию из UITableView?
  • Как отключить горизонтальную прокрутку UIScrollView?
  • Как заполнить фоновое изображение UIView
  • Давайте будем гением компьютера.