HWPanModalPresentableHandler.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. //
  2. // HWPanModalPresentableHandler.m
  3. // HWPanModal
  4. //
  5. // Created by heath wang on 2019/10/15.
  6. // Copyright © 2019 Heath Wang. All rights reserved.
  7. //
  8. #import "HWPanModalPresentableHandler.h"
  9. #import "UIScrollView+Helper.h"
  10. #import "UIViewController+LayoutHelper.h"
  11. #import "UIView+HW_Frame.h"
  12. #import "KeyValueObserver.h"
  13. #import "HWPanModalContentView.h"
  14. static NSString *const kScrollViewKVOContentOffsetKey = @"contentOffset";
  15. @interface HWPanModalPresentableHandler ()
  16. @property (nonatomic, assign) CGFloat shortFormYPosition;
  17. @property (nonatomic, assign) CGFloat mediumFormYPosition;
  18. @property (nonatomic, assign) CGFloat longFormYPosition;
  19. @property (nonatomic, assign) BOOL extendsPanScrolling;
  20. @property (nonatomic, assign) BOOL anchorModalToLongForm;
  21. @property (nonatomic, assign) CGFloat anchoredYPosition;
  22. @property (nonatomic, strong) id<HWPanModalPresentable, HWPanModalPanGestureDelegate> presentable;
  23. // keyboard handle
  24. @property (nonatomic, copy) NSDictionary *keyboardInfo;
  25. @property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;
  26. @property (nonatomic, strong) UIPanGestureRecognizer *screenEdgeGestureRecognizer;
  27. // kvo
  28. @property (nonatomic, strong) id observerToken;
  29. @property (nonatomic, assign) CGFloat scrollViewYOffset;
  30. @end
  31. @implementation HWPanModalPresentableHandler
  32. - (instancetype)initWithPresentable:(id <HWPanModalPresentable>)presentable {
  33. self = [super init];
  34. if (self) {
  35. _presentable = presentable;
  36. _extendsPanScrolling = YES;
  37. _anchorModalToLongForm = YES;
  38. [self addKeyboardObserver];
  39. }
  40. return self;
  41. }
  42. + (instancetype)handlerWithPresentable:(id <HWPanModalPresentable>)presentable {
  43. return [[self alloc] initWithPresentable:presentable];
  44. }
  45. #pragma mark - Pan Gesture Event Handler
  46. - (void)didPanOnView:(UIPanGestureRecognizer *)panGestureRecognizer {
  47. if ([self shouldResponseToPanGestureRecognizer:panGestureRecognizer] && !self.keyboardInfo) {
  48. switch (panGestureRecognizer.state) {
  49. case UIGestureRecognizerStateBegan:
  50. case UIGestureRecognizerStateChanged: {
  51. [self handlePanGestureBeginOrChanged:panGestureRecognizer];
  52. }
  53. break;
  54. case UIGestureRecognizerStateEnded:
  55. case UIGestureRecognizerStateCancelled:
  56. case UIGestureRecognizerStateFailed: {
  57. [self handlePanGestureEnded:panGestureRecognizer];
  58. }
  59. break;
  60. default: break;
  61. }
  62. } else {
  63. [self handlePanGestureDidNotResponse:panGestureRecognizer];
  64. }
  65. [self.presentable didRespondToPanModalGestureRecognizer:panGestureRecognizer];
  66. }
  67. - (BOOL)shouldResponseToPanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
  68. if ([self.presentable shouldRespondToPanModalGestureRecognizer:panGestureRecognizer] ||
  69. !(panGestureRecognizer.state == UIGestureRecognizerStateBegan || panGestureRecognizer.state == UIGestureRecognizerStateCancelled)) {
  70. return ![self shouldFailPanGestureRecognizer:panGestureRecognizer];
  71. } else {
  72. // stop pan gesture working.
  73. panGestureRecognizer.enabled = NO;
  74. panGestureRecognizer.enabled = YES;
  75. return NO;
  76. }
  77. }
  78. - (BOOL)shouldFailPanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
  79. if ([self shouldPrioritizePanGestureRecognizer:panGestureRecognizer]) {
  80. // high priority than scroll view gesture, disable scrollView gesture.
  81. [self.presentable panScrollable].panGestureRecognizer.enabled = NO;
  82. [self.presentable panScrollable].panGestureRecognizer.enabled = YES;
  83. return NO;
  84. }
  85. if ([self shouldHandleShortStatePullDownWithRecognizer:panGestureRecognizer]) {
  86. // panGestureRecognizer.enabled = NO;
  87. // panGestureRecognizer.enabled = YES;
  88. return YES;
  89. }
  90. BOOL shouldFail = NO;
  91. UIScrollView *scrollView = [self.presentable panScrollable];
  92. if (scrollView) {
  93. shouldFail = scrollView.contentOffset.y > -MAX(scrollView.contentInset.top, 0);
  94. // we want scroll the panScrollable, not the presentedView
  95. if (self.isPresentedViewAnchored && shouldFail) {
  96. CGPoint location = [panGestureRecognizer locationInView:self.presentedView];
  97. BOOL flag = CGRectContainsPoint(scrollView.frame, location) || scrollView.isScrolling;
  98. if (flag) {
  99. [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
  100. }
  101. return flag;
  102. } else {
  103. return NO;
  104. }
  105. } else {
  106. return NO;
  107. }
  108. }
  109. - (BOOL)shouldHandleShortStatePullDownWithRecognizer:(UIPanGestureRecognizer *)recognizer {
  110. if ([self.presentable allowsPullDownWhenShortState]) return NO;
  111. CGPoint location = [recognizer translationInView:self.presentedView];
  112. if ([self.delegate getCurrentPresentationState] == PresentationStateShort && recognizer.state == UIGestureRecognizerStateBegan) {
  113. return YES;
  114. }
  115. if ((self.presentedView.frame.origin.y >= self.shortFormYPosition || HW_TWO_FLOAT_IS_EQUAL(self.presentedView.frame.origin.y, self.shortFormYPosition)) && location.y > 0) {
  116. return YES;
  117. }
  118. return NO;
  119. }
  120. - (BOOL)shouldPrioritizePanGestureRecognizer:(UIPanGestureRecognizer *)recognizer {
  121. return recognizer.state == UIGestureRecognizerStateBegan && [[self presentable] shouldPrioritizePanModalGestureRecognizer:recognizer];
  122. }
  123. - (void)respondToPanGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
  124. [self.presentable willRespondToPanModalGestureRecognizer:panGestureRecognizer];
  125. CGFloat yDisplacement = [panGestureRecognizer translationInView:self.presentedView].y;
  126. if (self.presentedView.frame.origin.y < self.longFormYPosition) {
  127. yDisplacement = yDisplacement / 2;
  128. }
  129. id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
  130. if ([delegate respondsToSelector:@selector(adjustPresentableYPos:)]) {
  131. [delegate adjustPresentableYPos:self.presentedView.frame.origin.y + yDisplacement];
  132. }
  133. [panGestureRecognizer setTranslation:CGPointZero inView:self.presentedView];
  134. }
  135. - (BOOL)isVelocityWithinSensitivityRange:(CGFloat)velocity {
  136. return (fabs(velocity) - [self.presentable minVerticalVelocityToTriggerDismiss]) > 0;
  137. }
  138. - (CGFloat)nearestDistance:(CGFloat)position inDistances:(NSArray *)distances {
  139. if (distances.count <= 0) {
  140. return position;
  141. }
  142. // TODO: need refine this sort code.
  143. NSMutableArray *tmpArr = [NSMutableArray arrayWithCapacity:distances.count];
  144. NSMutableDictionary *tmpDict = [NSMutableDictionary dictionaryWithCapacity:distances.count];
  145. [distances enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  146. NSNumber *number = obj;
  147. NSNumber *absValue = @(fabs(number.floatValue - position));
  148. [tmpArr addObject:absValue];
  149. tmpDict[absValue] = number;
  150. }];
  151. [tmpArr sortUsingSelector:@selector(compare:)];
  152. NSNumber *result = tmpDict[tmpArr.firstObject];
  153. return result.floatValue;
  154. }
  155. - (void)screenEdgeInteractiveAction:(UIPanGestureRecognizer *)gestureRecognizer {
  156. //
  157. }
  158. #pragma mark - handle did Pan gesture events
  159. - (void)handlePanGestureDidNotResponse:(UIPanGestureRecognizer *)panGestureRecognizer {
  160. switch (panGestureRecognizer.state) {
  161. case UIGestureRecognizerStateEnded:
  162. case UIGestureRecognizerStateCancelled:
  163. case UIGestureRecognizerStateFailed: {
  164. [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
  165. [self cancelInteractiveTransition];
  166. }
  167. break;
  168. default:
  169. break;
  170. }
  171. [panGestureRecognizer setTranslation:CGPointZero inView:panGestureRecognizer.view];
  172. }
  173. - (void)handlePanGestureBeginOrChanged:(UIPanGestureRecognizer *)panGestureRecognizer {
  174. CGPoint velocity = [panGestureRecognizer velocityInView:self.presentedView];
  175. [self respondToPanGestureRecognizer:panGestureRecognizer];
  176. if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) {
  177. // check if toggle dismiss action
  178. if ([[self presentable] presentingVCAnimationStyle] > PresentingViewControllerAnimationStyleNone &&
  179. velocity.y > 0 &&
  180. (self.presentedView.frame.origin.y > self.shortFormYPosition || HW_TWO_FLOAT_IS_EQUAL(self.presentedView.frame.origin.y, self.shortFormYPosition))) {
  181. [self dismissPresentable:YES mode:PanModalInteractiveModeDragDown];
  182. }
  183. }
  184. if (HW_TWO_FLOAT_IS_EQUAL(self.presentedView.frame.origin.y, self.anchoredYPosition) && self.extendsPanScrolling) {
  185. [self.presentable willTransitionToState:PresentationStateLong];
  186. }
  187. // update drag indicator
  188. if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
  189. if (velocity.y > 0) {
  190. [self.dragIndicatorView didChangeToState:HWIndicatorStatePullDown];
  191. } else if (velocity.y < 0 && self.presentedView.frame.origin.y <= self.anchoredYPosition && !self.extendsPanScrolling) {
  192. [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
  193. }
  194. }
  195. }
  196. - (void)handlePanGestureEnded:(UIPanGestureRecognizer *)panGestureRecognizer {
  197. CGPoint velocity = [panGestureRecognizer velocityInView:self.presentedView];
  198. /**
  199. * pan recognizer结束
  200. * 根据velocity(速度),当velocity.y < 0,说明用户在向上拖拽view;当velocity.y > 0,向下拖拽
  201. * 根据拖拽的速度,处理不同的情况:
  202. * 1.超过拖拽速度阈值时并且向下拖拽,dismiss controller
  203. * 2.向上拖拽永远不会dismiss,回弹至相应的状态
  204. */
  205. if ([self isVelocityWithinSensitivityRange:velocity.y]) {
  206. id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
  207. PresentationState currentState = [delegate getCurrentPresentationState];
  208. if (velocity.y < 0) {
  209. [self handleDragUpState:currentState];
  210. } else {
  211. [self handleDragDownState:currentState];
  212. }
  213. } else {
  214. CGFloat position = [self nearestDistance:CGRectGetMinY(self.presentedView.frame) inDistances:@[@([self containerSize].height), @(self.shortFormYPosition), @(self.longFormYPosition), @(self.mediumFormYPosition)]];
  215. if (HW_TWO_FLOAT_IS_EQUAL(position, self.longFormYPosition)) {
  216. [self transitionToState:PresentationStateLong];
  217. [self cancelInteractiveTransition];
  218. } else if (HW_TWO_FLOAT_IS_EQUAL(position, self.mediumFormYPosition)) {
  219. [self transitionToState:PresentationStateMedium];
  220. [self cancelInteractiveTransition];
  221. } else if (HW_TWO_FLOAT_IS_EQUAL(position, self.shortFormYPosition) || ![self.presentable allowsDragToDismiss]) {
  222. [self transitionToState:PresentationStateShort];
  223. [self cancelInteractiveTransition];
  224. } else {
  225. if ([self isBeingDismissed]) {
  226. [self finishInteractiveTransition];
  227. } else {
  228. [self dismissPresentable:NO mode:PanModalInteractiveModeNone];
  229. }
  230. }
  231. }
  232. [self.presentable didEndRespondToPanModalGestureRecognizer:panGestureRecognizer];
  233. }
  234. - (void)handleDragUpState:(PresentationState)state {
  235. switch (state) {
  236. case PresentationStateLong:
  237. [self transitionToState:PresentationStateLong];
  238. [self cancelInteractiveTransition];
  239. break;
  240. case PresentationStateMedium:
  241. [self transitionToState:PresentationStateLong];
  242. [self cancelInteractiveTransition];
  243. break;
  244. case PresentationStateShort:
  245. [self transitionToState:PresentationStateMedium];
  246. [self cancelInteractiveTransition];
  247. break;
  248. default:
  249. break;
  250. }
  251. }
  252. - (void)handleDragDownState:(PresentationState)state {
  253. switch (state) {
  254. case PresentationStateLong:
  255. [self transitionToState:PresentationStateMedium];
  256. [self cancelInteractiveTransition];
  257. break;
  258. case PresentationStateMedium:
  259. [self transitionToState:PresentationStateShort];
  260. [self cancelInteractiveTransition];
  261. break;
  262. case PresentationStateShort:
  263. if (![self.presentable allowsDragToDismiss]) {
  264. [self transitionToState:PresentationStateShort];
  265. [self cancelInteractiveTransition];
  266. } else {
  267. if ([self isBeingDismissed]) {
  268. [self finishInteractiveTransition];
  269. } else {
  270. [self dismissPresentable:NO mode:PanModalInteractiveModeNone];
  271. }
  272. }
  273. break;
  274. default:
  275. break;
  276. }
  277. }
  278. #pragma mark - UIScrollView kvo
  279. - (void)observeScrollable {
  280. UIScrollView *scrollView = [[self presentable] panScrollable];
  281. if (!scrollView) {
  282. // force set observerToken to nil, make sure to callback.
  283. self.observerToken = nil;
  284. return;
  285. }
  286. self.scrollViewYOffset = MAX(scrollView.contentOffset.y, -(MAX(scrollView.contentInset.top, 0)));
  287. self.observerToken = [KeyValueObserver observeObject:scrollView keyPath:kScrollViewKVOContentOffsetKey target:self selector:@selector(didPanOnScrollViewChanged:) options:NSKeyValueObservingOptionOld];
  288. }
  289. /**
  290. As the user scrolls, track & save the scroll view y offset.
  291. This helps halt scrolling when we want to hold the scroll view in place.
  292. */
  293. - (void)trackScrolling:(UIScrollView *)scrollView {
  294. self.scrollViewYOffset = MAX(scrollView.contentOffset.y, -(MAX(scrollView.contentInset.top, 0)));
  295. scrollView.showsVerticalScrollIndicator = [[self presentable] showsScrollableVerticalScrollIndicator];
  296. }
  297. /**
  298. * Halts the scroll of a given scroll view & anchors it at the `scrollViewYOffset`
  299. */
  300. - (void)haltScrolling:(UIScrollView *)scrollView {
  301. //
  302. // Fix bug: the app will crash after the table view reloads data via calling [tableView reloadData] if the user scrolls to the bottom.
  303. //
  304. // We remove some element and reload data, for example, [self.dataSource removeLastObject], the previous saved scrollViewYOffset value
  305. // will be great than or equal to the current actual offset(i.e. scrollView.contentOffset.y). At this time, if the method
  306. // [scrollView setContentOffset:CGPointMake(0, self.scrollViewYOffset) animated:NO] is called, which will trigger KVO recursively.
  307. // So scrollViewYOffset must be less than or equal to the actual offset here.
  308. // See issues: https://github.com/HeathWang/HWPanModal/issues/107 and https://github.com/HeathWang/HWPanModal/issues/103
  309. if (scrollView.contentOffset.y <= 0 || self.scrollViewYOffset <= scrollView.contentOffset.y) {
  310. [scrollView setContentOffset:CGPointMake(0, self.scrollViewYOffset) animated:NO];
  311. scrollView.showsVerticalScrollIndicator = NO;
  312. }
  313. }
  314. - (void)didPanOnScrollViewChanged:(NSDictionary<NSKeyValueChangeKey, id> *)change {
  315. UIScrollView *scrollView = [[self presentable] panScrollable];
  316. if (!scrollView) return;
  317. if ((![self isBeingDismissed] && ![self isBeingPresented]) ||
  318. ([self isBeingDismissed] && [self isPresentedViewControllerInteractive])) {
  319. if (![self isPresentedViewAnchored] && scrollView.contentOffset.y > 0) {
  320. [self haltScrolling:scrollView];
  321. } else if ([scrollView isScrolling] || [self isPresentedViewAnimating]) {
  322. // While we're scrolling upwards on the scrollView, store the last content offset position
  323. if ([self isPresentedViewAnchored]) {
  324. [self trackScrolling:scrollView];
  325. } else {
  326. /**
  327. * Keep scroll view in place while we're panning on main view
  328. */
  329. [self haltScrolling:scrollView];
  330. }
  331. } else {
  332. [self trackScrolling:scrollView];
  333. }
  334. } else {
  335. /**
  336. * 当present Controller,而且动画没有结束的时候,用户可能会对scrollView设置contentOffset
  337. * 首次用户滑动scrollView时,会因为scrollViewYOffset = 0而出现错位
  338. */
  339. if ([self isBeingPresented]) {
  340. [self setScrollableContentOffset:scrollView.contentOffset animated:YES];
  341. }
  342. }
  343. }
  344. #pragma mark - UIScrollView update
  345. - (void)configureScrollViewInsets {
  346. // when scrolling, return
  347. if ([self.presentable panScrollable] && ![self.presentable panScrollable].isScrolling) {
  348. UIScrollView *scrollView = [self.presentable panScrollable];
  349. // 禁用scrollView indicator除非用户开始滑动scrollView
  350. scrollView.showsVerticalScrollIndicator = [self.presentable showsScrollableVerticalScrollIndicator];
  351. scrollView.scrollEnabled = [self.presentable isPanScrollEnabled];
  352. scrollView.scrollIndicatorInsets = [self.presentable scrollIndicatorInsets];
  353. if (![self.presentable shouldAutoSetPanScrollContentInset]) return;
  354. UIEdgeInsets insets1 = scrollView.contentInset;
  355. CGFloat bottomLayoutOffset = [UIApplication sharedApplication].keyWindow.rootViewController.bottomLayoutGuide.length;
  356. /*
  357. * If scrollView has been set contentInset, and bottom is NOT zero, we won't change it.
  358. * If contentInset.bottom is zero, set bottom = bottomLayoutOffset
  359. * If scrollView has been set contentInset, BUT the bottom < bottomLayoutOffset, set bottom = bottomLayoutOffset
  360. */
  361. if (HW_FLOAT_IS_ZERO(insets1.bottom) || insets1.bottom < bottomLayoutOffset) {
  362. insets1.bottom = bottomLayoutOffset;
  363. scrollView.contentInset = insets1;
  364. }
  365. if (@available(iOS 11.0, *)) {
  366. scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  367. } else {
  368. // Fallback on earlier versions
  369. }
  370. }
  371. }
  372. - (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated {
  373. if (![self.presentable panScrollable]) return;
  374. UIScrollView *scrollView = [self.presentable panScrollable];
  375. [self.observerToken unObserver];
  376. [scrollView setContentOffset:offset animated:animated];
  377. // wait for animation finished.
  378. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) ((animated ? 0.30 : 0.1) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  379. [self trackScrolling:scrollView];
  380. [self observeScrollable];
  381. });
  382. }
  383. #pragma mark - layout
  384. - (void)configureViewLayout {
  385. if ([self.presentable isKindOfClass:UIViewController.class]) {
  386. UIViewController<HWPanModalPresentable> *layoutPresentable = (UIViewController<HWPanModalPresentable> *) self.presentable;
  387. self.shortFormYPosition = layoutPresentable.shortFormYPos;
  388. self.mediumFormYPosition = layoutPresentable.mediumFormYPos;
  389. self.longFormYPosition = layoutPresentable.longFormYPos;
  390. self.anchorModalToLongForm = [layoutPresentable anchorModalToLongForm];
  391. self.extendsPanScrolling = [layoutPresentable allowsExtendedPanScrolling];
  392. } else if ([self.presentable isKindOfClass:HWPanModalContentView.class]) {
  393. HWPanModalContentView<HWPanModalPresentable> *layoutPresentable = (HWPanModalContentView<HWPanModalPresentable> *) self.presentable;
  394. self.shortFormYPosition = layoutPresentable.shortFormYPos;
  395. self.mediumFormYPosition = layoutPresentable.mediumFormYPos;
  396. self.longFormYPosition = layoutPresentable.longFormYPos;
  397. self.anchorModalToLongForm = [layoutPresentable anchorModalToLongForm];
  398. self.extendsPanScrolling = [layoutPresentable allowsExtendedPanScrolling];
  399. }
  400. }
  401. #pragma mark - UIGestureRecognizerDelegate
  402. /**
  403. * ONLY When otherGestureRecognizer is panGestureRecognizer, and target gestureRecognizer is panGestureRecognizer, return YES.
  404. */
  405. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  406. if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
  407. return [self.presentable hw_gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
  408. }
  409. if ([gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) {
  410. return [otherGestureRecognizer isKindOfClass:UIPanGestureRecognizer.class];
  411. }
  412. return NO;
  413. }
  414. /**
  415. * 当前手势为screenGestureRecognizer时,其他pan recognizer都应该fail
  416. */
  417. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  418. if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:)]) {
  419. return [self.presentable hw_gestureRecognizer:gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:otherGestureRecognizer];
  420. }
  421. if (gestureRecognizer == self.screenEdgeGestureRecognizer && [otherGestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) {
  422. return YES;
  423. }
  424. return NO;
  425. }
  426. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  427. if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizer:shouldRequireFailureOfGestureRecognizer:)]) {
  428. return [self.presentable hw_gestureRecognizer:gestureRecognizer shouldRequireFailureOfGestureRecognizer:otherGestureRecognizer];
  429. }
  430. return NO;
  431. }
  432. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  433. if ([self.presentable respondsToSelector:@selector(hw_gestureRecognizerShouldBegin:)]) {
  434. return [self.presentable hw_gestureRecognizerShouldBegin:gestureRecognizer];
  435. }
  436. if (gestureRecognizer == self.screenEdgeGestureRecognizer) {
  437. CGPoint velocity = [self.screenEdgeGestureRecognizer velocityInView:self.screenEdgeGestureRecognizer.view];
  438. if (velocity.x <= 0 || HW_TWO_FLOAT_IS_EQUAL(velocity.x, 0)) {
  439. return NO;
  440. }
  441. // check the distance to left edge
  442. CGPoint location = [self.screenEdgeGestureRecognizer locationInView:self.screenEdgeGestureRecognizer.view];
  443. CGFloat thresholdDistance = [[self presentable] maxAllowedDistanceToLeftScreenEdgeForPanInteraction];
  444. if (thresholdDistance > 0 && location.x > thresholdDistance) {
  445. return NO;
  446. }
  447. if (velocity.x > 0 && HW_TWO_FLOAT_IS_EQUAL(velocity.y, 0)) {
  448. return YES;
  449. }
  450. //TODO: this logic can be updated later.
  451. if (velocity.x > 0 && velocity.x / fabs(velocity.y) > 2) {
  452. return YES;
  453. }
  454. return NO;
  455. }
  456. return YES;
  457. }
  458. #pragma mark - UIKeyboard Handle
  459. - (void)addKeyboardObserver {
  460. if ([self.presentable isAutoHandleKeyboardEnabled]) {
  461. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
  462. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
  463. }
  464. }
  465. - (void)removeKeyboardObserver {
  466. [[NSNotificationCenter defaultCenter] removeObserver:self];
  467. }
  468. - (void)keyboardWillShow:(NSNotification *)notification {
  469. UIView<UIKeyInput> *currentInput = [self findCurrentTextInputInView:self.presentedView];
  470. if (!currentInput)
  471. return;
  472. self.keyboardInfo = notification.userInfo;
  473. [self updatePanContainerFrameForKeyboard];
  474. }
  475. - (void)keyboardWillHide:(NSNotification *)notification {
  476. self.keyboardInfo = nil;
  477. NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  478. UIViewAnimationCurve curve = (UIViewAnimationCurve) [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
  479. [UIView beginAnimations:nil context:nil];
  480. [UIView setAnimationBeginsFromCurrentState:YES];
  481. [UIView setAnimationCurve:curve];
  482. [UIView setAnimationDuration:duration];
  483. self.presentedView.transform = CGAffineTransformIdentity;
  484. [UIView commitAnimations];
  485. }
  486. - (void)updatePanContainerFrameForKeyboard {
  487. if (!self.keyboardInfo)
  488. return;
  489. UIView<UIKeyInput> *textInput = [self findCurrentTextInputInView:self.presentedView];
  490. if (!textInput)
  491. return;
  492. CGAffineTransform lastTransform = self.presentedView.transform;
  493. self.presentedView.transform = CGAffineTransformIdentity;
  494. CGFloat textViewBottomY = [textInput convertRect:textInput.bounds toView:self.presentedView].origin.y + textInput.hw_height;
  495. CGFloat keyboardHeight = [self.keyboardInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
  496. CGFloat offsetY = 0;
  497. CGFloat top = [self.presentable keyboardOffsetFromInputView];
  498. offsetY = self.presentedView.hw_height - (keyboardHeight + top + textViewBottomY + self.presentedView.hw_top);
  499. NSTimeInterval duration = [self.keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  500. UIViewAnimationCurve curve = (UIViewAnimationCurve) [self.keyboardInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
  501. self.presentedView.transform = lastTransform;
  502. [UIView beginAnimations:nil context:NULL];
  503. [UIView setAnimationBeginsFromCurrentState:YES];
  504. [UIView setAnimationCurve:curve];
  505. [UIView setAnimationDuration:duration];
  506. self.presentedView.transform = CGAffineTransformMakeTranslation(0, offsetY);
  507. [UIView commitAnimations];
  508. }
  509. - (UIView <UIKeyInput> *)findCurrentTextInputInView:(UIView *)view {
  510. if ([view conformsToProtocol:@protocol(UIKeyInput)] && view.isFirstResponder) {
  511. // Quick fix for web view issue
  512. if ([view isKindOfClass:NSClassFromString(@"UIWebBrowserView")] || [view isKindOfClass:NSClassFromString(@"WKContentView")]) {
  513. return nil;
  514. }
  515. return (UIView <UIKeyInput> *) view;
  516. }
  517. for (UIView *subview in view.subviews) {
  518. UIView <UIKeyInput> *inputInView = [self findCurrentTextInputInView:subview];
  519. if (inputInView) {
  520. return inputInView;
  521. }
  522. }
  523. return nil;
  524. }
  525. #pragma mark - delegate throw
  526. - (void)transitionToState:(PresentationState)state {
  527. id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
  528. if ([delegate respondsToSelector:@selector(presentableTransitionToState:)]) {
  529. [delegate presentableTransitionToState:state];
  530. }
  531. }
  532. - (void)cancelInteractiveTransition {
  533. id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
  534. if ([delegate respondsToSelector:@selector(cancelInteractiveTransition)]) {
  535. [delegate cancelInteractiveTransition];
  536. }
  537. }
  538. - (void)finishInteractiveTransition {
  539. id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
  540. if ([delegate respondsToSelector:@selector(finishInteractiveTransition)]) {
  541. [delegate finishInteractiveTransition];
  542. }
  543. }
  544. - (void)dismissPresentable:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode {
  545. id <HWPanModalPresentableHandlerDelegate> delegate = self.delegate;
  546. if ([delegate respondsToSelector:@selector(dismiss:mode:)]) {
  547. [delegate dismiss:isInteractive mode:mode];
  548. }
  549. }
  550. #pragma mark - dataSource handle
  551. - (BOOL)isPresentedViewAnchored {
  552. if (self.dataSource && [self.dataSource respondsToSelector:@selector(isPresentedViewAnchored)]) {
  553. return [self.dataSource isPresentedViewAnchored];
  554. }
  555. return NO;
  556. }
  557. - (BOOL)isBeingDismissed {
  558. if (self.dataSource && [self.dataSource respondsToSelector:@selector(isBeingDismissed)]) {
  559. return [self.dataSource isBeingDismissed];
  560. }
  561. return NO;
  562. }
  563. - (BOOL)isBeingPresented {
  564. if (self.dataSource && [self.dataSource respondsToSelector:@selector(isBeingPresented)]) {
  565. return [self.dataSource isBeingPresented];
  566. }
  567. return NO;
  568. }
  569. - (BOOL)isPresentedViewControllerInteractive {
  570. if (self.dataSource && [self.dataSource respondsToSelector:@selector(isPresentedControllerInteractive)]) {
  571. return [self.dataSource isPresentedControllerInteractive];
  572. }
  573. return NO;
  574. }
  575. - (BOOL)isPresentedViewAnimating {
  576. if (self.dataSource && [self.dataSource respondsToSelector:@selector(isFormPositionAnimating)]) {
  577. [self.dataSource isFormPositionAnimating];
  578. }
  579. return NO;
  580. }
  581. - (CGSize)containerSize {
  582. if (self.dataSource && [self.dataSource respondsToSelector:@selector(containerSize)]) {
  583. return [self.dataSource containerSize];
  584. }
  585. return CGSizeZero;
  586. }
  587. #pragma mark - Getter
  588. - (UIPanGestureRecognizer *)panGestureRecognizer {
  589. if (!_panGestureRecognizer) {
  590. _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didPanOnView:)];
  591. _panGestureRecognizer.minimumNumberOfTouches = 1;
  592. _panGestureRecognizer.maximumNumberOfTouches = 1;
  593. _panGestureRecognizer.delegate = self;
  594. }
  595. return _panGestureRecognizer;
  596. }
  597. - (UIPanGestureRecognizer *)screenEdgeGestureRecognizer {
  598. if (!_screenEdgeGestureRecognizer) {
  599. _screenEdgeGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(screenEdgeInteractiveAction:)];
  600. _screenEdgeGestureRecognizer.minimumNumberOfTouches = 1;
  601. _screenEdgeGestureRecognizer.maximumNumberOfTouches = 1;
  602. _screenEdgeGestureRecognizer.delegate = self;
  603. }
  604. return _screenEdgeGestureRecognizer;
  605. }
  606. - (CGFloat)anchoredYPosition {
  607. CGFloat defaultTopOffset = [self.presentable topOffset];
  608. return self.anchorModalToLongForm ? self.longFormYPosition : defaultTopOffset;
  609. }
  610. #pragma mark - Dealloc
  611. - (void)dealloc {
  612. [self removeKeyboardObserver];
  613. }
  614. @end