This commit is contained in:
启星
2025-08-11 10:43:19 +08:00
commit fb2c58d96f
8839 changed files with 709982 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
//
// HWDefaultPopAnimator.h
// HWPopController
//
// Created by heath wang on 2019/5/22.
//
#import <Foundation/Foundation.h>
#import <HWPopController/HWPopControllerAnimationProtocol.h>
#import <HWPopController/HWPopController.h>
NS_ASSUME_NONNULL_BEGIN
@interface HWDefaultPopAnimator : NSObject <HWPopControllerAnimationProtocol>
@property (nonatomic, assign) HWPopType popType;
@property (nonatomic, assign) HWDismissType dismissType;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,301 @@
//
// HWDefaultPopAnimator.m
// HWPopController
//
// Created by heath wang on 2019/5/22.
//
#import "HWDefaultPopAnimator.h"
#import "HWPopControllerAnimatedTransitioning.h"
static const CGFloat kDefaultSpringDamping = 0.8;
static const CGFloat kDefaultSpringVelocity = 10.0;
@implementation HWDefaultPopAnimator
- (NSTimeInterval)popControllerAnimationDuration:(HWPopAnimationContext *)context {
return context.duration ?: 0.2;
}
- (void)popAnimate:(HWPopAnimationContext *)context completion:(void (^)(BOOL finished))completion {
NSTimeInterval duration = [self popControllerAnimationDuration:context];
UIView *containerView = context.containerView;
switch (self.popType) {
case HWPopTypeFadeIn:{
containerView.transform = CGAffineTransformIdentity;
containerView.alpha = 0;
[UIView animateWithDuration:duration animations:^{
containerView.alpha = 1;
} completion:completion];
}
break;
case HWPopTypeGrowIn:{
containerView.transform = CGAffineTransformMakeScale(0.9, 0.9);
containerView.alpha = 0;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.transform = CGAffineTransformIdentity;
containerView.alpha = 1;
} completion:completion];
}
break;
case HWPopTypeShrinkIn:{
containerView.alpha = 0;
containerView.transform = CGAffineTransformMakeScale(1.1, 1.1);
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
} completion:completion];
}
break;
case HWPopTypeSlideInFromTop:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.y = -CGRectGetHeight(originFrame) - 20;
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
case HWPopTypeSlideInFromBottom:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.y = containerView.superview.frame.size.height;
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
case HWPopTypeSlideInFromLeft:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.x = -CGRectGetWidth(rect);
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
case HWPopTypeSlideInFromRight:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.x = CGRectGetWidth(containerView.superview.frame);
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
case HWPopTypeBounceIn:{
containerView.transform = CGAffineTransformIdentity;
containerView.alpha = 0;
containerView.transform = CGAffineTransformMakeScale(0.1, 0.1);
[UIView animateWithDuration:duration delay:0 usingSpringWithDamping:kDefaultSpringDamping initialSpringVelocity:kDefaultSpringVelocity options:0 animations:^{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
} completion:completion];
}
break;
case HWPopTypeBounceInFromTop:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.y = -CGRectGetHeight(originFrame);
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 usingSpringWithDamping:kDefaultSpringDamping initialSpringVelocity:kDefaultSpringVelocity options:0 animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
case HWPopTypeBounceInFromBottom:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.y = CGRectGetHeight(containerView.superview.frame);
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 usingSpringWithDamping:kDefaultSpringDamping initialSpringVelocity:kDefaultSpringVelocity options:0 animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
case HWPopTypeBounceInFromLeft:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.x = -CGRectGetWidth(rect);
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 usingSpringWithDamping:kDefaultSpringDamping initialSpringVelocity:kDefaultSpringVelocity options:0 animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
case HWPopTypeBounceInFromRight:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
CGRect originFrame = containerView.frame;
CGRect rect = containerView.frame;
rect.origin.x = CGRectGetWidth(containerView.superview.frame);
containerView.frame = rect;
[UIView animateWithDuration:duration delay:0 usingSpringWithDamping:kDefaultSpringDamping initialSpringVelocity:kDefaultSpringVelocity options:0 animations:^{
containerView.frame = originFrame;
} completion:completion];
}
break;
default:{
containerView.alpha = 1;
containerView.transform = CGAffineTransformIdentity;
completion ? completion(YES) : nil;
}
break;
}
}
- (void)dismissAnimate:(HWPopAnimationContext *)context completion:(void (^)(BOOL finished))completion {
NSTimeInterval duration = [self popControllerAnimationDuration:context];
NSTimeInterval bounceDuration1 = duration * 1.f / 3.f;
NSTimeInterval bounceDuration2 = duration * 2.f / 3.f;
UIView *containerView = context.containerView;
switch (self.dismissType) {
case HWDismissTypeFadeOut:{
containerView.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:duration animations:^{
containerView.alpha = 0;
} completion:completion];
}
break;
case HWDismissTypeGrowOut:{
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.transform = CGAffineTransformMakeScale(1.1, 1.1);
containerView.alpha = 0;
} completion:completion];
}
break;
case HWDismissTypeShrinkOut:{
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.alpha = 0;
containerView.transform = CGAffineTransformMakeScale(0.85, 0.85);
} completion:completion];
}
break;
case HWDismissTypeSlideOutToTop:{
CGRect rect = containerView.frame;
rect.origin.y = -CGRectGetHeight(rect);
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = rect;
} completion:completion];
}
break;
case HWDismissTypeSlideOutToBottom:{
CGRect rect = containerView.frame;
rect.origin.y = containerView.superview.frame.size.height;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = rect;
} completion:completion];
}
break;
case HWDismissTypeSlideOutToLeft:{
CGRect rect = containerView.frame;
rect.origin.x = -CGRectGetWidth(rect);
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = rect;
} completion:completion];
}
break;
case HWDismissTypeSlideOutToRight:{
CGRect rect = containerView.frame;
rect.origin.x = CGRectGetWidth(containerView.superview.frame);
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
containerView.frame = rect;
} completion:completion];
}
break;
case HWDismissTypeBounceOut:{
[UIView animateWithDuration:bounceDuration1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
containerView.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
[UIView animateWithDuration:bounceDuration2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
containerView.alpha = 0;
containerView.transform = CGAffineTransformMakeScale(0.1, 0.1);
} completion:completion];
}];
}
break;
case HWDismissTypeBounceOutToTop:{
CGRect rect1 = containerView.frame;
rect1.origin.y += 20;
CGRect rect2 = containerView.frame;
rect2.origin.y = -CGRectGetHeight(rect2);
[UIView animateWithDuration:bounceDuration1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
containerView.frame = rect1;
} completion:^(BOOL finished) {
[UIView animateWithDuration:bounceDuration2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
containerView.frame = rect2;
} completion:completion];
}];
}
break;
case HWDismissTypeBounceOutToBottom:{
CGRect rect1 = containerView.frame;
rect1.origin.y -= 20;
CGRect rect2 = containerView.frame;
rect2.origin.y = CGRectGetHeight(containerView.superview.frame);
[UIView animateWithDuration:bounceDuration1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
containerView.frame = rect1;
} completion:^(BOOL finished) {
[UIView animateWithDuration:bounceDuration2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
containerView.frame = rect2;
} completion:completion];
}];
}
break;
case HWDismissTypeBounceOutToLeft:{
CGRect rect1 = containerView.frame;
rect1.origin.x += 20;
CGRect rect2 = containerView.frame;
rect2.origin.x = -CGRectGetWidth(rect2);
[UIView animateWithDuration:bounceDuration1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
containerView.frame = rect1;
} completion:^(BOOL finished) {
[UIView animateWithDuration:bounceDuration2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
containerView.frame = rect2;
} completion:completion];
}];
}
break;
case HWDismissTypeBounceOutToRight:{
CGRect rect1 = containerView.frame;
rect1.origin.x -= 20;
CGRect rect2 = containerView.frame;
rect2.origin.x = CGRectGetWidth(containerView.superview.frame);
[UIView animateWithDuration:bounceDuration1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
containerView.frame = rect1;
} completion:^(BOOL finished) {
[UIView animateWithDuration:bounceDuration2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
containerView.frame = rect2;
} completion:completion];
}];
}
break;
default:{
containerView.alpha = 0;
containerView.transform = CGAffineTransformIdentity;
completion ? completion(YES) : nil;
}
break;
}
}
@end

