添加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,48 @@
//
// HWPanModalHeight.h
// Pods
//
// Created by heath wang on 2019/4/26.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, PanModalHeightType) {
PanModalHeightTypeMax NS_SWIFT_NAME(max), // from top max
PanModalHeightTypeMaxTopInset NS_SWIFT_NAME(topInset), // from top offset
PanModalHeightTypeContent NS_SWIFT_NAME(content), // from bottom
PanModalHeightTypeContentIgnoringSafeArea NS_SWIFT_NAME(contentIgnoringSafeArea), // from bottom ignore safeArea
PanModalHeightTypeIntrinsic NS_SWIFT_NAME(intrinsic), // auto get size, There is something wrong, DO NOT recommend to use.
};
struct PanModalHeight {
PanModalHeightType heightType NS_SWIFT_NAME(type);
CGFloat height;
};
typedef struct PanModalHeight PanModalHeight;
/**
* When heightType is PanModalHeightTypeMax, PanModalHeightTypeIntrinsic, the height value will be ignored.
*/
CG_INLINE PanModalHeight PanModalHeightMake(PanModalHeightType heightType, CGFloat height) {
PanModalHeight modalHeight;
modalHeight.heightType = heightType;
modalHeight.height = height;
return modalHeight;
}
static inline BOOL HW_FLOAT_IS_ZERO(CGFloat value) {
return (value > -FLT_EPSILON) && (value < FLT_EPSILON);
}
static inline BOOL HW_TWO_FLOAT_IS_EQUAL(CGFloat x, CGFloat y) {
CGFloat minusValue = fabs(x - y);
CGFloat criticalValue = 0.0001;
if (minusValue < criticalValue || minusValue < FLT_MIN) {
return YES;
}
return NO;
}

View File

