//
//? HWPopController.m
//? HWPopController
//
//? Created by heath wang on 2019/5/21.
//
#import "HWPopController.h"
#import "UIViewController+HWPopController.h"
#import "HWPopTransitioningDelegate.h"
staticNSMutableSet*_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{
? ? staticdispatch_once_tonceToken;
? ? dispatch_once(&onceToken, ^{
? ? ? ? _retainedPopControllers = [NSMutableSet set];
? ? });
}
- (instancetype)init {
? ? self= [superinit];
? ? if(self) {
? ? ? ? [selfsetup];
? ? }
? ? return self;
}
#pragma mark - public method
- (instancetype)initWithViewController:(UIViewController*)viewController {
? ? self= [selfinit];
? ? if(self) {
? ? ? ? self.topViewController= viewController;
? ? ? ? // set popController to the popped viewController
? ? ? ? viewController.popController=self;
? ? ? ? [self setupObserverForViewController:viewController];
? ? }
? ? return self;
}
- (void)presentInViewController:(UIViewController*)presentingViewController {
? ? [selfpresentInViewController:presentingViewControllercompletion:nil];
}
- (void)presentInViewController:(UIViewController*)presentingViewControllercompletion:(nullablevoid(^)(void))completion {
? ? if (self.presented)
? ? ? ? return;
? ? dispatch_async(dispatch_get_main_queue(), ^{
? ? ? ? [self setupObserver];
? ? ? ? [_retainedPopControllers addObject:self];
? ? ? ? UIViewController*VC = presentingViewController.tabBarController?: presentingViewController;
? ? ? ? if(@available(iOS11.0, *)) {
? ? ? ? ? ? if (!self.didOverrideSafeAreaInsets) {
? ? ? ? ? ? ? ? self.safeAreaInsets= presentingViewController.view.safeAreaInsets;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? [VCpresentViewController:self.containerViewController animated:YES completion:completion];
? ? });
}
- (void)dismiss {
? ? [self dismissWithCompletion:nil];
}
- (void)dismissWithCompletion:(nullablevoid(^)(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 {
? ? [viewControlleraddObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeInPop)) options:NSKeyValueObservingOptionNew context:nil];
? ? [viewControlleraddObserver: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];
? ? // 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 {
? ? [viewControllerremoveObserver:selfforKeyPath:NSStringFromSelector(@selector(contentSizeInPop))];
? ? [viewControllerremoveObserver:selfforKeyPath:NSStringFromSelector(@selector(contentSizeInPopWhenLandscape))];
}
- (void)observeValueForKeyPath:(nullableNSString*)keyPathofObject:(nullableid)objectchange:(nullableNSDictionary *)changecontext:(nullablevoid*)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:^{
? ? ? ? ? ? ? ? [selflayoutContainerView];
? ? ? ? ? ? }completion:^(BOOLfinished) {
? ? ? ? ? ? ? ? [self adjustContainerViewOrigin];
? ? ? ? ? ? }];
? ? ? ? }
? ? }
}
#pragma mark - UIApplicationDidChangeStatusBarOrientationNotification
- (void)orientationDidChange {
? ? [self.containerView endEditing:YES];
? ? [UIView animateWithDuration:0.25 animations:^{
? ? ? ? [self layoutContainerView];
? ? }completion:^(BOOLfinished) {
? ? }];
}
#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;
? ? CGFloattextFieldBottomY = [currentTextInputconvertPoint:CGPointZerotoView: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;
? ? }
? ? CGFloatoffsetY =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{
? ? ? ? ? ? CGFloatspacing =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 {
//? ? UIView *currentTextInput = [self getCurrentTextInputInView:self.containerView];
//? ? if (!currentTextInput) {
//? ? ? ? return;
//? ? }
//
//? ? self.keyboardInfo = notification.userInfo;
//? ? [self adjustContainerViewOrigin];
}
- (void)keyboardWillHide:(NSNotification*)notification {
? ? 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")]) {
? ? ? ? ? ? returnnil;
? ? ? ? }
? ? ? ? return(UIView *) view;
? ? }
? ? for(UIView*subviewinview.subviews) {
? ? ? ? UIView *inputInView = [selfgetCurrentTextInputInView:subview];
? ? ? ? if(inputInView) {
? ? ? ? ? ? returninputInView;
? ? ? ? }
? ? }
? ? return nil;
}
#pragma mark- touch event
- (void)didTapBackgroundView {
? ? if (self.shouldDismissOnBackgroundTouch) {
? ? ? ? [selfdismiss];
? ? }
}
#pragma mark- UI Layout
- (void)layoutContainerView {
? ? CGAffineTransform lastTransform = self.containerView.transform;
? ? self.containerView.transform = CGAffineTransformIdentity;
? ? self.backgroundView.frame = self.containerViewController.view.bounds;
? ? CGSizecontentSizeOfTopView = [selfcontentSizeOfTopView];
? ? CGFloatcontainerViewWidth = contentSizeOfTopView.width;
? ? CGFloatcontainerViewHeight = contentSizeOfTopView.height;
? ? CGFloatcontainerViewY;
? ? 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;
? ? CGFloatcontainerViewX = (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;
? ? CGSizecontentSize;
? ? 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.");
? ? returncontentSize;
}
#pragma mark- UI prepare
- (void)setup{
? ? self.shouldDismissOnBackgroundTouch = YES;
? ? self.animationDuration = 0.2;
? ? self.popType = HWPopTypeGrowIn;
? ? self.dismissType = HWDismissTypeFadeOut;
? ? [self.containerViewController.view addSubview:self.containerView];
? ? [self.containerView addSubview:self.contentView];
? ? UIView*bgView = [UIViewnew];
? ? 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= [UIViewnew];
? ? ? ? _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= [UIViewnew];
? ? }
? ? return _contentView;
}
- (BOOL)presented {
? ? return self.containerViewController.presentingViewController != nil;
}
- (void)dealloc {
? ? [self destroyObserver];
? ? [self destroyObserverOfViewController:self.topViewController];
}
@end