View File

@@ -0,0 +1,29 @@
//
// HWNavAnimatedTransitioning.h
// HWPopController
//
// Created by heath wang on 2019/6/10.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <HWPopController/HWPopController.h>
@class HWNavAnimatedTransitioning;
NS_ASSUME_NONNULL_BEGIN
@interface HWNavAnimatedTransitioning : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) HWPopState state;
- (instancetype)initWithState:(HWPopState)state NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,54 @@
//
// HWNavAnimatedTransitioning.m
// HWPopController
//
// Created by heath wang on 2019/6/10.
//
#import "HWNavAnimatedTransitioning.h"
@implementation HWNavAnimatedTransitioning
- (instancetype)initWithState:(HWPopState)state {
self = [super init];
if (self) {
_state = state;
}
return self;
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
return 0.15;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
if (self.state == HWPopStatePop) {
CGRect f = [transitionContext finalFrameForViewController:toVC];
toVC.view.frame = f;
[transitionContext.containerView insertSubview:toVC.view aboveSubview:fromVC.view];
} else {
[transitionContext.containerView insertSubview:toVC.view belowSubview:fromVC.view];
}
fromVC.view.alpha = 1;
toVC.view.alpha = 0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
fromVC.view.alpha = 0;
toVC.view.alpha = 1;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
fromVC.view.alpha = 1;
}];
}
@end

View File

