HWPanModalPresentationController.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. //
  2. // HWPanModalPresentationController.m
  3. // HWPanModal
  4. //
  5. // Created by heath wang on 2019/4/26.
  6. //
  7. #import "HWPanModalPresentationController.h"
  8. #import "HWDimmedView.h"
  9. #import "HWPanContainerView.h"
  10. #import "UIViewController+LayoutHelper.h"
  11. #import "HWPanModalAnimator.h"
  12. #import "HWPanModalInteractiveAnimator.h"
  13. #import "HWPanModalPresentationDelegate.h"
  14. #import "UIViewController+PanModalPresenter.h"
  15. #import "HWPanIndicatorView.h"
  16. #import "UIView+HW_Frame.h"
  17. #import "HWPanModalPresentableHandler.h"
  18. @interface HWPanModalPresentationController () <UIGestureRecognizerDelegate, HWPanModalPresentableHandlerDelegate, HWPanModalPresentableHandlerDataSource>
  19. // 判断弹出的view是否在做动画
  20. @property (nonatomic, assign) BOOL isPresentedViewAnimating;
  21. @property (nonatomic, assign) PresentationState currentPresentationState;
  22. @property (nonatomic, strong) id<HWPanModalPresentable> presentable;
  23. // view
  24. @property (nonatomic, strong) HWDimmedView *backgroundView;
  25. @property (nonatomic, strong) HWPanContainerView *panContainerView;
  26. @property (nonatomic, strong) UIView<HWPanModalIndicatorProtocol> *dragIndicatorView;
  27. @property (nonatomic, strong) HWPanModalPresentableHandler *handler;
  28. @end
  29. @implementation HWPanModalPresentationController
  30. - (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(nullable UIViewController *)presentingViewController {
  31. self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
  32. if (self) {
  33. _handler = [[HWPanModalPresentableHandler alloc] initWithPresentable:[self presentable]];
  34. _handler.delegate = self;
  35. _handler.dataSource = self;
  36. }
  37. return self;
  38. }
  39. #pragma mark - overridden
  40. - (UIView *)presentedView {
  41. return self.panContainerView;
  42. }
  43. - (void)containerViewWillLayoutSubviews {
  44. [super containerViewWillLayoutSubviews];
  45. [self configureViewLayout];
  46. }
  47. #pragma mark - Tracking the Transition Start and End
  48. - (void)presentationTransitionWillBegin {
  49. [[self presentable] panModalTransitionWillBegin];
  50. if (!self.containerView)
  51. return;
  52. [self layoutBackgroundView:self.containerView];
  53. if ([[self presentable] originPresentationState] == PresentationStateLong) {
  54. self.currentPresentationState = PresentationStateLong;
  55. } else if ([[self presentable] originPresentationState] == PresentationStateMedium) {
  56. self.currentPresentationState = PresentationStateMedium;
  57. }
  58. [self layoutPresentedView:self.containerView];
  59. [self.handler configureScrollViewInsets];
  60. if (!self.presentedViewController.transitionCoordinator) {
  61. self.backgroundView.dimState = DimStateMax;
  62. return;
  63. }
  64. __weak typeof(self) wkSelf = self;
  65. [self.presentedViewController.transitionCoordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
  66. wkSelf.backgroundView.dimState = DimStateMax;
  67. [wkSelf.presentedViewController setNeedsStatusBarAppearanceUpdate];
  68. } completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
  69. if ([[wkSelf presentable] allowsTouchEventsPassingThroughTransitionView]) {
  70. // hack TransitionView
  71. [wkSelf.containerView setValue:@(YES) forKey:@"ignoreDirectTouchEvents"];
  72. }
  73. }];
  74. }
  75. - (void)presentationTransitionDidEnd:(BOOL)completed {
  76. [[self presentable] panModalTransitionDidFinish];
  77. if (completed)
  78. return;
  79. [self.backgroundView removeFromSuperview];
  80. [self.presentedView endEditing:YES];
  81. }
  82. - (void)dismissalTransitionWillBegin {
  83. id <UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentedViewController.transitionCoordinator;
  84. if (!transitionCoordinator) {
  85. self.backgroundView.dimState = DimStateOff;
  86. return;
  87. }
  88. __weak typeof(self) wkSelf = self;
  89. [transitionCoordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
  90. wkSelf.dragIndicatorView.alpha = 0;
  91. wkSelf.backgroundView.dimState = DimStateOff;
  92. [wkSelf.presentedViewController setNeedsStatusBarAppearanceUpdate];
  93. } completion:^(id <UIViewControllerTransitionCoordinatorContext> context) {
  94. }];
  95. }
  96. - (void)dismissalTransitionDidEnd:(BOOL)completed {
  97. if (completed) {
  98. // break the delegate
  99. self.delegate = nil;
  100. }
  101. }
  102. #pragma mark - UIContentContainer protocol
  103. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
  104. [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  105. [coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
  106. if (self && [self presentable]) {
  107. [self adjustPresentedViewFrame];
  108. if ([self.presentable shouldRoundTopCorners]) {
  109. [self addRoundedCornersToView:self.panContainerView.contentView];
  110. }
  111. [self updateDragIndicatorView];
  112. }
  113. } completion:^(id <UIViewControllerTransitionCoordinatorContext> context) {
  114. [self transitionToState:self.currentPresentationState animated:NO];
  115. }];
  116. }
  117. #pragma mark - public method
  118. - (void)setNeedsLayoutUpdate {
  119. [self configureViewLayout];
  120. [self adjustPresentedViewFrame];
  121. [self updateBackgroundColor];
  122. [self updateContainerViewShadow];
  123. [self updateDragIndicatorView];
  124. [self updateRoundedCorners];
  125. [self.handler observeScrollable];
  126. [self.handler configureScrollViewInsets];
  127. [self checkEdgeInteractive];
  128. }
  129. - (void)transitionToState:(PresentationState)state animated:(BOOL)animated {
  130. if (![self.presentable shouldTransitionToState:state])
  131. return;
  132. [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
  133. [self.presentable willTransitionToState:state];
  134. switch (state) {
  135. case PresentationStateLong: {
  136. [self snapToYPos:self.handler.longFormYPosition animated:animated];
  137. }
  138. break;
  139. case PresentationStateMedium: {
  140. [self snapToYPos:self.handler.mediumFormYPosition animated:animated];
  141. }
  142. break;
  143. case PresentationStateShort: {
  144. [self snapToYPos:self.handler.shortFormYPosition animated:animated];
  145. }
  146. break;
  147. }
  148. self.currentPresentationState = state;
  149. [[self presentable] didChangeTransitionToState:state];
  150. }
  151. - (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated {
  152. [self.handler setScrollableContentOffset:offset animated:animated];
  153. }
  154. - (void)updateUserHitBehavior {
  155. [self checkVCContainerEventPass];
  156. [self checkBackgroundViewEventPass];
  157. }
  158. #pragma mark - layout
  159. - (void)adjustPresentedViewFrame {
  160. if (!self.containerView)
  161. return;
  162. CGRect frame = self.containerView.frame;
  163. CGSize size = CGSizeMake(CGRectGetWidth(frame), CGRectGetHeight(frame) - self.handler.anchoredYPosition);
  164. self.presentedView.hw_size = frame.size;
  165. self.panContainerView.contentView.frame = CGRectMake(0, 0, size.width, size.height);
  166. self.presentedViewController.view.frame = self.panContainerView.contentView.bounds;
  167. [self.presentedViewController.view setNeedsLayout];
  168. [self.presentedViewController.view layoutIfNeeded];
  169. }
  170. /**
  171. * add backGroundView并设置约束
  172. */
  173. - (void)layoutBackgroundView:(UIView *)containerView {
  174. [containerView addSubview:self.backgroundView];
  175. [self updateBackgroundColor];
  176. self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
  177. NSArray *hCons = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
  178. NSArray *vCons = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
  179. [NSLayoutConstraint activateConstraints:hCons];
  180. [NSLayoutConstraint activateConstraints:vCons];
  181. }
  182. - (void)updateBackgroundColor {
  183. self.backgroundView.blurTintColor = [self.presentable backgroundConfig].blurTintColor;
  184. }
  185. - (void)layoutPresentedView:(UIView *)containerView {
  186. if (!self.presentable)
  187. return;
  188. self.handler.presentedView = self.presentedView;
  189. [containerView addSubview:self.presentedView];
  190. [containerView addGestureRecognizer:self.handler.panGestureRecognizer];
  191. if ([self.presentable allowScreenEdgeInteractive]) {
  192. [containerView addGestureRecognizer:self.handler.screenEdgeGestureRecognizer];
  193. [self.handler.screenEdgeGestureRecognizer addTarget:self action:@selector(screenEdgeInteractiveAction:)];
  194. }
  195. [self setNeedsLayoutUpdate];
  196. [self adjustPanContainerBackgroundColor];
  197. [[self presentable] presentedViewDidMoveToSuperView];
  198. }
  199. - (void)adjustPanContainerBackgroundColor {
  200. self.panContainerView.contentView.backgroundColor = self.presentedViewController.view.backgroundColor ? : [self.presentable panScrollable].backgroundColor;
  201. }
  202. - (void)updateDragIndicatorView {
  203. if ([self.presentable showDragIndicator]) {
  204. [self addDragIndicatorViewToView:self.panContainerView];
  205. } else {
  206. self.dragIndicatorView.hidden = YES;
  207. }
  208. }
  209. - (void)addDragIndicatorViewToView:(UIView *)view {
  210. // if has been add, won't update it.
  211. self.dragIndicatorView.hidden = NO;
  212. CGSize indicatorSize = [self.dragIndicatorView indicatorSize];
  213. if (self.dragIndicatorView.superview == view) {
  214. self.dragIndicatorView.frame = CGRectMake((view.hw_width - indicatorSize.width) / 2, -kIndicatorYOffset - indicatorSize.height, indicatorSize.width, indicatorSize.height);
  215. [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
  216. return;
  217. }
  218. self.handler.dragIndicatorView = self.dragIndicatorView;
  219. [view addSubview:self.dragIndicatorView];
  220. self.dragIndicatorView.frame = CGRectMake((view.hw_width - indicatorSize.width) / 2, -kIndicatorYOffset - indicatorSize.height, indicatorSize.width, indicatorSize.height);
  221. [self.dragIndicatorView setupSubviews];
  222. [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
  223. }
  224. - (void)updateRoundedCorners {
  225. if ([self.presentable shouldRoundTopCorners]) {
  226. [self addRoundedCornersToView:self.panContainerView.contentView];
  227. } else {
  228. [self resetRoundedCornersToView:self.panContainerView.contentView];
  229. }
  230. }
  231. - (void)addRoundedCornersToView:(UIView *)view {
  232. CGFloat radius = [self.presentable cornerRadius];
  233. UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerTopRight | UIRectCornerTopLeft cornerRadii:CGSizeMake(radius, radius)];
  234. CAShapeLayer *mask = [CAShapeLayer new];
  235. mask.path = bezierPath.CGPath;
  236. view.layer.mask = mask;
  237. // 提高性能
  238. view.layer.shouldRasterize = YES;
  239. view.layer.rasterizationScale = [UIScreen mainScreen].scale;
  240. }
  241. - (void)resetRoundedCornersToView:(UIView *)view {
  242. view.layer.mask = nil;
  243. view.layer.shouldRasterize = NO;
  244. }
  245. - (void)updateContainerViewShadow {
  246. HWPanModalShadow shadow = [[self presentable] contentShadow];
  247. if (shadow.shadowColor) {
  248. [self.panContainerView updateShadow:shadow.shadowColor shadowRadius:shadow.shadowRadius shadowOffset:shadow.shadowOffset shadowOpacity:shadow.shadowOpacity];
  249. } else {
  250. [self.panContainerView clearShadow];
  251. }
  252. }
  253. /**
  254. * Calculates & stores the layout anchor points & options
  255. */
  256. - (void)configureViewLayout {
  257. [self.handler configureViewLayout];
  258. self.containerView.userInteractionEnabled = [[self presentable] isUserInteractionEnabled];
  259. }
  260. #pragma mark - event passing through
  261. - (void)checkVCContainerEventPass {
  262. BOOL eventPassValue = [[self presentable] allowsTouchEventsPassingThroughTransitionView];
  263. // hack TransitionView
  264. [self.containerView setValue:@(eventPassValue) forKey:@"ignoreDirectTouchEvents"];
  265. }
  266. - (void)checkBackgroundViewEventPass {
  267. if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
  268. self.backgroundView.userInteractionEnabled = NO;
  269. self.backgroundView.tapBlock = nil;
  270. } else {
  271. self.backgroundView.userInteractionEnabled = YES;
  272. __weak typeof(self) wkSelf = self;
  273. self.backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
  274. if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
  275. [wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
  276. }
  277. };
  278. }
  279. }
  280. #pragma mark - y position update
  281. - (void)snapToYPos:(CGFloat)yPos animated:(BOOL)animated {
  282. if (animated) {
  283. [HWPanModalAnimator animate:^{
  284. self.isPresentedViewAnimating = YES;
  285. [self adjustToYPos:yPos];
  286. } config:self.presentable completion:^(BOOL completion) {
  287. self.isPresentedViewAnimating = NO;
  288. }];
  289. } else {
  290. [self adjustToYPos:yPos];
  291. }
  292. }
  293. - (void)adjustToYPos:(CGFloat)yPos {
  294. self.presentedView.hw_top = MAX(yPos, self.handler.anchoredYPosition);
  295. // change dim background starting from shortFormYPosition.
  296. if (self.presentedView.frame.origin.y >= self.handler.shortFormYPosition) {
  297. CGFloat yDistanceFromShortForm = self.presentedView.frame.origin.y - self.handler.shortFormYPosition;
  298. CGFloat bottomHeight = self.containerView.hw_height - self.handler.shortFormYPosition;
  299. CGFloat percent = yDistanceFromShortForm / bottomHeight;
  300. self.backgroundView.dimState = DimStatePercent;
  301. self.backgroundView.percent = 1 - percent;
  302. [self.presentable panModalGestureRecognizer:self.handler.panGestureRecognizer dismissPercent:MIN(percent, 1)];
  303. if (self.presentedViewController.isBeingDismissed) {
  304. [[self interactiveAnimator] updateInteractiveTransition:MIN(percent, 1)];
  305. }
  306. } else {
  307. self.backgroundView.dimState = DimStateMax;
  308. }
  309. }
  310. #pragma mark - HWPanModalPresentableHandlerDelegate
  311. - (void)adjustPresentableYPos:(CGFloat)yPos {
  312. [self adjustToYPos:yPos];
  313. }
  314. - (void)dismiss:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode {
  315. [self dismiss:isInteractive mode:mode animated:YES completion:nil];
  316. }
  317. - (void)dismiss:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode animated:(BOOL)animated completion:(void (^)(void))completion {
  318. self.presentedViewController.hw_panModalPresentationDelegate.interactive = isInteractive;
  319. self.presentedViewController.hw_panModalPresentationDelegate.interactiveMode = mode;
  320. [self.presentable panModalWillDismiss];
  321. [self.presentedViewController dismissViewControllerAnimated:animated completion:^{
  322. if (completion) completion();
  323. [self.presentable panModalDidDismissed];
  324. }];
  325. }
  326. - (void)dismissAnimated:(BOOL)animated completion:(nonnull void (^)(void))completion {
  327. [self dismiss:NO mode:PanModalInteractiveModeNone animated:animated completion:completion];
  328. }
  329. - (void)presentableTransitionToState:(PresentationState)state {
  330. [self transitionToState:state animated:YES];
  331. }
  332. - (PresentationState)getCurrentPresentationState {
  333. return self.currentPresentationState;
  334. }
  335. #pragma mark - interactive handle
  336. - (void)finishInteractiveTransition {
  337. if (self.presentedViewController.isBeingDismissed) {
  338. // make the containerView can not response event action.
  339. self.containerView.userInteractionEnabled = NO;
  340. [[self interactiveAnimator] finishInteractiveTransition];
  341. if (self.presentedViewController.hw_panModalPresentationDelegate.interactiveMode != PanModalInteractiveModeDragDown)
  342. return;
  343. if ([[self presentable] presentingVCAnimationStyle] > PresentingViewControllerAnimationStyleNone) {
  344. [HWPanModalAnimator animate:^{
  345. [self presentedView].hw_top = self.containerView.frame.size.height;
  346. self.dragIndicatorView.alpha = 0;
  347. self.backgroundView.dimState = DimStateOff;
  348. } config:[self presentable] completion:^(BOOL completion) {
  349. }];
  350. }
  351. }
  352. }
  353. - (void)cancelInteractiveTransition {
  354. if (self.presentedViewController.isBeingDismissed) {
  355. [[self interactiveAnimator] cancelInteractiveTransition];
  356. self.presentedViewController.hw_panModalPresentationDelegate.interactiveMode = PanModalInteractiveModeNone;
  357. self.presentedViewController.hw_panModalPresentationDelegate.interactive = NO;
  358. }
  359. }
  360. #pragma mark - HWPanModalPresentableHandlerDataSource
  361. - (CGSize)containerSize {
  362. return self.containerView.bounds.size;
  363. }
  364. - (BOOL)isBeingDismissed {
  365. return self.presentedViewController.isBeingDismissed;
  366. }
  367. - (BOOL)isBeingPresented {
  368. return self.presentedViewController.isBeingPresented;
  369. }
  370. - (BOOL)isPresentedViewAnchored {
  371. if (![[self presentable] shouldRespondToPanModalGestureRecognizer:self.handler.panGestureRecognizer]) {
  372. return YES;
  373. }
  374. if (!self.isPresentedViewAnimating && self.handler.extendsPanScrolling && (CGRectGetMinY(self.presentedView.frame) <= self.handler.anchoredYPosition || HW_TWO_FLOAT_IS_EQUAL(CGRectGetMinY(self.presentedView.frame), self.handler.anchoredYPosition))) {
  375. return YES;
  376. }
  377. return NO;
  378. }
  379. - (BOOL)isPresentedControllerInteractive {
  380. return self.presentedViewController.hw_panModalPresentationDelegate.interactive;
  381. }
  382. - (BOOL)isFormPositionAnimating {
  383. return self.isPresentedViewAnimating;
  384. }
  385. #pragma mark - Screen Gesture enevt
  386. - (void)screenEdgeInteractiveAction:(UIPanGestureRecognizer *)recognizer {
  387. CGPoint translation = [recognizer translationInView:recognizer.view];
  388. CGFloat percent = translation.x / CGRectGetWidth(recognizer.view.bounds);
  389. CGPoint velocity = [recognizer velocityInView:recognizer.view];
  390. switch (recognizer.state) {
  391. case UIGestureRecognizerStateBegan: {
  392. [self dismiss:YES mode:PanModalInteractiveModeSideslip];
  393. }
  394. break;
  395. case UIGestureRecognizerStateCancelled:
  396. case UIGestureRecognizerStateEnded: {
  397. if (percent > 0.5 || velocity.x >= [[self presentable] minHorizontalVelocityToTriggerScreenEdgeDismiss]) {
  398. [self finishInteractiveTransition];
  399. } else {
  400. [self cancelInteractiveTransition];
  401. }
  402. }
  403. break;
  404. case UIGestureRecognizerStateChanged: {
  405. [[self interactiveAnimator] updateInteractiveTransition:percent];
  406. }
  407. break;
  408. default:
  409. break;
  410. }
  411. }
  412. - (void)checkEdgeInteractive {
  413. //TODO: changed the user interactive, if someone else has different requirements, change it.
  414. self.handler.screenEdgeGestureRecognizer.enabled = [[self presentable] allowScreenEdgeInteractive];
  415. }
  416. #pragma mark - Getter
  417. - (id <HWPanModalPresentable>)presentable {
  418. if ([self.presentedViewController conformsToProtocol:@protocol(HWPanModalPresentable)]) {
  419. return (id <HWPanModalPresentable>) self.presentedViewController;
  420. }
  421. return nil;
  422. }
  423. - (HWPanModalInteractiveAnimator *)interactiveAnimator {
  424. HWPanModalPresentationDelegate *presentationDelegate = self.presentedViewController.hw_panModalPresentationDelegate;
  425. return presentationDelegate.interactiveDismissalAnimator;
  426. }
  427. - (HWDimmedView *)backgroundView {
  428. if (!_backgroundView) {
  429. if (self.presentable) {
  430. _backgroundView = [[HWDimmedView alloc] initWithBackgroundConfig:[self.presentable backgroundConfig]];
  431. } else {
  432. _backgroundView = [[HWDimmedView alloc] init];
  433. }
  434. if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
  435. _backgroundView.userInteractionEnabled = NO;
  436. } else {
  437. __weak typeof(self) wkSelf = self;
  438. _backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
  439. if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
  440. [wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
  441. }
  442. };
  443. }
  444. }
  445. return _backgroundView;
  446. }
  447. - (HWPanContainerView *)panContainerView {
  448. if (!_panContainerView) {
  449. _panContainerView = [[HWPanContainerView alloc] initWithPresentedView:self.presentedViewController.view frame:self.containerView.frame];
  450. }
  451. return _panContainerView;
  452. }
  453. - (UIView<HWPanModalIndicatorProtocol> *)dragIndicatorView {
  454. if (!_dragIndicatorView) {
  455. if ([self presentable] &&
  456. [[self presentable] respondsToSelector:@selector(customIndicatorView)] &&
  457. [[self presentable] customIndicatorView] != nil) {
  458. _dragIndicatorView = [[self presentable] customIndicatorView];
  459. // set the indicator size first in case `setupSubviews` can Not get the right size.
  460. _dragIndicatorView.hw_size = [[[self presentable] customIndicatorView] indicatorSize];
  461. } else {
  462. _dragIndicatorView = [HWPanIndicatorView new];
  463. }
  464. }
  465. return _dragIndicatorView;
  466. }
  467. @end