PopupController icon indicating copy to clipboard operation
PopupController copied to clipboard

iPad orientation changes breaks sizes.

Open mureatencio opened this issue 7 years ago • 1 comments

Orientation changes are not working on iPad (iOS 11). Any idea on how to fix it? screen shot 2017-09-16 at 7 01 07 pm

mureatencio avatar Sep 17 '17 01:09 mureatencio

Found a solution, updated orientation changes code.

`// // PopupController.swift // PopupController // // Created by 佐藤 大輔 on 2/4/16. // Copyright © 2016 Daisuke Sato. All rights reserved. //

import UIKit

public enum PopupCustomOption { case layout(PopupController.PopupLayout) case animation(PopupController.PopupAnimation) case backgroundStyle(PopupController.PopupBackgroundStyle) case scrollable(Bool) case dismissWhenTaps(Bool) case movesAlongWithKeyboard(Bool) }

typealias PopupAnimateCompletion = () -> ()

// MARK: - Protocols /** PopupContentViewController: Every ViewController which is added on the PopupController must need to be conformed this protocol. */ public protocol PopupContentViewController {

/** sizeForPopup(popupController: size: showingKeyboard:):
 return view's size
 */
func sizeForPopup(_ popupController: PopupController, size: CGSize, showingKeyboard: Bool) -> CGSize

}

open class PopupController: UIViewController {

public enum PopupLayout {
    case top, center, bottom, left
    
    func origin(_ view: UIView, size: CGSize = UIScreen.main.bounds.size) -> CGPoint {
        switch self {
        case .top: return CGPoint(x: (size.width - view.frame.width) / 2, y: 0)
        case .center: return CGPoint(x: (size.width - view.frame.width) / 2, y: (size.height - view.frame.height) / 2)
        case .bottom: return CGPoint(x: (size.width - view.frame.width) / 2, y: size.height - view.frame.height)
        case .left: return CGPoint(x: 0, y: 0)
        }
    }
}

public enum PopupAnimation {
    case fadeIn, slideUp, slideDown, slideLeft
}

public enum PopupBackgroundStyle {
    case blackFilter(alpha: CGFloat)
}

// MARK: - Public variables
open var popupView: UIView!

// MARK: - Private variables
fileprivate var movesAlongWithKeyboard: Bool = true
fileprivate var scrollable: Bool = true {
    didSet {
        updateScrollable()
    }
}
fileprivate var dismissWhenTaps: Bool = true {
    didSet {
        if dismissWhenTaps {
            registerTapGesture()
        } else {
            unregisterTapGesture()
        }
    }
}
fileprivate var backgroundStyle: PopupBackgroundStyle = .blackFilter(alpha: 0.4) {
    didSet {
        updateBackgroundStyle(backgroundStyle)
    }
}
fileprivate var layout: PopupLayout = .center
fileprivate var animation: PopupAnimation = .fadeIn

fileprivate let margin: CGFloat = 16
fileprivate let baseScrollView = UIScrollView()
fileprivate var isShowingKeyboard: Bool = false
fileprivate var defaultContentOffset = CGPoint.zero
fileprivate var closedHandler: ((PopupController) -> Void)?
fileprivate var showedHandler: ((PopupController) -> Void)?


fileprivate var maximumSize: CGSize {
    get {
        return CGSize(
            width: UIScreen.main.bounds.size.width - margin * 2,
            height: UIScreen.main.bounds.size.height - margin * 2
        )
    }
}

deinit {
    self.removeFromParentViewController()
}

// MARK: Overrides
open override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    registerNotification()
}

open override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    unregisterNotification()
}

open override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    updateOrientation()
}

}

// MARK: - Publics public extension PopupController {

// MARK: Classes
public class func create(_ parentViewController: UIViewController) -> PopupController {
    let controller = PopupController()
    controller.defaultConfigure()
    
    parentViewController.addChildViewController(controller)
    parentViewController.view.addSubview(controller.view)
    controller.didMove(toParentViewController: parentViewController)
    
    return controller
}

public func customize(_ options: [PopupCustomOption]) -> PopupController {
    customOptions(options)
    return self
}

@discardableResult
public func show(_ childViewController: UIViewController) -> PopupController {
    self.addChildViewController(childViewController)
    popupView = childViewController.view
    configure()
    
    childViewController.didMove(toParentViewController: self)
    
    show(layout, animation: animation) {
        self.defaultContentOffset = self.baseScrollView.contentOffset
        self.showedHandler?(self)
    }
    
    return self
}

public func didShowHandler(_ handler: @escaping (PopupController) -> Void) -> PopupController {
    self.showedHandler = handler
    return self
}

public func didCloseHandler(_ handler: @escaping (PopupController) -> Void) -> PopupController {
    self.closedHandler = handler
    return self
}

public func dismiss(_ completion: (() -> Void)? = nil) {
    if isShowingKeyboard {
        popupView.endEditing(true)
    }
    self.closePopup(completion)
}

}

// MARK: Privates private extension PopupController {

func defaultConfigure() {
    scrollable = true
    dismissWhenTaps = true
    backgroundStyle = .blackFilter(alpha: 0.4)
}

func configure() {
    view.isHidden = true
    view.frame = UIScreen.main.bounds
    
    baseScrollView.frame = view.frame
    view.addSubview(baseScrollView)
    
    popupView.layer.cornerRadius = 2
    popupView.layer.masksToBounds = true
    popupView.frame.origin.y = 0
    
    baseScrollView.addSubview(popupView)
}

func registerNotification() {
    NotificationCenter.default.addObserver(self, selector: #selector(PopupController.popupControllerWillShowKeyboard(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(PopupController.popupControllerWillHideKeyboard(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(PopupController.popupControllerDidHideKeyboard(_:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
}

func unregisterNotification() {
    NotificationCenter.default.removeObserver(self)
}

func customOptions(_ options: [PopupCustomOption]) {
    for option in options {
        switch option {
        case .layout(let layout):
            self.layout = layout
        case .animation(let animation):
            self.animation = animation
        case .backgroundStyle(let style):
            self.backgroundStyle = style
        case .scrollable(let scrollable):
            self.scrollable = scrollable
        case .dismissWhenTaps(let dismiss):
            self.dismissWhenTaps = dismiss
        case .movesAlongWithKeyboard(let moves):
            self.movesAlongWithKeyboard = moves
        }
    }
}

func registerTapGesture() {
    let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(PopupController.didTapGesture(_:)))
    
    gestureRecognizer.delegate = self
    baseScrollView.addGestureRecognizer(gestureRecognizer)
}

func unregisterTapGesture() {
    for recognizer in baseScrollView.gestureRecognizers ?? [] {
        baseScrollView.removeGestureRecognizer(recognizer)
    }
}

func updateOrientation(){
    guard let child = self.childViewControllers.last as? PopupContentViewController else { return }
    popupView.frame.size = child.sizeForPopup(self, size: maximumSize, showingKeyboard: isShowingKeyboard)
    let scrollViewWidth = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
    let scrollViewHeight = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
    baseScrollView.frame.size = CGSize(width: scrollViewWidth, height: scrollViewHeight)
}

func updateLayouts() {
    guard let child = self.childViewControllers.last as? PopupContentViewController else { return }
    popupView.frame.size = child.sizeForPopup(self, size: maximumSize, showingKeyboard: isShowingKeyboard)
    popupView.frame.origin.x = layout.origin(popupView).x
    baseScrollView.frame = UIScreen.main.bounds//view.frame
    baseScrollView.contentInset.top = layout.origin(popupView).y
    defaultContentOffset.y = -baseScrollView.contentInset.top
}

func updateBackgroundStyle(_ style: PopupBackgroundStyle) {
    switch style {
    case .blackFilter(let alpha):
        baseScrollView.backgroundColor = UIColor.black.withAlphaComponent(alpha)
    }
}

func updateScrollable() {
    baseScrollView.isScrollEnabled = scrollable
    baseScrollView.alwaysBounceVertical = scrollable
    
    if scrollable {
        baseScrollView.delegate = self
    }
}

@objc func popupControllerWillShowKeyboard(_ notification: Notification) {
    self.isShowingKeyboard = true
    guard let obj = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
        return
    }
    
    if needsToMoveFrom(obj.cgRectValue.origin) {
        move(obj.cgRectValue.origin)
    }
}

@objc func popupControllerWillHideKeyboard(_ notification: Notification) {
    back()
}

@objc func popupControllerDidHideKeyboard(_ notification: Notification) {
    self.isShowingKeyboard = false
}

// Tap Gesture
@objc func didTapGesture(_ sender: UITapGestureRecognizer) {
    self.closePopup {}
}

func closePopup(_ completion: (() -> Void)?) {
    hide(animation) {
        completion?()
        self.didClosePopup()
    }
}

func didClosePopup() {
    popupView.endEditing(true)
    popupView.removeFromSuperview()
    
    childViewControllers.forEach { $0.removeFromParentViewController() }
    
    view.isHidden = true
    self.closedHandler?(self)
    
    self.removeFromParentViewController()
}

func show(_ layout: PopupLayout, animation: PopupAnimation, completion: @escaping PopupAnimateCompletion) {
    guard let childViewController = childViewControllers.last as? PopupContentViewController else {
        return
    }
    
    popupView.frame.size = childViewController.sizeForPopup(self, size: maximumSize, showingKeyboard: isShowingKeyboard)
    popupView.frame.origin.x = layout.origin(popupView!).x
    
    switch animation {
    case .fadeIn:
        fadeIn(layout, completion: { () -> Void in
            completion()
        })
    case .slideUp:
        slideUp(layout, completion: { () -> Void in
            completion()
        })
    case .slideDown:
        slideDown(layout, completion: { () -> Void in
            completion()
        })
    case .slideLeft:
        slideLeft(layout, completion: { () -> Void in
            completion()
        })
    }
   
}

func hide(_ animation: PopupAnimation, completion: @escaping PopupAnimateCompletion) {
    guard let child = childViewControllers.last as? PopupContentViewController else {
        return
    }
    
    popupView.frame.size = child.sizeForPopup(self, size: maximumSize, showingKeyboard: isShowingKeyboard)
    popupView.frame.origin.x = layout.origin(popupView).x
    
    switch animation {
    case .fadeIn:
        self.fadeOut({ () -> Void in
            self.clean()
            completion()
        })
    case .slideUp:
        self.slideOut({ () -> Void in
            self.clean()
            completion()
        })
    case .slideDown:
        self.slideDown({ () -> Void in
            self.clean()
            completion()
        })
    case .slideLeft:
        self.slideRight({ () -> Void in
            self.clean()
            completion()
        })
    }
}

func needsToMoveFrom(_ origin: CGPoint) -> Bool {
    guard movesAlongWithKeyboard else {
        return false
    }
    return (popupView.frame.maxY + layout.origin(popupView).y) > origin.y
}

func move(_ origin: CGPoint) {
    guard let child = childViewControllers.last as? PopupContentViewController else {
        return
    }
    popupView.frame.size = child.sizeForPopup(self, size: maximumSize, showingKeyboard: isShowingKeyboard)
    baseScrollView.contentInset.top = origin.y - popupView.frame.height
    baseScrollView.contentOffset.y = -baseScrollView.contentInset.top
    defaultContentOffset = baseScrollView.contentOffset
}

func back() {
    guard let child = childViewControllers.last as? PopupContentViewController else {
        return
    }
    popupView.frame.size = child.sizeForPopup(self, size: maximumSize, showingKeyboard: isShowingKeyboard)
    baseScrollView.contentInset.top = layout.origin(popupView).y
    defaultContentOffset.y = -baseScrollView.contentInset.top
}

func clean() {
    popupView.endEditing(true)
    popupView.removeFromSuperview()
    baseScrollView.removeFromSuperview()
}

}

// MARK: Animations private extension PopupController {

func fadeIn(_ layout: PopupLayout, completion: @escaping () -> Void) {
    baseScrollView.contentInset.top = layout.origin(popupView).y
    
    view.isHidden = false
    popupView.alpha = 0.0
    popupView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
    baseScrollView.alpha = 0.0
    
    UIView.animate(withDuration: 0.3, delay: 0.1, options: UIViewAnimationOptions(), animations: { () -> Void in
        self.popupView.alpha = 1.0
        self.baseScrollView.alpha = 1.0
        self.popupView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
    }) { (finished) -> Void in
        completion()
    }
}

func slideUp(_ layout: PopupLayout, completion: @escaping () -> Void) {
    view.isHidden = false
    baseScrollView.backgroundColor = UIColor.clear
    baseScrollView.contentInset.top = layout.origin(popupView).y
    baseScrollView.contentOffset.y = -UIScreen.main.bounds.height
    
    UIView.animate(
        withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: { () -> Void in
            
            self.updateBackgroundStyle(self.backgroundStyle)
            self.baseScrollView.contentOffset.y = -layout.origin(self.popupView).y
            self.defaultContentOffset = self.baseScrollView.contentOffset
    }, completion: { (isFinished) -> Void in
        completion()
    })
}

func slideLeft(_ layout: PopupLayout, completion: @escaping () -> Void) {
    view.isHidden = false
    baseScrollView.backgroundColor = UIColor.clear
    baseScrollView.contentInset.left = layout.origin(popupView).y
    baseScrollView.contentOffset.y = 0
    
    UIView.animate(
        withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: { () -> Void in
            
            self.updateBackgroundStyle(self.backgroundStyle)
            self.baseScrollView.contentOffset.x = 0//-layout.origin(self.popupView).y
            self.defaultContentOffset = self.baseScrollView.contentOffset
    }, completion: { (isFinished) -> Void in
        completion()
    })
}

func slideDown(_ layout: PopupLayout, completion: @escaping () -> Void) {
    view.isHidden = false
    baseScrollView.backgroundColor = UIColor.clear
    baseScrollView.contentInset.top = layout.origin(popupView).y
    baseScrollView.contentOffset.y = self.popupView.frame.size.height
    
    UIView.animate(
        withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: { () -> Void in
            
            self.updateBackgroundStyle(self.backgroundStyle)
            self.baseScrollView.contentOffset.y = -layout.origin(self.popupView).y
            self.defaultContentOffset = self.baseScrollView.contentOffset
    }, completion: { (isFinished) -> Void in
        completion()
    })
}

func fadeOut(_ completion: @escaping () -> Void) {
    
    UIView.animate(
        withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in
            self.popupView.alpha = 0.0
            self.baseScrollView.alpha = 0.0
            self.popupView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
    }) { (finished) -> Void in
        completion()
    }
}

func slideOut(_ completion: @escaping () -> Void) {
    
    UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: { () -> Void in
        self.popupView.frame.origin.y = UIScreen.main.bounds.height
        self.baseScrollView.alpha = 0.0
    }, completion: { (isFinished) -> Void in
        completion()
    })
}

func slideDown(_ completion: @escaping () -> Void) {
    
    UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: { () -> Void in
        self.popupView.frame.origin.y -= self.popupView.frame.size.height
        self.baseScrollView.alpha = 0.0
    }, completion: { (isFinished) -> Void in
        completion()
    })
}

func slideRight(_ completion: @escaping () -> Void) {
    
    UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: { () -> Void in
        self.popupView.frame.origin.x -= self.popupView.frame.size.width
        self.baseScrollView.alpha = 0.0
    }, completion: { (isFinished) -> Void in
        completion()
    })
}

}

// MARK: UIScrollViewDelegate methods extension PopupController: UIScrollViewDelegate {

public func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let delta: CGFloat = defaultContentOffset.y - scrollView.contentOffset.y
    if delta > 20 && isShowingKeyboard {
        popupView.endEditing(true)
        return
    }
}

public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    let delta: CGFloat = defaultContentOffset.y - scrollView.contentOffset.y
    if delta > 50 {
        baseScrollView.contentInset.top = -scrollView.contentOffset.y
        animation = .slideUp
        self.closePopup {}
    }
}

}

extension PopupController: UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { return gestureRecognizer.view == touch.view } } `

mureatencio avatar Sep 18 '17 09:09 mureatencio