@@ -0,0 +1,35 @@
//
// HWPopControllerAnimationContext.h
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import <Foundation/Foundation.h>
#import <HWPopController/HWPopController.h>
NS_ASSUME_NONNULL_BEGIN
@interface HWPopAnimationContext : NSObject
@property (nonatomic, assign, readonly) HWPopState state;
@property (nonatomic, strong, readonly) UIView *containerView;
@property (nonatomic, assign) NSTimeInterval duration;
- (instancetype)initWithState:(HWPopState)state containerView:(UIView *)containerView;
@end
@interface HWPopControllerAnimatedTransitioning : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign, readonly) HWPopState state;
@property (nonatomic, weak, readonly) HWPopController *popController;
- (instancetype)initWithState:(HWPopState)state popController:(HWPopController *)popController NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,166 @@
//
// HWPopControllerAnimationContext.m
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import "HWPopControllerAnimatedTransitioning.h"
#import "HWDefaultPopAnimator.h"
@implementation HWPopAnimationContext
- (instancetype)initWithState:(HWPopState)state containerView:(UIView *)containerView {
self = [super init];
if (self) {
_state = state;
_containerView = containerView;
}
return self;
}
@end
@interface HWPopControllerAnimatedTransitioning ()
@property (nonatomic, strong) id<HWPopControllerAnimationProtocol> animator;
@end
@interface HWPopControllerAnimatedTransitioning ()
@property (nonatomic, strong, readonly) UIView *containerView;
@property (nonatomic, strong, readonly) UIView *backgroundView;
@property (nonatomic, strong) HWPopAnimationContext *animationContext;
@end
@implementation HWPopControllerAnimatedTransitioning
- (instancetype)initWithState:(HWPopState)state popController:(HWPopController *)popController {
self = [super init];
if (self) {
_state = state;
_popController = popController;
_containerView = _popController.containerView;
_backgroundView = _popController.backgroundView;
_animationContext = [[HWPopAnimationContext alloc] initWithState:state containerView:_containerView];
_animationContext.duration = _popController.animationDuration;
[self getAnimator];
}
return self;
}
- (void)getAnimator {
if (self.popController.animationProtocol) {
self.animator = self.popController.animationProtocol;
} else {
HWDefaultPopAnimator *defaultPopAnimator = [HWDefaultPopAnimator new];
defaultPopAnimator.popType = self.popController.popType;
defaultPopAnimator.dismissType = self.popController.dismissType;
self.animator = defaultPopAnimator;
}
}
#pragma mark - Animation
- (void)popAnimateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
toViewController.view.frame = fromViewController.view.frame;
UIViewController *topViewController = self.popController.topViewController;
[fromViewController beginAppearanceTransition:NO animated:YES];
[[transitionContext containerView] addSubview:toViewController.view];
[topViewController beginAppearanceTransition:YES animated:YES];
[toViewController addChildViewController:topViewController];
[self.popController.contentView addSubview:topViewController.view];
[self.popController layoutContainerView];
CGFloat lastBackgroundViewAlpha = self.backgroundView.alpha;
self.backgroundView.alpha = 0;
[self setContainerUserInteractionEnabled:NO];
self.containerView.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:[self.animator popControllerAnimationDuration:self.animationContext] delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.backgroundView.alpha = lastBackgroundViewAlpha;
} completion:nil];
[self.animator popAnimate:self.animationContext completion:^(BOOL finished){
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
[self setContainerUserInteractionEnabled:YES];
[topViewController endAppearanceTransition];
[topViewController didMoveToParentViewController:toViewController];
[fromViewController endAppearanceTransition];
[toViewController setNeedsStatusBarAppearanceUpdate];
}];
}
- (void)dismissTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
toViewController.view.frame = fromViewController.view.frame;
UIViewController *topViewController = self.popController.topViewController;
[toViewController beginAppearanceTransition:YES animated:YES];
[topViewController beginAppearanceTransition:NO animated:YES];
[topViewController willMoveToParentViewController:nil];
CGFloat lastBackgroundViewAlpha = self.backgroundView.alpha;
[self setContainerUserInteractionEnabled:NO];
[UIView animateWithDuration:[self.animator popControllerAnimationDuration:self.animationContext] delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.backgroundView.alpha = 0;
} completion:nil];
[self.animator dismissAnimate:self.animationContext completion:^(BOOL finished){
[self setContainerUserInteractionEnabled:YES];
self.backgroundView.alpha = lastBackgroundViewAlpha;
[fromViewController.view removeFromSuperview];
[topViewController.view removeFromSuperview];
[topViewController removeFromParentViewController];
[toViewController endAppearanceTransition];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
- (void)setContainerUserInteractionEnabled:(BOOL)enabled {
self.containerView.userInteractionEnabled = enabled;
self.backgroundView.userInteractionEnabled = enabled;
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
return [self.animator popControllerAnimationDuration:self.animationContext];
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
switch (self.state) {
case HWPopStatePop: {
[self popAnimateTransition:transitionContext];
}
break;
case HWPopStateDismiss:{
[self dismissTransition:transitionContext];
}
break;
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// HWPopControllerAnimationProtocol.h
// HWPopController
//
// Created by heath wang on 2019/5/22.
//
#ifndef HWPopControllerAnimationProtocol_h
#define HWPopControllerAnimationProtocol_h
NS_ASSUME_NONNULL_BEGIN
@class HWPopAnimationContext;
@protocol HWPopControllerAnimationProtocol <NSObject>
- (NSTimeInterval)popControllerAnimationDuration:(HWPopAnimationContext *)context;
- (void)popAnimate:(HWPopAnimationContext *)context completion:(void (^)(BOOL finished))completion;
- (void)dismissAnimate:(HWPopAnimationContext *)context completion:(void (^)(BOOL finished))completion;
@end
NS_ASSUME_NONNULL_END
#endif /* HWPopControllerAnimationProtocol_h */

View File

@@ -0,0 +1,19 @@
//
// NSObject+HWAdd.h
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (HWAdd)
+ (BOOL)hw_swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel;
+ (BOOL)hw_swizzleClassMethod:(SEL)originalSel with:(SEL)newSel;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,41 @@
//
// NSObject+HWAdd.m
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import "NSObject+HWAdd.h"
#import <objc/runtime.h>
@implementation NSObject (HWAdd)
+ (BOOL)hw_swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel {
Method originalMethod = class_getInstanceMethod(self, originalSel);
Method newMethod = class_getInstanceMethod(self, newSel);
if (!originalMethod || !newMethod) return NO;
class_addMethod(self,
originalSel,
class_getMethodImplementation(self, originalSel),
method_getTypeEncoding(originalMethod));
class_addMethod(self,
newSel,
class_getMethodImplementation(self, newSel),
method_getTypeEncoding(newMethod));
method_exchangeImplementations(class_getInstanceMethod(self, originalSel),
class_getInstanceMethod(self, newSel));
return YES;
}
+ (BOOL)hw_swizzleClassMethod:(SEL)originalSel with:(SEL)newSel {
Class class = object_getClass(self);
Method originalMethod = class_getInstanceMethod(class, originalSel);
Method newMethod = class_getInstanceMethod(class, newSel);
if (!originalMethod || !newMethod) return NO;
method_exchangeImplementations(originalMethod, newMethod);
return YES;
}
@end

View File

@@ -0,0 +1,59 @@
//
// UIViewController+HWPopController.h
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import <UIKit/UIKit.h>
#import <HWPopController/HWPopController.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (HWPopController)
/**
* pop size for portrait orientation.
*/
@property (nonatomic, assign) IBInspectable CGSize contentSizeInPop;
/**
* pop size for landscape orientation
*/
@property (nonatomic, assign) IBInspectable CGSize contentSizeInPopWhenLandscape;
/**
* The pop ViewController referred HWPopController
*/
@property (nullable, nonatomic, weak, readonly) HWPopController *popController;
@end
@interface UIViewController (HWPop)
/**
* The controller which will be pop call this method to popup.
* Use default param.
* @return HWPopController
*/
- (HWPopController *)popup;
- (HWPopController *)popupWithPopType:(HWPopType)popType
dismissType:(HWDismissType)dismissType;
- (HWPopController *)popupWithPopType:(HWPopType)popType
dismissType:(HWDismissType)dismissType
position:(HWPopPosition)popPosition;
- (HWPopController *)popupWithPopType:(HWPopType)popType
dismissType:(HWDismissType)dismissType
position:(HWPopPosition)popPosition
dismissOnBackgroundTouch:(BOOL)shouldDismissOnBackgroundTouch;
- (HWPopController *)popupWithPopType:(HWPopType)popType
dismissType:(HWDismissType)dismissType
position:(HWPopPosition)popPosition
inViewController:(UIViewController *)inViewController
dismissOnBackgroundTouch:(BOOL)shouldDismissOnBackgroundTouch;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,192 @@
//
// UIViewController+HWPopController.m
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import "UIViewController+HWPopController.h"
#import <objc/runtime.h>
#import "NSObject+HWAdd.h"
@implementation UIViewController (HWPopController)
@dynamic contentSizeInPop;
@dynamic contentSizeInPopWhenLandscape;
@dynamic popController;
static inline BOOL HW_FLOAT_VALUE_IS_ZERO(CGFloat value) {
return (value > -FLT_EPSILON) && (value < FLT_EPSILON);
}
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self hw_swizzleInstanceMethod:@selector(viewDidLoad) with:@selector(hw_viewDidLoad)];
[self hw_swizzleInstanceMethod:@selector(presentViewController:animated:completion:) with:@selector(hw_presentViewController:animated:completion:)];
[self hw_swizzleInstanceMethod:@selector(dismissViewControllerAnimated:completion:) with:@selector(hw_dismissViewControllerAnimated:completion:)];
});
}
- (void)hw_viewDidLoad {
[self hw_viewDidLoad];
CGSize contentSize;
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:{
contentSize = self.contentSizeInPopWhenLandscape;
if (CGSizeEqualToSize(contentSize, CGSizeZero)) {
contentSize = self.contentSizeInPop;
}
}
break;
default:{
contentSize = self.contentSizeInPop;
}
break;
}
if (!CGSizeEqualToSize(contentSize, CGSizeZero)) {
self.view.frame = CGRectMake(0, 0, contentSize.width, contentSize.height);
}
}
- (void)hw_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
if (!self.popController) {
[self hw_presentViewController:viewControllerToPresent animated:flag completion:completion];
return;
}
[[self.popController valueForKey:@"containerViewController"] hw_presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- (void)hw_dismissViewControllerAnimated:(BOOL)flag completion:(void (^ __nullable)(void))completion {
if (!self.popController) {
[self hw_dismissViewControllerAnimated:flag completion:completion];
return;
}
[self.popController dismissWithCompletion:completion];
}
#pragma mark - props
- (CGSize)contentSizeInPop {
NSValue *value = objc_getAssociatedObject(self, _cmd);
return [value CGSizeValue];
}
- (void)setContentSizeInPop:(CGSize)contentSizeInPop {
if (!CGSizeEqualToSize(contentSizeInPop, CGSizeZero) && HW_FLOAT_VALUE_IS_ZERO(contentSizeInPop.width)) {
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:{
contentSizeInPop.width = [UIScreen mainScreen].bounds.size.height;
}
break;
default: {
contentSizeInPop.width = [UIScreen mainScreen].bounds.size.width;
}
break;
}
}
objc_setAssociatedObject(self, @selector(contentSizeInPop), [NSValue valueWithCGSize:contentSizeInPop], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CGSize)contentSizeInPopWhenLandscape {
NSValue *value = objc_getAssociatedObject(self, _cmd);
return [value CGSizeValue];
}
- (void)setContentSizeInPopWhenLandscape:(CGSize)contentSizeInPopWhenLandscape {
if (!CGSizeEqualToSize(contentSizeInPopWhenLandscape, CGSizeZero) && HW_FLOAT_VALUE_IS_ZERO(contentSizeInPopWhenLandscape.width)) {
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:{
contentSizeInPopWhenLandscape.width = [UIScreen mainScreen].bounds.size.width;
}
break;
default: {
contentSizeInPopWhenLandscape.width = [UIScreen mainScreen].bounds.size.height;
}
break;
}
}
objc_setAssociatedObject(self, @selector(contentSizeInPopWhenLandscape), [NSValue valueWithCGSize:contentSizeInPopWhenLandscape], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (HWPopController *)popController {
HWPopController *popController = objc_getAssociatedObject(self, _cmd);
return popController;
}
- (void)setPopController:(HWPopController *)popController {
objc_setAssociatedObject(self, @selector(popController), popController, OBJC_ASSOCIATION_ASSIGN);
}
@end
UIViewController *HWGetTopMostViewController() {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
UIViewController *topVC = keyWindow.rootViewController;
while (topVC.presentedViewController) {
topVC = topVC.presentedViewController;
}
if ([topVC isKindOfClass:[UINavigationController class]]) {
topVC = ((UINavigationController *) topVC).topViewController;
}
if ([topVC isKindOfClass:[UITabBarController class]]) {
topVC = ((UITabBarController *) topVC).selectedViewController;
}
return topVC;
}
@implementation UIViewController (HWPop)
- (HWPopController *)popup {
return [self popupWithPopType:HWPopTypeGrowIn dismissType:HWDismissTypeFadeOut position:HWPopPositionCenter inViewController:HWGetTopMostViewController() dismissOnBackgroundTouch:YES];
}
- (HWPopController *)popupWithPopType:(HWPopType)popType dismissType:(HWDismissType)dismissType{
return [self popupWithPopType:popType dismissType:dismissType position:HWPopPositionCenter];
}
- (HWPopController *)popupWithPopType:(HWPopType)popType
dismissType:(HWDismissType)dismissType
position:(HWPopPosition)popPosition {
return [self popupWithPopType:popType dismissType:dismissType position:popPosition inViewController:HWGetTopMostViewController() dismissOnBackgroundTouch:YES];
}
- (HWPopController *)popupWithPopType:(HWPopType)popType
dismissType:(HWDismissType)dismissType
position:(HWPopPosition)popPosition
dismissOnBackgroundTouch:(BOOL)shouldDismissOnBackgroundTouch {
return [self popupWithPopType:popType dismissType:dismissType position:popPosition inViewController:HWGetTopMostViewController() dismissOnBackgroundTouch:shouldDismissOnBackgroundTouch];
}
- (HWPopController *)popupWithPopType:(HWPopType)popType
dismissType:(HWDismissType)dismissType
position:(HWPopPosition)popPosition
inViewController:(UIViewController *)inViewController
dismissOnBackgroundTouch:(BOOL)shouldDismissOnBackgroundTouch {
HWPopController *popController = [[HWPopController alloc] initWithViewController:self];
popController.popType = popType;
popController.dismissType = dismissType;
popController.popPosition = popPosition;
popController.shouldDismissOnBackgroundTouch = shouldDismissOnBackgroundTouch;
[popController presentInViewController:inViewController];
return popController;
}
@end

View File

@@ -0,0 +1,164 @@
//
// HWPopController.h
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import <Foundation/Foundation.h>
#import <HWPopController/HWPopControllerAnimationProtocol.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, HWPopPosition) {
HWPopPositionCenter,
HWPopPositionTop,
HWPopPositionBottom,
};
typedef NS_ENUM(NSInteger, HWPopState) {
HWPopStatePop, // present
HWPopStateDismiss, // dismiss
};
typedef NS_ENUM(NSInteger, HWPopType) {
HWPopTypeNone,
HWPopTypeFadeIn,
HWPopTypeGrowIn,
HWPopTypeShrinkIn,
HWPopTypeSlideInFromTop,
HWPopTypeSlideInFromBottom,
HWPopTypeSlideInFromLeft,
HWPopTypeSlideInFromRight,
HWPopTypeBounceIn,
HWPopTypeBounceInFromTop,
HWPopTypeBounceInFromBottom,
HWPopTypeBounceInFromLeft,
HWPopTypeBounceInFromRight,
};
typedef NS_ENUM(NSInteger, HWDismissType) {
HWDismissTypeNone,
HWDismissTypeFadeOut,
HWDismissTypeGrowOut,
HWDismissTypeShrinkOut,
HWDismissTypeSlideOutToTop,
HWDismissTypeSlideOutToBottom,
HWDismissTypeSlideOutToLeft,
HWDismissTypeSlideOutToRight,
HWDismissTypeBounceOut,
HWDismissTypeBounceOutToTop,
HWDismissTypeBounceOutToBottom,
HWDismissTypeBounceOutToLeft,
HWDismissTypeBounceOutToRight,
};
@interface HWPopController : NSObject
#pragma mark - config properties
/**
//////////////////////////////////////////////////////
Below props should be set when you pop the controller.
//////////////////////////////////////////////////////
*/
/**
* pop animation style
* default is HWPopTypeGrowIn
*/
@property (nonatomic, assign) HWPopType popType;
/**
* dismiss animation style
* default is HWDismissTypeFadeOut
*/
@property (nonatomic, assign) HWDismissType dismissType;
/**
* animation duration
* default is 0.2 s
*/
@property (nonatomic, assign) NSTimeInterval animationDuration;
/**
* The pop view final position.
* Default is HWPopPositionCenter
*/
@property (nonatomic, assign) HWPopPosition popPosition;
/**
* The offset of the pop view.
*/
@property (nonatomic, assign) CGPoint positionOffset;
/**
* You can custom your own animation for pop and dismiss.
* once you set this property, and NOT nil,
* the `popType` and `dismissType` will be ignore.
*/
@property (nonatomic, weak) id<HWPopControllerAnimationProtocol> animationProtocol;
@property (nonatomic, assign) UIEdgeInsets safeAreaInsets;
/**
//////////////////////////////////////////////////////
Below props can be set when you need.
//////////////////////////////////////////////////////
*/
/**
* The background when popup. You can set it as `UIImageView`, `UIVisualEffectView` such as.
*/
@property (nullable, nonatomic, strong) UIView *backgroundView;
/**
* pop background alpha.
* default is 0.5
*/
@property (nonatomic, assign) CGFloat backgroundAlpha;
/**
* determine touch background to dismiss
* Default is YES.
*/
@property (nonatomic, assign) BOOL shouldDismissOnBackgroundTouch;
/// Default is YES
@property (nonatomic, assign) BOOL shouldAutoHandleKeyboardEvent;
#pragma mark - readonly properties
/**
* Hold the pop view container.
* Default the backgroundColor is White.
* Default the corner radius is 8.0f.
* If you want to custom corner, change containerView layer.
*/
@property (nonatomic, strong, readonly) UIView *containerView;
/**
* Which view the popped ViewController view added.
*/
@property (nonatomic, strong, readonly) UIView *contentView;
/**
* topViewController is the viewController which is presented.
*/
@property (nonatomic, strong, readonly) UIViewController *topViewController;
@property (nonatomic, assign, readonly) BOOL presented;
/**
* init PopController
* @param presentedViewController the controller which will be presented
*/
- (instancetype)initWithViewController:(UIViewController *)presentedViewController;
/**
* pop controller
* @param presentingViewController which controller to present.
*/
- (void)presentInViewController:(UIViewController *)presentingViewController;
- (void)presentInViewController:(UIViewController *)presentingViewController completion:(nullable void (^)(void))completion;
- (void)dismiss;
- (void)dismissWithCompletion:(nullable void (^)(void))completion;
- (void)layoutContainerView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,432 @@
//
// HWPopController.m
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import "HWPopController.h"
#import "UIViewController+HWPopController.h"
#import "HWPopTransitioningDelegate.h"
static NSMutableSet *_retainedPopControllers;
@interface UIViewController (Internal)
@property (nonatomic, weak) HWPopController *popController;
@end
@interface HWPopContainerViewController : UIViewController
@end
@implementation HWPopContainerViewController
@end
@interface HWPopController ()
@property (nonatomic, strong) HWPopContainerViewController *containerViewController;
@property (nonatomic, strong) UIViewController *topViewController;
@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, assign) BOOL didOverrideSafeAreaInsets;
@property (nonatomic, assign) BOOL isObserving;
@property (nonatomic, copy) NSDictionary *keyboardInfo;
@property (nonatomic, strong) HWPopTransitioningDelegate *transitioningDelegate;
@end
@implementation HWPopController
#pragma mark - init
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_retainedPopControllers = [NSMutableSet set];
});
}
- (instancetype)init {
self = [super init];
if (self) {
[self setup];
}
return self;
}
#pragma mark - public method
- (instancetype)initWithViewController:(UIViewController *)viewController {
self = [self init];
if (self) {
self.topViewController = viewController;
// set popController to the popped viewController
viewController.popController = self;
[self setupObserverForViewController:viewController];
}
return self;
}
- (void)presentInViewController:(UIViewController *)presentingViewController {
[self presentInViewController:presentingViewController completion:nil];
}
- (void)presentInViewController:(UIViewController *)presentingViewController completion:(nullable void (^)(void))completion {
if (self.presented)
return;
dispatch_async(dispatch_get_main_queue(), ^{
[self setupObserver];
[_retainedPopControllers addObject:self];
UIViewController *VC = presentingViewController.tabBarController ?: presentingViewController;
if (@available(iOS 11.0, *)) {
if (!self.didOverrideSafeAreaInsets) {
self.safeAreaInsets = presentingViewController.view.safeAreaInsets;
}
}
[VC presentViewController:self.containerViewController animated:YES completion:completion];
});
}
- (void)dismiss {
[self dismissWithCompletion:nil];
}
- (void)dismissWithCompletion:(nullable void (^)(void))completion {
if (!self.presented)
return;
dispatch_async(dispatch_get_main_queue(), ^{
[self destroyObserver];
[self.containerViewController dismissViewControllerAnimated:YES completion:^{
[_retainedPopControllers removeObject:self];
completion ? completion() : nil;
}];
});
}
#pragma mark - observe
- (void)setupObserverForViewController:(UIViewController *)viewController {
[viewController addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeInPop)) options:NSKeyValueObservingOptionNew context:nil];
[viewController addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeInPopWhenLandscape)) options:NSKeyValueObservingOptionNew context:nil];
}
- (void)setupObserver {
if (self.isObserving)
return;
// Observe orientation change
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
if (self.shouldAutoHandleKeyboardEvent) {
// Observe keyboard
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
self.isObserving = YES;
}
- (void)destroyObserver {
if (!self.isObserving)
return;
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.isObserving = NO;
}
- (void)destroyObserverOfViewController:(UIViewController *)viewController {
[viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeInPop))];
[viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeInPopWhenLandscape))];
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context {
if (object == self.topViewController) {
if (self.topViewController.isViewLoaded && self.topViewController.view.superview) {
[UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
[self layoutContainerView];
} completion:^(BOOL finished) {
[self adjustContainerViewOrigin];
}];
}
}
}
#pragma mark - UIApplicationDidChangeStatusBarOrientationNotification
- (void)orientationDidChange {
[self.containerView endEditing:YES];
[UIView animateWithDuration:0.25 animations:^{
[self layoutContainerView];
} completion:^(BOOL finished) {
}];
}
#pragma mark - keyboard handle
- (void)adjustContainerViewOrigin {
if (!self.keyboardInfo)
return;
UIView <UIKeyInput> *currentTextInput = [self getCurrentTextInputInView:self.containerView];
if (!currentTextInput) {
return;
}
CGAffineTransform lastTransform = self.containerView.transform;
self.containerView.transform = CGAffineTransformIdentity;
CGFloat textFieldBottomY = [currentTextInput convertPoint:CGPointZero toView:self.containerViewController.view].y + currentTextInput.bounds.size.height;
CGFloat keyboardHeight = [self.keyboardInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
// For iOS 7
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1 &&
(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight)) {
keyboardHeight = [self.keyboardInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.width;
}
CGFloat offsetY = 0;
if (self.popPosition == HWPopPositionBottom) {
offsetY = keyboardHeight - _safeAreaInsets.bottom;
} else {
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
if (self.containerView.bounds.size.height <= self.containerViewController.view.bounds.size.height - keyboardHeight - statusBarHeight) {
offsetY = self.containerView.frame.origin.y - (statusBarHeight + (self.containerViewController.view.bounds.size.height - keyboardHeight - statusBarHeight - self.containerView.bounds.size.height) / 2);
} else {
CGFloat spacing = 5;
offsetY = self.containerView.frame.origin.y + self.containerView.bounds.size.height - (self.containerViewController.view.bounds.size.height - keyboardHeight - spacing);
if (offsetY <= 0) { // self.containerView can be totally shown, so no need to translate the origin
return;
}
if (self.containerView.frame.origin.y - offsetY < statusBarHeight) { // self.containerView will be covered by status bar if the origin is translated by "offsetY"
offsetY = self.containerView.frame.origin.y - statusBarHeight;
// currentTextField can not be totally shown if self.containerView is going to repositioned with "offsetY"
if (textFieldBottomY - offsetY > self.containerViewController.view.bounds.size.height - keyboardHeight - spacing) {
offsetY = textFieldBottomY - (self.containerViewController.view.bounds.size.height - keyboardHeight - spacing);
}
}
}
}
NSTimeInterval duration = [self.keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [self.keyboardInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
self.containerView.transform = lastTransform; // Restore transform
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:curve];
[UIView setAnimationDuration:duration];
self.containerView.transform = CGAffineTransformMakeTranslation(0, -offsetY);
[UIView commitAnimations];
}
- (void)keyboardWillShow:(NSNotification *)notification {
if (!self.shouldAutoHandleKeyboardEvent) return;
UIView <UIKeyInput> *currentTextInput = [self getCurrentTextInputInView:self.containerView];
if (!currentTextInput) {
return;
}
self.keyboardInfo = notification.userInfo;
[self adjustContainerViewOrigin];
}
- (void)keyboardWillHide:(NSNotification *)notification {
if (!self.shouldAutoHandleKeyboardEvent) return;
self.keyboardInfo = nil;
NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:curve];
[UIView setAnimationDuration:duration];
self.containerView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
- (UIView <UIKeyInput> *)getCurrentTextInputInView:(UIView *)view {
if ([view conformsToProtocol:@protocol(UIKeyInput)] && view.isFirstResponder) {
// Quick fix for web view issue
if ([view isKindOfClass:NSClassFromString(@"UIWebBrowserView")] || [view isKindOfClass:NSClassFromString(@"WKContentView")]) {
return nil;
}
return (UIView <UIKeyInput> *) view;
}
for (UIView *subview in view.subviews) {
UIView <UIKeyInput> *inputInView = [self getCurrentTextInputInView:subview];
if (inputInView) {
return inputInView;
}
}
return nil;
}
#pragma mark - touch event
- (void)didTapBackgroundView {
if (self.shouldDismissOnBackgroundTouch) {
[self dismiss];
}
}
#pragma mark - UI Layout
- (void)layoutContainerView {
CGAffineTransform lastTransform = self.containerView.transform;
self.containerView.transform = CGAffineTransformIdentity;
self.backgroundView.frame = self.containerViewController.view.bounds;
CGSize contentSizeOfTopView = [self contentSizeOfTopView];
CGFloat containerViewWidth = contentSizeOfTopView.width;
CGFloat containerViewHeight = contentSizeOfTopView.height;
CGFloat containerViewY;
switch (self.popPosition) {
case HWPopPositionBottom:{
containerViewHeight += _safeAreaInsets.bottom;
containerViewY = self.containerViewController.view.bounds.size.height - containerViewHeight;
}
break;
case HWPopPositionTop:{
containerViewY = 0;
}
break;
default:{
containerViewY = (self.containerViewController.view.bounds.size.height - containerViewHeight) / 2;
}
break;
}
containerViewY += self.positionOffset.y;
CGFloat containerViewX = (self.containerViewController.view.bounds.size.width - containerViewWidth) / 2 + self.positionOffset.x;
self.containerView.frame = CGRectMake(containerViewX, containerViewY, containerViewWidth, containerViewHeight);
self.contentView.frame = CGRectMake(0, 0, contentSizeOfTopView.width, contentSizeOfTopView.height);
UIViewController *topViewController = self.topViewController;
topViewController.view.frame = self.contentView.bounds;
self.containerView.transform = lastTransform;
}
- (CGSize)contentSizeOfTopView {
UIViewController *topViewController = self.topViewController;
CGSize contentSize;
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight: {
contentSize = topViewController.contentSizeInPopWhenLandscape;
if (CGSizeEqualToSize(contentSize, CGSizeZero)) {
contentSize = topViewController.contentSizeInPop;
}
}
break;
default: {
contentSize = topViewController.contentSizeInPop;
}
break;
}
NSAssert(!CGSizeEqualToSize(contentSize, CGSizeZero), @"contentSizeInPopup should not be size zero.");
return contentSize;
}
#pragma mark - UI prepare
- (void)setup {
self.shouldDismissOnBackgroundTouch = YES;
self.shouldAutoHandleKeyboardEvent = YES;
self.animationDuration = 0.2;
self.popType = HWPopTypeGrowIn;
self.dismissType = HWDismissTypeFadeOut;
[self.containerViewController.view addSubview:self.containerView];
[self.containerView addSubview:self.contentView];
UIView *bgView = [UIView new];
self.backgroundView = bgView;
self.backgroundAlpha = 0.5;
}
#pragma mark - Setter
- (void)setSafeAreaInsets:(UIEdgeInsets)safeAreaInsets {
_safeAreaInsets = safeAreaInsets;
self.didOverrideSafeAreaInsets = YES;
}
- (void)setBackgroundView:(UIView *)backgroundView {
[_backgroundView removeFromSuperview];
_backgroundView = backgroundView;
[_backgroundView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapBackgroundView)]];
[self.containerViewController.view insertSubview:_backgroundView atIndex:0];
}
- (void)setBackgroundAlpha:(CGFloat)backgroundAlpha {
_backgroundAlpha = backgroundAlpha;
self.backgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:backgroundAlpha];
}
#pragma mark - Getter
- (UIView *)containerView {
if (!_containerView) {
_containerView = [UIView new];
_containerView.backgroundColor = [UIColor whiteColor];
_containerView.clipsToBounds = YES;
_containerView.layer.cornerRadius = 8;
}
return _containerView;
}
- (HWPopContainerViewController *)containerViewController {
if (!_containerViewController) {
_containerViewController = [HWPopContainerViewController new];
_containerViewController.modalPresentationStyle = UIModalPresentationCustom;
self.transitioningDelegate = [[HWPopTransitioningDelegate alloc] initWithPopController:self];
_containerViewController.transitioningDelegate = self.transitioningDelegate;
}
return _containerViewController;
}
- (UIView *)contentView {
if (!_contentView) {
_contentView = [UIView new];
}
return _contentView;
}
- (BOOL)presented {
return self.containerViewController.presentingViewController != nil;
}
- (void)dealloc {
[self destroyObserver];
[self destroyObserverOfViewController:self.topViewController];
}
@end

