添加HWPanModal和FLAnimatedImage

This commit is contained in:
2025-11-05 22:04:56 +08:00
parent efdcf60ed1
commit abf32e8457
97 changed files with 10853 additions and 2067 deletions

View File

@@ -0,0 +1,39 @@
//
// HWPanModalContainerView.h
// Pods
//
// Created by heath wang on 2019/10/17.
//
#import <UIKit/UIKit.h>
#import <HWPanModal/HWPanModalPresentable.h>
@class HWPanModalContentView;
@class HWDimmedView;
@class HWPanContainerView;
NS_ASSUME_NONNULL_BEGIN
@interface HWPanModalContainerView : UIView
@property (nonatomic, readonly) HWDimmedView *backgroundView;
@property (readonly) HWPanContainerView *panContainerView;
@property (nonatomic, readonly) PresentationState currentPresentationState;
- (instancetype)initWithPresentingView:(UIView *)presentingView contentView:(HWPanModalContentView<HWPanModalPresentable> *)contentView;
- (void)show;
- (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion;
- (void)setNeedsLayoutUpdate;
- (void)updateUserHitBehavior;
- (void)transitionToState:(PresentationState)state animated:(BOOL)animated;
- (void)setScrollableContentOffset:(CGPoint)offset animated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,546 @@
//
// 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

View File

@@ -0,0 +1,36 @@
//
// HWPanModalContentView.h
// Pods
//
// Created by heath wang on 2019/10/17.
//
#import <UIKit/UIKit.h>
#import <HWPanModal/HWPanModalPresentable.h>
#import <HWPanModal/HWPanModalPresentationUpdateProtocol.h>
#import <HWPanModal/UIViewController+LayoutHelper.h>
#import <HWPanModal/HWPanModalPanGestureDelegate.h>
@class HWPanModalContainerView;
NS_ASSUME_NONNULL_BEGIN
/// when use `HWPanModalContentView`, you should take care of the safe area by yourself.
@interface HWPanModalContentView : UIView <HWPanModalPresentable, HWPanModalPanGestureDelegate, HWPanModalPresentationUpdateProtocol, HWPanModalPresentableLayoutProtocol>
/**
* present in the target view
* @param view The view which present to. If the view is nil, will use UIWindow's keyWindow.
*/
- (void)presentInView:(nullable UIView *)view;
/**
* call this method to dismiss contentView directly.
* @param flag should animate flag
* @param completion dismiss completion block
*/
- (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,414 @@
//
// HWPanModalContentView.m
// Pods
//
// Created by heath wang on 2019/10/17.
//
#import "HWPanModalContentView.h"
#import "HWPanModalContainerView.h"
@interface HWPanModalContentView ()
@property (nonatomic, weak) HWPanModalContainerView *containerView;
@end
@implementation HWPanModalContentView
#pragma mark - public method
- (void)presentInView:(UIView *)view {
if (!view) {
view = [self findKeyWindow];
}
HWPanModalContainerView *containerView = [[HWPanModalContainerView alloc] initWithPresentingView:view contentView:self];
[containerView show];
}
- (void)dismissAnimated:(BOOL)flag completion:(void (^)(void))completion {
[self.containerView dismissAnimated:flag completion:completion];
}
#pragma mark - HWPanModalPresentationUpdateProtocol
- (void)hw_panModalTransitionTo:(PresentationState)state {
[self.containerView transitionToState:state animated:YES];
}
- (void)hw_panModalSetContentOffset:(CGPoint)offset {
[self.containerView setScrollableContentOffset:offset animated:YES];
}
- (void)hw_panModalSetNeedsLayoutUpdate {
[self.containerView setNeedsLayoutUpdate];
}
- (void)hw_panModalUpdateUserHitBehavior {
[self.containerView updateUserHitBehavior];
}
- (void)hw_panModalTransitionTo:(PresentationState)state animated:(BOOL)animated {
[self.containerView transitionToState:state animated:animated];
}
- (void)hw_panModalSetContentOffset:(CGPoint)offset animated:(BOOL)animated {
[self.containerView setScrollableContentOffset:offset animated:animated];
}
- (void)hw_dismissAnimated:(BOOL)animated completion:(void (^)(void))completion {
[self dismissAnimated:animated completion:completion];
}
- (HWDimmedView *)hw_dimmedView {
return self.containerView.backgroundView;
}
- (UIView *)hw_rootContainerView {
return self.containerView;
}
- (UIView *)hw_contentView {
return (UIView *)self.containerView.panContainerView;
}
- (PresentationState)hw_presentationState {
return self.containerView.currentPresentationState;
}
#pragma mark - HWPanModalPresentable
- (UIScrollView *)panScrollable {
return nil;
}
- (CGFloat)topOffset {
return self.topLayoutOffset + 21.f;
}
- (PanModalHeight)shortFormHeight {
return [self longFormHeight];
}
- (PanModalHeight)mediumFormHeight {
return [self longFormHeight];
}
- (PanModalHeight)longFormHeight {
if ([self panScrollable]) {
[[self panScrollable] layoutIfNeeded];
return PanModalHeightMake(PanModalHeightTypeContent, MAX([self panScrollable].contentSize.height, [self panScrollable].bounds.size.height));
} else {
return PanModalHeightMake(PanModalHeightTypeMax, 0);
}
}
- (PresentationState)originPresentationState {
return PresentationStateShort;
}
- (CGFloat)springDamping {
return 0.8;
}
- (NSTimeInterval)transitionDuration {
return 0.5;
}
- (NSTimeInterval)dismissalDuration {
return [self transitionDuration];
}
- (UIViewAnimationOptions)transitionAnimationOptions {
return UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState;
}
- (CGFloat)backgroundAlpha {
return 0.7;
}
- (CGFloat)backgroundBlurRadius {
return 0;
}
- (nonnull UIColor *)backgroundBlurColor {
return [UIColor whiteColor];
}
- (HWBackgroundConfig *)backgroundConfig {
return [HWBackgroundConfig configWithBehavior:HWBackgroundBehaviorDefault];
}
- (UIEdgeInsets)scrollIndicatorInsets {
CGFloat top = [self shouldRoundTopCorners] ? [self cornerRadius] : 0;
return UIEdgeInsetsMake(top, 0, self.bottomLayoutOffset, 0);
}
- (BOOL)showsScrollableVerticalScrollIndicator {
return YES;
}
- (BOOL)shouldAutoSetPanScrollContentInset {
return YES;
}
- (BOOL)anchorModalToLongForm {
return YES;
}
- (BOOL)allowsExtendedPanScrolling {
if ([self panScrollable]) {
UIScrollView *scrollable = [self panScrollable];
[scrollable layoutIfNeeded];
return scrollable.contentSize.height > (scrollable.frame.size.height - self.bottomLayoutOffset);
} else {
return NO;
}
}
- (BOOL)allowsDragToDismiss {
return YES;
}
- (CGFloat)minVerticalVelocityToTriggerDismiss {
return 300;
}
- (BOOL)allowsTapBackgroundToDismiss {
return YES;
}
- (BOOL)allowsPullDownWhenShortState {
return YES;
}
- (BOOL)allowScreenEdgeInteractive {
return NO;
}
- (CGFloat)maxAllowedDistanceToLeftScreenEdgeForPanInteraction {
return 0;
}
- (CGFloat)minHorizontalVelocityToTriggerScreenEdgeDismiss {
return 500;
}
- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle {
return PresentingViewControllerAnimationStyleNone;
}
- (BOOL)shouldAnimatePresentingVC {
return NO;
}
- (id <HWPresentingViewControllerAnimatedTransitioning>)customPresentingVCAnimation {
return nil;
}
- (BOOL)isPanScrollEnabled {
return YES;
}
- (BOOL)isUserInteractionEnabled {
return YES;
}
- (BOOL)isHapticFeedbackEnabled {
return YES;
}
- (BOOL)allowsTouchEventsPassingThroughTransitionView {
return NO;
}
- (BOOL)shouldRoundTopCorners {
return YES;
}
- (CGFloat)cornerRadius {
return 8;
}
- (HWPanModalShadow *)contentShadow {
return [HWPanModalShadow panModalShadowNil];
}
- (BOOL)showDragIndicator {
if ([self allowsTouchEventsPassingThroughTransitionView]) {
return NO;
}
return [self shouldRoundTopCorners];
}
- (nullable UIView <HWPanModalIndicatorProtocol> *)customIndicatorView {
return nil;
}
- (BOOL)isAutoHandleKeyboardEnabled {
return YES;
}
- (CGFloat)keyboardOffsetFromInputView {
return 5;
}
- (BOOL)shouldRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
return YES;
}
- (void)willRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
}
- (void)didRespondToPanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
}
- (void)didEndRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer {
}
- (BOOL)shouldPrioritizePanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
return NO;
}
- (BOOL)shouldTransitionToState:(PresentationState)state {
return YES;
}
- (void)willTransitionToState:(PresentationState)state {
}
- (void)didChangeTransitionToState:(PresentationState)state {
}
- (void)panModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer dismissPercent:(CGFloat)percent {
}
- (void)panModalWillDismiss {
}
- (void)panModalDidDismissed {
}
- (void)panModalTransitionWillBegin {
}
- (void)panModalTransitionDidFinish {
}
- (void)presentedViewDidMoveToSuperView {
}
- (BOOL)shouldEnableAppearanceTransition {
return YES;
}
#pragma mark - HWPanModalPresentableLayoutProtocol
- (CGFloat)topLayoutOffset {
return 0;
}
- (CGFloat)bottomLayoutOffset {
return 0;
}
- (CGFloat)shortFormYPos {
CGFloat shortFormYPos = [self topMarginFromPanModalHeight:[self shortFormHeight]] + [self topOffset];
return MAX(shortFormYPos, self.longFormYPos);
}
- (CGFloat)mediumFormYPos {
CGFloat mediumFormYPos = [self topMarginFromPanModalHeight:[self mediumFormHeight]] + [self topOffset];
return MAX(mediumFormYPos, self.longFormYPos);
}
- (CGFloat)longFormYPos {
CGFloat longFrom = MAX([self topMarginFromPanModalHeight:[self longFormHeight]], [self topMarginFromPanModalHeight:PanModalHeightMake(PanModalHeightTypeMax, 0)]) + [self topOffset];
return longFrom;
}
- (CGFloat)bottomYPos {
if (self.containerView) {
return self.containerView.bounds.size.height - [self topOffset];
}
return self.bounds.size.height;
}
- (CGFloat)topMarginFromPanModalHeight:(PanModalHeight)panModalHeight {
switch (panModalHeight.heightType) {
case PanModalHeightTypeMax:
return 0.0f;
case PanModalHeightTypeMaxTopInset:
return panModalHeight.height;
case PanModalHeightTypeContent:
return self.bottomYPos - (panModalHeight.height + self.bottomLayoutOffset);
case PanModalHeightTypeContentIgnoringSafeArea:
return self.bottomYPos - panModalHeight.height;
case PanModalHeightTypeIntrinsic: {
[self layoutIfNeeded];
CGSize targetSize = CGSizeMake(self.containerView ? self.containerView.bounds.size.width : [UIScreen mainScreen].bounds.size.width, UILayoutFittingCompressedSize.height);
CGFloat intrinsicHeight = [self systemLayoutSizeFittingSize:targetSize].height;
return self.bottomYPos - (intrinsicHeight + self.bottomLayoutOffset);
}
default:
return 0;
}
}
#pragma mark - Getter
- (HWPanModalContainerView *)containerView {
// we assume the container view will not change after we got it.
if (!_containerView) {
UIView *fatherView = self.superview;
while (fatherView) {
if ([fatherView isKindOfClass:HWPanModalContainerView.class]) {
_containerView = (HWPanModalContainerView *) fatherView;
break;
}
fatherView = fatherView.superview;
}
}
return _containerView;
}
- (UIView *)findKeyWindow {
if (@available(iOS 13.0, *)) {
NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in connectedScenes) {
if ([scene isKindOfClass:UIWindowScene.class]) {
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *tmpWindow in windowScene.windows) {
if ([tmpWindow isKeyWindow]) {
return tmpWindow;
}
}
}
}
} else {
NSArray *windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows) {
if ([window isKeyWindow]) {
return window;
}
}
}
return nil;
}
@end