@@ -0,0 +1,31 @@
//
// HWPanModalPanGestureDelegate.h
// Pods
//
// Created by heath wang on 2022/8/1.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* In this framewok, we use UIPanGestureRecognizer to control user drags behavior.
* The internal logic, there are two panGestureRecognizers delegate will response below delegate: the main panGesture used to control darg down, another panGesture used to control screen edge dismiss.
* Implement this delegate and custom user drag behavior.
* WARNING: BE CAREFUL, AND KNOW WHAT YOU ARE DOING!
*/
@protocol HWPanModalPanGestureDelegate <NSObject>
- (BOOL)hw_gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
- (BOOL)hw_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
- (BOOL)hw_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
- (BOOL)hw_gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,451 @@
//
// HWPanModalPresentable.h
// Pods
//
// Created by heath wang on 2019/4/26.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <HWPanModal/HWPanModalHeight.h>
#import <HWPanModal/HWPresentingVCAnimatedTransitioning.h>
#import <HWPanModal/HWPanModalIndicatorProtocol.h>
#import <HWPanModal/HWBackgroundConfig.h>
#import <HWPanModal/HWPanModalShadow.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, PresentationState) {
PresentationStateShort NS_SWIFT_NAME(short),
PresentationStateMedium NS_SWIFT_NAME(medium),
PresentationStateLong NS_SWIFT_NAME(long),
};
typedef NS_ENUM(NSInteger, PresentingViewControllerAnimationStyle) {
// no animation for presentingVC
PresentingViewControllerAnimationStyleNone NS_SWIFT_NAME(none),
// page sheet animation, like iOS13 default modalPresentation style
PresentingViewControllerAnimationStylePageSheet NS_SWIFT_NAME(pageSheet),
// shopping cart animation, like jd/taobao shopping cart animation
PresentingViewControllerAnimationStyleShoppingCart NS_SWIFT_NAME(shoppingCart),
// make your own custom animation
PresentingViewControllerAnimationStyleCustom NS_SWIFT_NAME(custom),
};
/**
* HWPanModalPresentable为present配置协议
* 默认情况下无需实现只需Controller/View适配该协议
* 通过category来默认实现以下所有方法避免继承类
*
* This Protocol is the core of HWPanModal, we use it to config presentation.
* Default, you don't need to conform all of these methods, just implement what you want to customize.
* All the config has default value, we use a `UIViewController` category to conform `HWPanModalPresentable` protocol.
*/
@protocol HWPanModalPresentable <NSObject>
#pragma mark - ScrollView Config
/**
* 支持同步拖拽的scrollView
* 如果ViewController中包含scrollView并且你想scrollView滑动和拖拽手势同时存在请返回此scrollView
*
* If your ViewController has a scrollable view(UIScrollView and subclass), and you want pan gesture and scrollable both work, return it.
*/
- (nullable UIScrollView *)panScrollable;
/**
* determine ScrollView scrollEnabled
* default is YES
*/
- (BOOL)isPanScrollEnabled;
/**
* scrollView指示器insets
* Use `panModalSetNeedsLayoutUpdate()` when updating insets.
*/
- (UIEdgeInsets)scrollIndicatorInsets;
/**
* A Boolean value that controls whether the scrollable vertical scroll indicator is visible.
* default is YES.
*/
- (BOOL)showsScrollableVerticalScrollIndicator;
/**
* default is YES.
*/
- (BOOL)shouldAutoSetPanScrollContentInset;
/**
* 是否允许拖动额外拖动如果panScrollable存在且scrollView contentSize > (size + bottomLayoutOffset),返回YES
* 其余情况返回NO
*
* If panScrollable exists, and scrollView contentSize > (size + bottomLayoutOffset), auto return YES, otherwise return NO.
* You can make your own logic if you want, and you know what you are doing.
*/
- (BOOL)allowsExtendedPanScrolling;
#pragma mark - Offset/position
/**
* Screen top offset from presented viewController
* Default is topLayoutGuide.length + 21.0.
*/
- (CGFloat)topOffset;
/**
* 当pan状态为short时候的高度
* default: shortFormHeight = longFormHeight
*/
- (PanModalHeight)shortFormHeight;
/**
* default: mediumFormHeight = longFormHeight
*/
- (PanModalHeight)mediumFormHeight;
/**
* 当pan状态为long的高度
*/
- (PanModalHeight)longFormHeight;
/**
* 初始弹出高度状态,默认为`shortFormHeight`
*
* Origin presentation height state, if you have special requirement, change it.
* Default is `shortFormHeight`
*/
- (PresentationState)originPresentationState;
#pragma mark - Animation config
/**
* spring弹性动画数值
* Default is 0.9
*/
- (CGFloat)springDamping;
/**
* 转场动画时间
* Default is 0.5 second
*/
- (NSTimeInterval)transitionDuration;
/**
* starting from version 0.6.5, Only works when dismiss
* Default is same as `- (NSTimeInterval)transitionDuration;`
*/
- (NSTimeInterval)dismissalDuration;
/**
* 转场动画options
* Default is UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
*/
- (UIViewAnimationOptions)transitionAnimationOptions;
#pragma mark - AppearanceTransition
/**
* If enabled, the presenting VC will invoke viewWillAppear:, viewWillDisappear:
* Default is YES
*/
- (BOOL)shouldEnableAppearanceTransition;
#pragma mark - Background config
/**
* use this object to config background alpha or blur effect
* @return background config object
*/
- (HWBackgroundConfig *)backgroundConfig;
#pragma mark - User Interaction
/**
* 该bool值控制当pan View状态为long的情况下是否可以继续拖拽到PanModalHeight = MAX的情况
* 默认为YES,即当已经拖拽到long的情况下不能再继续拖动
*/
- (BOOL)anchorModalToLongForm;
/**
* 是否允许点击背景处dismiss presented Controller
* 默认为YES
*/
- (BOOL)allowsTapBackgroundToDismiss;
/**
* 是否允许drag操作dismiss presented Controller
* Default is YES
*/
- (BOOL)allowsDragToDismiss;
/// Default is YES, When return NO, and you did set shortForm, user CAN NOT pull down the view.
- (BOOL)allowsPullDownWhenShortState;
/**
min Velocity from Vertical direction that trigger dismiss action.
Default is 300.0
*/
- (CGFloat)minVerticalVelocityToTriggerDismiss;
/**
* 是否允许用户操作
* Default is YES
*/
- (BOOL)isUserInteractionEnabled;
/**
* 是否允许触觉反馈
* Default is YES
*/
- (BOOL)isHapticFeedbackEnabled;
/**
* 是否允许触摸事件透传到presenting ViewController/View。如果你有特殊需求的话(比如弹出一个底部视图但是你想操作弹出视图下面的view即presenting VC/View),可开启此功能
*
* Whether allows touch events passing through the transition container view.
* In some situations, you present the bottom VC/View, and you want to operate the presenting VC/View(mapView, scrollView and etc), enable this func.
*
* Note: You SHOULD MUST dismiss the presented VC in the right time.
*/
- (BOOL)allowsTouchEventsPassingThroughTransitionView;
#pragma mark - Screen left egde interaction
/**
* 是否允许屏幕边缘侧滑手势
* Default is NOnot allowed this user interaction.
*
* Note: Currently only works on UIViewController.
*/
- (BOOL)allowScreenEdgeInteractive;
/**
* Max allowed distance to screen left edge when you want to make screen edge pan interaction
* Default is 0, means it will ignore this limit, full screen left edge pan will work.
* @return distance to left screen edge
*/
- (CGFloat)maxAllowedDistanceToLeftScreenEdgeForPanInteraction;
/**
* When you enabled `- (BOOL)allowScreenEdgeInteractive`, this can work.
* min horizontal velocity to trigger screen edge dismiss if the drag didn't reach 0.5 screen width.
* Default is 500
*/
- (CGFloat)minHorizontalVelocityToTriggerScreenEdgeDismiss;
#pragma mark - Customize presentingViewController animation
/**
* Config presentingViewController animation style, this animations will work for present & dismiss.
* Default is `PresentingViewControllerAnimationStyleNone`.
* @return The animation style.
*/
- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle;
/**
* 自定义presenting ViewController转场动画默认为nil
* 注意:如果实现该方法并返回非空示例,要使该方法生效,`- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle`必须返回PresentingViewControllerAnimationStyleCustom
*
* custom presenting ViewController transition animation, default is nil
* Note: If you implement this method and return non nil value, You must implement `- (PresentingViewControllerAnimationStyle)
* presentingVCAnimationStyle` and return PresentingViewControllerAnimationStyleCustom
*/
- (nullable id<HWPresentingViewControllerAnimatedTransitioning>)customPresentingVCAnimation;
#pragma mark - Content UI config
/**
* 是否顶部圆角
* Default is YES
*/
- (BOOL)shouldRoundTopCorners;
/**
* 顶部圆角数值
* Default is 8.0
*/
- (CGFloat)cornerRadius;
/**
* presented content shadow
* Default is None config
*/
- (HWPanModalShadow *)contentShadow;
#pragma mark - Indicator config
/**
* 是否显示drag指示view
* Default is YESDefault this method depend on `- (BOOL)shouldRoundTopCorners`
*/
- (BOOL)showDragIndicator;
/**
* You can make the indicator customized. Just adopt `HWPanModalIndicatorProtocol`
* Default this method return nil, Then the default indicator will be used.
*/
- (__kindof UIView<HWPanModalIndicatorProtocol> * _Nullable)customIndicatorView;
#pragma mark - Keyboard handle
/**
* When there is text input view exists and becomeFirstResponder, will auto handle keyboard height.
* Default is YES. You can disable it, handle it by yourself.
*/
- (BOOL)isAutoHandleKeyboardEnabled;
/**
The offset that keyboard show from input view's bottom. It works when
`isAutoHandleKeyboardEnabled` return YES.
@return offset, default is 5.
*/
- (CGFloat)keyboardOffsetFromInputView;
#pragma mark - Delegate
#pragma mark - Pan Gesture delegate
/**
* 询问delegate是否需要使拖拽手势生效
* 若返回NO则禁用拖拽手势操作即不能拖拽dismiss
* 默认为YES
*/
- (BOOL)shouldRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
/**
* 当pan recognizer状态为begin/changed时通知delegate回调。
* 当拖动presented View时该方法会持续的回调
* 默认实现为空
*/
- (void)willRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
/**
* 内部处理完成拖动操作后触发此回调此时view frame可能已经变化。
* Framework has did finish logic for GestureRecognizer delegate. It will call many times when you darg.
*/
- (void)didRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
/**
* 内部处理完成拖动操作后触发此回调此时view frame可能已经变化。
* Framework has did finish logic for GestureRecognizer delegate. It will call many times when you darg.
*/
- (void)didEndRespondToPanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
/**
* 是否优先执行dismiss拖拽手势当存在panScrollable的情况下如果此方法返回YES
* dismiss手势生效scrollView本身的滑动则不再生效。也就是说可以拖动Controller view而scrollView没法拖动了。
*
* 例子controller view上添加一个TableView并铺满全屏然后在controller view 顶部添加一个一定大小的viewA
* 这个时候会发现viewA有时候无法拖动可以实现此delegate方法来解决
```
- (BOOL)shouldPrioritizePanModalGestureRecognizer:(UIPanGestureRecognizer *)panGestureRecognizer {
CGPoint loc = [panGestureRecognizer locationInView:self.view];
// check whether user pan action in viewA
if (CGRectContainsPoint(self.viewA.frame, loc)) {
return YES;
}
return NO;
}
```
* 默认为NO
*
* This delegate is useful when you want panGestureRecognizer has a high prioritize and
* make scrollable does NOT scroll.
* Example: You controller add a full size tableView, then add viewA on top of your controller view.
* Now you find you can not drag the viewA, use this delegate to resolve problem.
* Please refer to code above this comment.
*
* Default is NO
*/
- (BOOL)shouldPrioritizePanModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer;
/**
* When you pan present controller to dismiss, and the view's y <= shortFormYPos,
* this delegate method will be called.
* @param percent 0 ~ 1, 1 means has dismissed
*/
- (void)panModalGestureRecognizer:(nonnull UIPanGestureRecognizer *)panGestureRecognizer dismissPercent:(CGFloat)percent;
#pragma mark - PresentationState change delegate
/**
* 是否应该变更panModal状态
*/
- (BOOL)shouldTransitionToState:(PresentationState)state;
/**
* called when the Transition State will change.
* 通知回调即将变更状态
*/
- (void)willTransitionToState:(PresentationState)state;
/**
* PresentationState did change callback
*/
- (void)didChangeTransitionToState:(PresentationState)state;
#pragma mark - present delegate
/**
* call when present transition will begin.
*/
- (void)panModalTransitionWillBegin;
/**
* call when present transition did finish.
*/
- (void)panModalTransitionDidFinish;
/**
* call when your custom presented vc has been added to the presentation container.
*/
- (void)presentedViewDidMoveToSuperView;
#pragma mark - Dismiss delegate
/**
* will dismiss
*/
- (void)panModalWillDismiss;
/**
* Did finish dismissing
*/
- (void)panModalDidDismissed;
#pragma mark - DEPRECATED DECLARE
/**
* 是否对presentingViewController做动画效果默认该效果类似淘宝/京东购物车凹陷效果
* 默认为NO
*/
- (BOOL)shouldAnimatePresentingVC DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.3.6, please use `- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle` replaced.");
/**
* 背景透明度
* Default is 0.7
*/
- (CGFloat)backgroundAlpha DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.7.0, please use `- (HWBackgroundConfig *)backgroundConfig` replaced.");
/**
* Blur background
* This function can NOT coexist with backgroundAlpha
* Default use backgroundAlpha, Once you set backgroundBlurRadius > 0, blur will work.
* Recommend set the value 10 ~ 20.
* @return blur radius
*/
- (CGFloat)backgroundBlurRadius DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.7.0, please use `- (HWBackgroundConfig *)backgroundConfig` replaced.");
/**
* blur background color
* @return color, default is White Color.
*/
- (nonnull UIColor *)backgroundBlurColor DEPRECATED_MSG_ATTRIBUTE("This api has been marked as DEPRECATED on version 0.7.0, please use `- (HWBackgroundConfig *)backgroundConfig` replaced.");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,63 @@
//
// HWPanModalPresentationUpdateProtocol.h
// Pods
//
// Created by heath wang on 2019/10/17.
//
#import <HWPanModal/HWPanModalPresentable.h>
@class HWDimmedView;
@protocol HWPanModalPresentationUpdateProtocol <NSObject>
/// background view, you can call `reloadConfig:` to update the UI.
@property (nonatomic, readonly) HWDimmedView *hw_dimmedView;
/// the root container which your custom VC's view to be added.
@property (nonatomic, readonly) UIView *hw_rootContainerView;
/// which view that your presented viewController's view has been added.
@property (nonatomic, readonly) UIView *hw_contentView;
/// current presentation State
@property (nonatomic, readonly) PresentationState hw_presentationState;
/**
* force update pan modal State, short/long
*/
- (void)hw_panModalTransitionTo:(PresentationState)state NS_SWIFT_NAME(panModalTransitionTo(state:));
/**
* force update pan modal State, short/long
* @param state transition state
* @param animated whether animate when set state
*/
- (void)hw_panModalTransitionTo:(PresentationState)state animated:(BOOL)animated NS_SWIFT_NAME(panModalTransitionTo(state:animated:));
/**
* When presented ViewController has a UIScrollView, Use This method to update UIScrollView contentOffset
* Default it has animation
*/
- (void)hw_panModalSetContentOffset:(CGPoint)offset NS_SWIFT_NAME(panModalSetContentOffset(offset:));
/**
* When presented ViewController has a UIScrollView, Use This method to update UIScrollView contentOffset
* @param offset scrollView offset value
* @param animated whether animate
*/
- (void)hw_panModalSetContentOffset:(CGPoint)offset animated:(BOOL)animated NS_SWIFT_NAME(panModalSetContentOffset(offset:animated:));
/**
* Noteif we present a NavigationController, and we want to pan screen edge to dismiss.
* We MUST call this method when we PUSH/POP viewController.
*
*/
- (void)hw_panModalSetNeedsLayoutUpdate NS_SWIFT_NAME(panModalSetNeedsLayoutUpdate());
/**
* When you change the user touch event, like `allowsTouchEventsPassingThroughTransitionView`, you should call this method to make it work.
* 更新用户行为,比如事件穿透
*/
- (void)hw_panModalUpdateUserHitBehavior NS_SWIFT_NAME(panModalUpdateUserHitBehavior());
/**
* call this method to dismiss your presented VC directly
*/
- (void)hw_dismissAnimated:(BOOL)animated completion:(void (^)(void))completion NS_SWIFT_NAME(panModalDismissAnimated(animated:completion:));
@end