View File

@@ -0,0 +1,20 @@
//
// HWPopNavigationController.h
// HWPopController
//
// Created by heath wang on 2019/6/10.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* If you want to pop UINavigationController & dynamic change the pop contentSize:
* You should subclass HWPopNavigationController, then use it like a normally UIViewController
*/
@interface HWPopNavigationController : UINavigationController
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,83 @@
//
// HWPopNavigationController.m
// HWPopController
//
// Created by heath wang on 2019/6/10.
//
#import "HWPopNavigationController.h"
#import "HWNavAnimatedTransitioning.h"
#import "UIViewController+HWPopController.h"
@interface HWPopNavigationController () <UINavigationControllerDelegate>
@property (nonatomic, strong) HWNavAnimatedTransitioning *animatedTransitioning;
@property (nonatomic, assign) CGSize originContentSizeInPop;
@property (nonatomic, assign) CGSize originContentSizeInPopWhenLandscape;
@end
@implementation HWPopNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.originContentSizeInPop = self.contentSizeInPop;
self.originContentSizeInPopWhenLandscape = self.contentSizeInPopWhenLandscape;
}
#pragma mark - overwrite
- (void)adjustContentSizeBy:(UIViewController *)controller {
switch ([UIApplication sharedApplication].statusBarOrientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight: {
CGSize contentSize = controller.contentSizeInPopWhenLandscape;
if (!CGSizeEqualToSize(contentSize, CGSizeZero)) {
self.contentSizeInPopWhenLandscape = contentSize;
} else {
self.contentSizeInPopWhenLandscape = self.originContentSizeInPopWhenLandscape;
}
}
break;
default: {
CGSize contentSize = controller.contentSizeInPop;
if (!CGSizeEqualToSize(contentSize, CGSizeZero)) {
self.contentSizeInPop = contentSize;
} else {
self.contentSizeInPop = self.originContentSizeInPop;
}
}
break;
}
}
#pragma mark - UINavigationControllerDelegate
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
// call this to perform viewDidLoad
[toVC view];
[self adjustContentSizeBy:toVC];
self.animatedTransitioning.state = operation == UINavigationControllerOperationPush ? HWPopStatePop : HWPopStateDismiss;
return self.animatedTransitioning;
}
#pragma mark - Getter
- (HWNavAnimatedTransitioning *)animatedTransitioning {
if (!_animatedTransitioning) {
_animatedTransitioning = [[HWNavAnimatedTransitioning alloc] initWithState:HWPopStatePop];
}
return _animatedTransitioning;
}
@end

