123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- //
- // HWPanModalContainerView.m
- // Pods
- //
- // Created by heath wang on 2019/10/17.
- //
- #import "HWPanModalContainerView.h"
- #import "HWPanModalContentView.h"
- #import "HWPanModalPresentableHandler.h"
- #import "HWDimmedView.h"
- #import "HWPanContainerView.h"
- #import "UIView+HW_Frame.h"
- #import "HWPanIndicatorView.h"
- #import "HWPanModalAnimator.h"
- @interface HWPanModalContainerView () <HWPanModalPresentableHandlerDelegate, HWPanModalPresentableHandlerDataSource>
- @property (nonatomic, strong) HWPanModalContentView<HWPanModalPresentable> *contentView;
- @property (nonatomic, weak) UIView *presentingView;
- @property (nonatomic, strong) HWPanModalPresentableHandler *handler;
- // 判断弹出的view是否在做动画
- @property (nonatomic, assign) BOOL isPresentedViewAnimating;
- @property (nonatomic, assign) PresentationState currentPresentationState;
- @property (nonatomic, assign) BOOL isPresenting;
- @property (nonatomic, assign) BOOL isDismissing;
- // view
- @property (nonatomic, strong) HWDimmedView *backgroundView;
- @property (nonatomic, strong) HWPanContainerView *panContainerView;
- @property (nonatomic, strong) UIView<HWPanModalIndicatorProtocol> *dragIndicatorView;
- @property (nonatomic, copy) void(^animationBlock)(void);
- @property (nullable, nonatomic, strong) UISelectionFeedbackGenerator *feedbackGenerator API_AVAILABLE(ios(10.0));
- @end
- @implementation HWPanModalContainerView
- - (instancetype)initWithPresentingView:(UIView *)presentingView contentView:(HWPanModalContentView<HWPanModalPresentable> *)contentView {
- self = [super init];
- if (self) {
- _presentingView = presentingView;
- _contentView = contentView;
- }
- return self;
- }
- - (void)show {
- [self prepare];
- [self presentAnimationWillBegin];
- [self beginPresentAnimation];
- }
- - (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion {
- if (flag) {
- self.animationBlock = completion;
- [self dismiss:NO mode:PanModalInteractiveModeNone];
- } else {
- self.isDismissing = YES;
- [[self presentable] panModalWillDismiss];
- [self removeFromSuperview];
- [[self presentable] panModalDidDismissed];
- completion ? completion() : nil;
- self.isDismissing = NO;
- }
- }
- - (void)prepare {
- [self.presentingView addSubview:self];
- self.frame = self.presentingView.bounds;
- _handler = [[HWPanModalPresentableHandler alloc] initWithPresentable:self.contentView];
- _handler.delegate = self;
- _handler.dataSource = self;
- if (@available(iOS 10.0, *)) {
- _feedbackGenerator = [UISelectionFeedbackGenerator new];
- [_feedbackGenerator prepare];
- } else {
- // Fallback on earlier versions
- }
- }
- - (void)willMoveToSuperview:(UIView *)newSuperview {
- [super willMoveToSuperview:newSuperview];
- if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
- [self.superview removeObserver:self forKeyPath:@"frame"];
- [newSuperview addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
- }
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
- if (object == self.presentingView && [keyPath isEqualToString:@"frame"]) {
- self.frame = self.presentingView.bounds;
- [self setNeedsLayoutUpdate];
- [self updateDragIndicatorViewFrame];
- [self.contentView hw_panModalTransitionTo:self.contentView.hw_presentationState animated:NO];
- } else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
- }
- - (void)presentAnimationWillBegin {
- [[self presentable] panModalTransitionWillBegin];
- [self layoutBackgroundView];
- if ([[self presentable] originPresentationState] == PresentationStateLong) {
- self.currentPresentationState = PresentationStateLong;
- } else if ([[self presentable] originPresentationState] == PresentationStateMedium) {
- self.currentPresentationState = PresentationStateMedium;
- }
-
- [self addSubview:self.panContainerView];
- [self layoutPresentedView];
-
- [self.handler configureScrollViewInsets];
- [[self presentable] presentedViewDidMoveToSuperView];
- }
- - (void)beginPresentAnimation {
- self.isPresenting = YES;
- CGFloat yPos = self.contentView.shortFormYPos;
- if ([[self presentable] originPresentationState] == PresentationStateLong) {
- yPos = self.contentView.longFormYPos;
- } else if ([[self presentable] originPresentationState] == PresentationStateMedium) {
- yPos = self.contentView.mediumFormYPos;
- }
-
- // refresh layout
- [self configureViewLayout];
- [self adjustPresentedViewFrame];
- self.panContainerView.hw_top = self.hw_height;
-
- if ([[self presentable] isHapticFeedbackEnabled]) {
- if (@available(iOS 10.0, *)) {
- [self.feedbackGenerator selectionChanged];
- }
- }
-
- [HWPanModalAnimator animate:^{
- self.panContainerView.hw_top = yPos;
- self.backgroundView.dimState = DimStateMax;
- } config:[self presentable] completion:^(BOOL completion) {
- self.isPresenting = NO;
- [[self presentable] panModalTransitionDidFinish];
-
- if (@available(iOS 10.0, *)) {
- self.feedbackGenerator = nil;
- }
- }];
-
- }
- - (void)layoutSubviews {
- [super layoutSubviews];
- [self configureViewLayout];
- }
- #pragma mark - public method
- - (void)setNeedsLayoutUpdate {
- [self configureViewLayout];
- [self updateBackgroundColor];
- [self.handler observeScrollable];
- [self adjustPresentedViewFrame];
- [self.handler configureScrollViewInsets];
- [self updateContainerViewShadow];
- [self updateDragIndicatorView];
- [self updateRoundedCorners];
- }
- - (void)updateUserHitBehavior {
- [self checkBackgroundViewEventPass];
- [self checkPanGestureRecognizer];
- }
- - (void)transitionToState:(PresentationState)state animated:(BOOL)animated {
- if (![self.presentable shouldTransitionToState:state]) return;
- [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
- [self.presentable willTransitionToState:state];
- switch (state) {
- case PresentationStateLong: {
- [self snapToYPos:self.handler.longFormYPosition animated:animated];
- }
- break;
- case PresentationStateMedium: {
- [self snapToYPos:self.handler.mediumFormYPosition animated:animated];
- }
- break;
- case PresentationStateShort: {
- [self snapToYPos:self.handler.shortFormYPosition animated:animated];
- }
- break;
- default:
- break;
- }
- self.currentPresentationState = state;
- [[self presentable] didChangeTransitionToState:state];
- }
- - (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated {
- [self.handler setScrollableContentOffset:offset animated:animated];
- }
- #pragma mark - layout
- - (void)adjustPresentedViewFrame {
- CGRect frame = self.frame;
- CGSize size = CGSizeMake(CGRectGetWidth(frame), CGRectGetHeight(frame) - self.handler.anchoredYPosition);
-
- self.panContainerView.hw_size = frame.size;
- self.panContainerView.contentView.frame = CGRectMake(0, 0, size.width, size.height);
- self.contentView.frame = self.panContainerView.contentView.bounds;
- [self.contentView setNeedsLayout];
- [self.contentView layoutIfNeeded];
- }
- - (void)configureViewLayout {
- [self.handler configureViewLayout];
- self.userInteractionEnabled = [[self presentable] isUserInteractionEnabled];
- }
- - (void)layoutBackgroundView {
- [self addSubview:self.backgroundView];
- [self updateBackgroundColor];
- self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
- NSArray *hCons = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
- NSArray *vCons = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[backgroundView]|" options:0 metrics:nil views:@{@"backgroundView": self.backgroundView}];
- [NSLayoutConstraint activateConstraints:hCons];
- [NSLayoutConstraint activateConstraints:vCons];
- }
- - (void)updateBackgroundColor {
- self.backgroundView.blurTintColor = [self.presentable backgroundConfig].blurTintColor;
- }
- - (void)layoutPresentedView {
- if (!self.presentable)
- return;
- self.handler.presentedView = self.panContainerView;
-
- if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
- [self.panContainerView addGestureRecognizer:self.handler.panGestureRecognizer];
- } else {
- [self addGestureRecognizer:self.handler.panGestureRecognizer];
- }
- [self setNeedsLayoutUpdate];
- [self adjustPanContainerBackgroundColor];
- }
- - (void)adjustPanContainerBackgroundColor {
- self.panContainerView.contentView.backgroundColor = self.contentView.backgroundColor ? : [self.presentable panScrollable].backgroundColor;
- }
- - (void)updateDragIndicatorView {
- if ([self.presentable showDragIndicator]) {
- [self addDragIndicatorView];
- } else {
- self.dragIndicatorView.hidden = YES;
- }
- }
- - (void)addDragIndicatorView {
- // if has been add, won't update it.
- self.dragIndicatorView.hidden = NO;
- if (self.dragIndicatorView.superview == self.panContainerView) {
- [self updateDragIndicatorViewFrame];
- [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
- return;
- }
- self.handler.dragIndicatorView = self.dragIndicatorView;
- [self.panContainerView addSubview:self.dragIndicatorView];
- [self updateDragIndicatorViewFrame];
- [self.dragIndicatorView setupSubviews];
- [self.dragIndicatorView didChangeToState:HWIndicatorStateNormal];
- }
- - (void)updateDragIndicatorViewFrame {
- CGSize indicatorSize = [self.dragIndicatorView indicatorSize];
- self.dragIndicatorView.frame = CGRectMake((self.panContainerView.hw_width - indicatorSize.width) / 2, -kIndicatorYOffset - indicatorSize.height, indicatorSize.width, indicatorSize.height);
- }
- - (void)updateContainerViewShadow {
- HWPanModalShadow *shadow = [[self presentable] contentShadow];
- if (shadow.shadowColor) {
- [self.panContainerView updateShadow:shadow.shadowColor shadowRadius:shadow.shadowRadius shadowOffset:shadow.shadowOffset shadowOpacity:shadow.shadowOpacity];
- } else {
- [self.panContainerView clearShadow];
- }
- }
- - (void)updateRoundedCorners {
- if ([self.presentable shouldRoundTopCorners]) {
- [self addRoundedCornersToView:self.panContainerView.contentView];
- } else {
- [self resetRoundedCornersToView:self.panContainerView.contentView];
- }
- }
- - (void)addRoundedCornersToView:(UIView *)view {
- CGFloat radius = [self.presentable cornerRadius];
- UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerTopRight | UIRectCornerTopLeft cornerRadii:CGSizeMake(radius, radius)];
- CAShapeLayer *mask = [CAShapeLayer new];
- mask.path = bezierPath.CGPath;
- view.layer.mask = mask;
- // 提高性能
- view.layer.shouldRasterize = YES;
- view.layer.rasterizationScale = [UIScreen mainScreen].scale;
- }
- - (void)resetRoundedCornersToView:(UIView *)view {
- view.layer.mask = nil;
- view.layer.shouldRasterize = NO;
- }
- - (void)snapToYPos:(CGFloat)yPos animated:(BOOL)animated {
- if (animated) {
- [HWPanModalAnimator animate:^{
- self.isPresentedViewAnimating = YES;
- [self adjustToYPos:yPos];
- } config:self.presentable completion:^(BOOL completion) {
- self.isPresentedViewAnimating = NO;
- }];
- } else {
- [self adjustToYPos:yPos];
- }
- }
- - (void)adjustToYPos:(CGFloat)yPos {
- self.panContainerView.hw_top = MAX(yPos, self.handler.anchoredYPosition);
- // change dim background starting from shortFormYPosition.
- if (self.panContainerView.frame.origin.y >= self.handler.shortFormYPosition) {
- CGFloat yDistanceFromShortForm = self.panContainerView.frame.origin.y - self.handler.shortFormYPosition;
- CGFloat bottomHeight = self.hw_height - self.handler.shortFormYPosition;
- CGFloat percent = yDistanceFromShortForm / bottomHeight;
- self.backgroundView.dimState = DimStatePercent;
- self.backgroundView.percent = 1 - percent;
- [self.presentable panModalGestureRecognizer:self.handler.panGestureRecognizer dismissPercent:MIN(percent, 1)];
- } else {
- self.backgroundView.dimState = DimStateMax;
- }
- }
- #pragma mark - HWPanModalPresentableHandlerDelegate
- - (void)adjustPresentableYPos:(CGFloat)yPos {
- [self adjustToYPos:yPos];
- }
- - (void)presentableTransitionToState:(PresentationState)state {
- [self transitionToState:state animated:YES];
- }
- - (PresentationState)getCurrentPresentationState {
- return self.currentPresentationState;
- }
- - (void)dismiss:(BOOL)isInteractive mode:(PanModalInteractiveMode)mode {
- self.handler.panGestureRecognizer.enabled = NO;
- self.isDismissing = YES;
- [[self presentable] panModalWillDismiss];
- [HWPanModalAnimator animate:^{
- self.panContainerView.hw_top = CGRectGetHeight(self.bounds);
- self.backgroundView.dimState = DimStateOff;
- self.dragIndicatorView.alpha = 0;
- } config:[self presentable] completion:^(BOOL completion) {
- [self removeFromSuperview];
- [[self presentable] panModalDidDismissed];
- self.animationBlock ? self.animationBlock() : nil;
- self.isDismissing = NO;
- }];
- }
- #pragma mark - HWPanModalPresentableHandlerDataSource
- - (CGSize)containerSize {
- return self.presentingView.bounds.size;
- }
- - (BOOL)isBeingDismissed {
- return self.isDismissing;
- }
- - (BOOL)isBeingPresented {
- return self.isPresenting;
- }
-
- - (BOOL)isFormPositionAnimating {
- return self.isPresentedViewAnimating;
- }
- - (BOOL)isPresentedViewAnchored {
-
- if (![[self presentable] shouldRespondToPanModalGestureRecognizer:self.handler.panGestureRecognizer]) {
- return YES;
- }
-
- if (!self.isPresentedViewAnimating && self.handler.extendsPanScrolling && (CGRectGetMinY(self.panContainerView.frame) <= self.handler.anchoredYPosition || HW_TWO_FLOAT_IS_EQUAL(CGRectGetMinY(self.panContainerView.frame), self.handler.anchoredYPosition))) {
- return YES;
- }
- return NO;
- }
- #pragma mark - event handle
- - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
-
- if (!self.userInteractionEnabled || self.hidden || self.alpha < 0.01) {
- return nil;
- }
- if (![self pointInside:point withEvent:event]) {
- return nil;
- }
-
- BOOL eventThrough = [[self presentable] allowsTouchEventsPassingThroughTransitionView];
- if (eventThrough) {
- CGPoint convertedPoint = [self.panContainerView convertPoint:point fromView:self];
- if (CGRectGetWidth(self.panContainerView.frame) >= convertedPoint.x &&
- convertedPoint.x > 0 &&
- CGRectGetHeight(self.panContainerView.frame) >= convertedPoint.y &&
- convertedPoint.y > 0) {
- return [super hitTest:point withEvent:event];
- } else {
- return nil;
- }
- } else {
- return [super hitTest:point withEvent:event];
- }
- }
- - (void)checkBackgroundViewEventPass {
- if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
- self.backgroundView.userInteractionEnabled = NO;
- self.backgroundView.tapBlock = nil;
- } else {
- self.backgroundView.userInteractionEnabled = YES;
- __weak typeof(self) wkSelf = self;
- self.backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
- if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
- [wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
- }
- };
- }
- }
- - (void)checkPanGestureRecognizer {
- if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
- [self removeGestureRecognizer:self.handler.panGestureRecognizer];
- [self.panContainerView addGestureRecognizer:self.handler.panGestureRecognizer];
- } else {
- [self.panContainerView removeGestureRecognizer:self.handler.panGestureRecognizer];
- [self addGestureRecognizer:self.handler.panGestureRecognizer];
- }
- }
- #pragma mark - getter
- - (id<HWPanModalPresentable>)presentable {
- if ([self.contentView conformsToProtocol:@protocol(HWPanModalPresentable)]) {
- return self.contentView;
- }
- return nil;
- }
- - (HWDimmedView *)backgroundView {
- if (!_backgroundView) {
- if (self.presentable) {
- _backgroundView = [[HWDimmedView alloc] initWithBackgroundConfig:[self.presentable backgroundConfig]];
- } else {
- _backgroundView = [[HWDimmedView alloc] init];
- }
-
- if ([[self presentable] allowsTouchEventsPassingThroughTransitionView]) {
- _backgroundView.userInteractionEnabled = NO;
- } else {
- __weak typeof(self) wkSelf = self;
- _backgroundView.tapBlock = ^(UITapGestureRecognizer *recognizer) {
- if ([[wkSelf presentable] allowsTapBackgroundToDismiss]) {
- [wkSelf dismiss:NO mode:PanModalInteractiveModeNone];
- }
- };
- }
- }
- return _backgroundView;
- }
- - (HWPanContainerView *)panContainerView {
- if (!_panContainerView) {
- _panContainerView = [[HWPanContainerView alloc] initWithPresentedView:self.contentView frame:self.bounds];
- }
- return _panContainerView;
- }
- - (UIView<HWPanModalIndicatorProtocol> *)dragIndicatorView {
- if (!_dragIndicatorView) {
- if ([self presentable] &&
- [[self presentable] respondsToSelector:@selector(customIndicatorView)] &&
- [[self presentable] customIndicatorView] != nil) {
- _dragIndicatorView = [[self presentable] customIndicatorView];
- // set the indicator size first in case `setupSubviews` can Not get the right size.
- _dragIndicatorView.hw_size = [[[self presentable] customIndicatorView] indicatorSize];
- } else {
- _dragIndicatorView = [HWPanIndicatorView new];
- }
- }
- return _dragIndicatorView;
- }
- @end
|