View File

@@ -0,0 +1,38 @@
//
// UIViewController+LayoutHelper.h
// HWPanModal
//
// Created by heath wang on 2019/4/26.
//
#import <UIKit/UIKit.h>
@class HWPanModalPresentationController;
NS_ASSUME_NONNULL_BEGIN
@protocol HWPanModalPresentableLayoutProtocol <NSObject>
@property (nonatomic, assign, readonly) CGFloat topLayoutOffset;
@property (nonatomic, assign, readonly) CGFloat bottomLayoutOffset;
@property (nonatomic, assign, readonly) CGFloat shortFormYPos;
@property (nonatomic, assign, readonly) CGFloat mediumFormYPos;
@property (nonatomic, assign, readonly) CGFloat longFormYPos;
@property (nonatomic, assign, readonly) CGFloat bottomYPos;
@end
/**
* Help presentedViewController/presented View to layout.
*/
@interface UIViewController (LayoutHelper) <HWPanModalPresentableLayoutProtocol>
@property (nullable, nonatomic, strong, readonly) HWPanModalPresentationController *hw_presentedVC;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,130 @@
//
// UIViewController+LayoutHelper.m
// HWPanModal
//
// Created by heath wang on 2019/4/26.
//
#import "UIViewController+LayoutHelper.h"
#import "HWPanModalPresentationController.h"
#import "UIViewController+PanModalDefault.h"
@implementation UIViewController (LayoutHelper)
- (CGFloat)topLayoutOffset {
if (@available(iOS 11, *)) {
return [UIApplication sharedApplication].keyWindow.safeAreaInsets.top;
} else {
return [UIApplication sharedApplication].keyWindow.rootViewController.topLayoutGuide.length;
}
}
- (CGFloat)bottomLayoutOffset {
if (@available(iOS 11, *)) {
return [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom;
} else {
return [UIApplication sharedApplication].keyWindow.rootViewController.bottomLayoutGuide.length;
}
}
- (HWPanModalPresentationController *)hw_presentedVC {
/*
* Fix iOS13 bug: if we access presentationController before present VC, this will lead `modalPresentationStyle` not working.
* refer to: https://github.com/HeathWang/HWPanModal/issues/27
* Apple Doc: If you have not yet presented the current view controller, accessing this property creates a presentation controller based on the current value in the modalPresentationStyle property.
*/
/**
* fix bug: https://github.com/HeathWang/HWPanModal/issues/37
*/
if (self.presentingViewController) {
return [self hw_getPanModalPresentationController];
} else {
return nil;
}
}
- (HWPanModalPresentationController *)hw_getPanModalPresentationController {
UIViewController *ancestorsVC;
// seeking for the root presentation VC.
if (self.splitViewController) {
ancestorsVC = self.splitViewController;
} else if (self.navigationController) {
ancestorsVC = self.navigationController;
} else if (self.tabBarController) {
ancestorsVC = self.tabBarController;
} else {
ancestorsVC = self;
}
if ([ancestorsVC.presentationController isMemberOfClass:HWPanModalPresentationController.class]) {
return (HWPanModalPresentationController *) ancestorsVC.presentationController;
}
return nil;
}
/**
Returns the short form Y postion
- Note: If voiceover is on, the `longFormYPos` is returned.
We do not support short form when voiceover is on as it would make it difficult for user to navigate.
*/
- (CGFloat)shortFormYPos {
if (UIAccessibilityIsVoiceOverRunning()) {
return self.longFormYPos;
} else {
CGFloat shortFormYPos = [self topMarginFromPanModalHeight:[self shortFormHeight]] + [self topOffset];
return MAX(shortFormYPos, self.longFormYPos);
}
}
- (CGFloat)mediumFormYPos {
if (UIAccessibilityIsVoiceOverRunning()) {
return self.longFormYPos;
} else {
CGFloat mediumFormYPos = [self topMarginFromPanModalHeight:[self mediumFormHeight]] + [self topOffset];
return MAX(mediumFormYPos, self.longFormYPos);
}
}
- (CGFloat)longFormYPos {
return MAX([self topMarginFromPanModalHeight:[self longFormHeight]], [self topMarginFromPanModalHeight:PanModalHeightMake(PanModalHeightTypeMax, 0)]) + [self topOffset];
}
/**
* Use the container view for relative positioning as this view's frame
is adjusted in PanModalPresentationController
*/
- (CGFloat)bottomYPos {
if (self.hw_presentedVC.containerView) {
return self.hw_presentedVC.containerView.bounds.size.height - [self topOffset];
}
return self.view.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.view layoutIfNeeded];
CGSize targetSize = CGSizeMake(self.hw_presentedVC.containerView ? self.hw_presentedVC.containerView.bounds.size.width : [UIScreen mainScreen].bounds.size.width, UILayoutFittingCompressedSize.height);
CGFloat intrinsicHeight = [self.view systemLayoutSizeFittingSize:targetSize].height;
return self.bottomYPos - (intrinsicHeight + self.bottomLayoutOffset);
}
default:
return 0;
}
}
@end