View File

@@ -0,0 +1,17 @@
//
// HWPop.h
// Pods
//
// Created by heath wang on 2019/5/22.
//
#ifndef HWPop_h
#define HWPop_h
#import <HWPopController/HWPopController.h>
#import <HWPopController/HWPopNavigationController.h>
#import <HWPopController/UIViewController+HWPopController.h>
#import <HWPopController/HWPopControllerAnimationProtocol.h>
#import <HWPopController/HWPopControllerAnimatedTransitioning.h>
#endif /* HWPop_h */

View File

@@ -0,0 +1,25 @@
//
// HWPopTransitioningDelegate.h
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <HWPopController/HWPopController.h>
NS_ASSUME_NONNULL_BEGIN
@interface HWPopTransitioningDelegate : NSObject <UIViewControllerTransitioningDelegate>
@property (nonatomic, weak, readonly) HWPopController *popController;
- (instancetype)initWithPopController:(HWPopController *)popController NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,32 @@
//
// HWPopTransitioningDelegate.m
// HWPopController
//
// Created by heath wang on 2019/5/21.
//
#import "HWPopTransitioningDelegate.h"
#import "HWPopControllerAnimatedTransitioning.h"
@implementation HWPopTransitioningDelegate
- (instancetype)initWithPopController:(HWPopController *)popController {
self = [super init];
if (self) {
_popController = popController;
}
return self;
}
#pragma mark - UIViewControllerTransitioningDelegate
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return [[HWPopControllerAnimatedTransitioning alloc] initWithState:HWPopStatePop popController:self.popController];
}
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return [[HWPopControllerAnimatedTransitioning alloc] initWithState:HWPopStateDismiss popController:self.popController];
}
@end