View File

@@ -0,0 +1,18 @@
//
// UIViewController+PanModalDefault.h
// HWPanModal
//
// Created by heath wang on 2019/4/26.
//
#import <UIKit/UIKit.h>
#import <HWPanModal/HWPanModalPresentable.h>
#import <HWPanModal/HWPanModalPanGestureDelegate.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (PanModalDefault) <HWPanModalPresentable, HWPanModalPanGestureDelegate>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,253 @@
//
// UIViewController+PanModalDefault.m
// HWPanModal
//
// Created by heath wang on 2019/4/26.
//
#import "UIViewController+PanModalDefault.h"
#import "UIViewController+LayoutHelper.h"
@implementation UIViewController (PanModalDefault)
- (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];
/*
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window.
*/
if (!scrollable.superview || !scrollable.window) return NO;
[scrollable layoutIfNeeded];
return scrollable.contentSize.height > (scrollable.frame.size.height - self.bottomLayoutOffset);
}
return NO;
}
- (BOOL)allowsDragToDismiss {
return YES;
}
- (CGFloat)minVerticalVelocityToTriggerDismiss {
return 300;
}
- (BOOL)allowsTapBackgroundToDismiss {
return YES;
}
- (BOOL)allowScreenEdgeInteractive {
return NO;
}
- (CGFloat)maxAllowedDistanceToLeftScreenEdgeForPanInteraction {
return 0;
}
- (CGFloat)minHorizontalVelocityToTriggerScreenEdgeDismiss {
return 500;
}
- (PresentingViewControllerAnimationStyle)presentingVCAnimationStyle {
return PresentingViewControllerAnimationStyleNone;
}
- (BOOL)shouldEnableAppearanceTransition {
return YES;
}
- (BOOL)shouldAnimatePresentingVC {
return NO;
}
- (id <HWPresentingViewControllerAnimatedTransitioning>)customPresentingVCAnimation {
return nil;
}
- (BOOL)isPanScrollEnabled {
return YES;
}
- (BOOL)isUserInteractionEnabled {
return YES;
}
- (BOOL)allowsPullDownWhenShortState {
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 YES;
}
- (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 {
}
@end

View File

@@ -0,0 +1,21 @@
//
// UIViewController+Presentation.h
// HWPanModal
//
// Created by heath wang on 2019/4/29.
//
#import <UIKit/UIKit.h>
#import <HWPanModal/HWPanModalPresentable.h>
#import <HWPanModal/HWPanModalPresentationUpdateProtocol.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The presented Controller can use the category to update UIPresentationController container.
*/
@interface UIViewController (Presentation) <HWPanModalPresentationUpdateProtocol>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,70 @@
//
// UIViewController+Presentation.m
// HWPanModal
//
// Created by heath wang on 2019/4/29.
//
#import "UIViewController+Presentation.h"
#import "UIViewController+LayoutHelper.h"
#import "HWPanModalPresentationController.h"
@interface UIViewController ()
@end
@implementation UIViewController (Presentation)
- (void)hw_panModalTransitionTo:(PresentationState)state {
if (!self.hw_presentedVC) return;
[self.hw_presentedVC transitionToState:state animated:YES];
}
- (void)hw_panModalTransitionTo:(PresentationState)state animated:(BOOL)animated {
if (!self.hw_presentedVC) return;
[self.hw_presentedVC transitionToState:state animated:animated];
}
- (void)hw_panModalSetContentOffset:(CGPoint)offset animated:(BOOL)animated {
if (!self.hw_presentedVC) return;
[self.hw_presentedVC setScrollableContentOffset:offset animated:animated];
}
- (void)hw_panModalSetContentOffset:(CGPoint)offset {
if (!self.hw_presentedVC) return;
[self.hw_presentedVC setScrollableContentOffset:offset animated:YES];
}
- (void)hw_panModalSetNeedsLayoutUpdate {
if (!self.hw_presentedVC) return;
[self.hw_presentedVC setNeedsLayoutUpdate];
}
- (void)hw_panModalUpdateUserHitBehavior {
if (!self.hw_presentedVC) return;
[self.hw_presentedVC updateUserHitBehavior];
}
- (void)hw_dismissAnimated:(BOOL)animated completion:(void (^)(void))completion{
if (!self.hw_presentedVC) return;
[self.hw_presentedVC dismissAnimated:animated completion:completion];
}
- (HWDimmedView *)hw_dimmedView {
return self.hw_presentedVC.backgroundView;
}
- (UIView *)hw_rootContainerView {
return self.hw_presentedVC.containerView;
}
- (UIView *)hw_contentView {
return self.hw_presentedVC.presentedView;
}
- (PresentationState)hw_presentationState {
return self.hw_presentedVC.currentPresentationState;
}
@end