19
Pods/HWPopController/LICENSE generated Executable file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2019 Heath Wang <yishu.jay@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

146
Pods/HWPopController/README.md generated Executable file
View File

@@ -0,0 +1,146 @@
# HWPopController
<p style="align: left">
<a href="https://cocoapods.org/pods/HWPopController">
<img src="https://img.shields.io/cocoapods/v/HWPopController.svg?style=flat">
</a>
<a href="https://cocoapods.org/pods/HWPopController">
<img src="https://img.shields.io/cocoapods/p/HWPopController.svg?style=flat">
</a>
<a href="https://cocoapods.org/pods/HWPopController">
<img src="https://img.shields.io/badge/support-ios%208%2B-orange.svg">
</a>
<a href="https://cocoapods.org/pods/HWPopController">
<img src="https://img.shields.io/badge/language-objective--c-blue.svg">
</a>
<a href="https://cocoapods.org/pods/HWPopController">
<img src="https://img.shields.io/cocoapods/l/HWPopController.svg?style=flat">
</a>
<a href="https://cocoapods.org/pods/HWPopController">
<img src="https://img.shields.io/badge/cocoapods-supported-4BC51D.svg?style=plastic">
</a>
</p>
`HWPopController` can popup `UIViewController` with `multiple animations`, custom pop size in portrait / landscape screen. Popup position can be `top/center/bottom`. And you can define your own popup animations.
**My another project for pop view from bottom:**[**HWPanModal**](https://github.com/HeathWang/HWPanModal)
## Screen Shot
<div style="text-align: center"><table><tr>
<td style="text-align: center">
<img src="https://github.com/HeathWang/HWPopController/blob/master/screenshoot1.gif" width="250" />
</td>
<td style="text-align: center">
<img src="https://github.com/HeathWang/HWPopController/blob/master/screenshoot2.gif" width="250"/>
</td>
<td style="text-align: center">
<img src="https://github.com/HeathWang/HWPopController/blob/master/screenshoot3.gif" width="250"/>
</td>
</tr></table></div>
## Features
* Support popup UIViewController.
* Support popup UINavigationController, dynamic change pop size when you push/pop.
* Support 12 kinds of pop & dismiss animations.
* Support define your own custom animation for pop & dismiss.
* Support popup at top/center/bottom, and use `positionOffset` to adjust x,y coordinates.
## Compatibility
**iOS 8.0+**, support Objective-C & Swift.
## Installation
HWPopController is available through [CocoaPods](https://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'HWPopController', '~> 1.0.5'
```
## How to use
1. Create you popup UIViewController.
1. import HWPopController framework
2. config `contentSizeInPop` and `contentSizeInPopWhenLandscape`(if you want to support landscape)
```Objective-C
#import "HWPop1ViewController.h"
#import <HWPopController/HWPop.h>
@interface HWPop1ViewController ()
@end
@implementation HWPop1ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.contentSizeInPop = CGSizeMake(250, 300);
self.contentSizeInPopWhenLandscape = CGSizeMake(300, 200);
// build you pop view.
}
```
1. Popup your viewController
If you want high custom your popup, init HWPopController. Then config the properties what you want.
Please see `HWPopController.h`
```Objective-C
{
HWPop1ViewController *pop1ViewController = [HWPop1ViewController new];
HWPopController *popController = [[HWPopController alloc] initWithRootViewController:pop1ViewController];
// popView position
popController.popPosition = HWPopPositionTop;
[popController presentInViewController:self];
}
```
Quick popup, use the UIViewController category.
```Objective-C
HWPop1ViewController *pop1ViewController = [HWPop1ViewController new];
[pop1ViewController popupWithPopType:HWPopTypeGrowIn dismissType:HWDismissTypeGrowOut dismissOnBackgroundTouch:YES];
```
1. Dismiss pop
Use native api.
```Objective-C
[self dismissViewControllerAnimated:YES completion:^{
}];
```
Get popController, then call popController dismiss api.
```Objective-C
[self.popController dismiss];
```
### Pop UINavigationController
If you want pop UINavigationController, and every stacked UIViewController contentSize is not same. You can use `HWPopNavigationController`.
`HWPopNavigationController` subclass `UINavigationController`, you can inherit from `HWPopNavigationController`, config contentSizeInPop as default contentSize.
When you push A Controller, then Push B Controller. A, B Controller should config contentSizeInPop if you need. If you have not config it, we will use NavigationController contentSizeInPop.
Relationship
* UINavigationController
* HWPopNavigationController
* Your custom UINavigationController inherit from HWPopNavigationController
**More details, pls see the Example.**
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Author
HeathWang, yishu.jay@gmail.com
## License
**HWPopController** is available under the MIT license. See the LICENSE file for more info.