mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			224 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			224 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Swift
		
	
//
 | 
						|
//  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 | 
						|
//
 | 
						|
 | 
						|
import Foundation
 | 
						|
 | 
						|
@objc(OWSSheetViewControllerDelegate)
 | 
						|
public protocol SheetViewControllerDelegate: class {
 | 
						|
    func sheetViewControllerRequestedDismiss(_ sheetViewController: SheetViewController)
 | 
						|
}
 | 
						|
 | 
						|
@objc(OWSSheetViewController)
 | 
						|
public class SheetViewController: UIViewController {
 | 
						|
 | 
						|
    @objc
 | 
						|
    weak var delegate: SheetViewControllerDelegate?
 | 
						|
 | 
						|
    @objc
 | 
						|
    public let contentView: UIView = UIView()
 | 
						|
 | 
						|
    private let sheetView: SheetView = SheetView()
 | 
						|
    private let handleView: UIView = UIView()
 | 
						|
 | 
						|
    deinit {
 | 
						|
        Logger.verbose("")
 | 
						|
    }
 | 
						|
 | 
						|
    @objc
 | 
						|
    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
 | 
						|
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
 | 
						|
        self.transitioningDelegate = self
 | 
						|
        self.modalPresentationStyle = .overCurrentContext
 | 
						|
    }
 | 
						|
 | 
						|
    public required init?(coder aDecoder: NSCoder) {
 | 
						|
        notImplemented()
 | 
						|
    }
 | 
						|
 | 
						|
    // MARK: View LifeCycle
 | 
						|
 | 
						|
    var sheetViewVerticalConstraint: NSLayoutConstraint?
 | 
						|
 | 
						|
    override public func loadView() {
 | 
						|
        self.view = UIView()
 | 
						|
 | 
						|
        sheetView.preservesSuperviewLayoutMargins = true
 | 
						|
 | 
						|
        sheetView.addSubview(contentView)
 | 
						|
        contentView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom)
 | 
						|
        contentView.autoPinEdge(toSuperviewMargin: .bottom)
 | 
						|
 | 
						|
        view.addSubview(sheetView)
 | 
						|
        sheetView.autoPinWidthToSuperview()
 | 
						|
        sheetView.setContentHuggingVerticalHigh()
 | 
						|
        sheetView.setCompressionResistanceHigh()
 | 
						|
        self.sheetViewVerticalConstraint = sheetView.autoPinEdge(.top, to: .bottom, of: self.view)
 | 
						|
 | 
						|
        handleView.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_white : UIColor.ows_gray05
 | 
						|
        let kHandleViewHeight: CGFloat = 5
 | 
						|
        handleView.autoSetDimensions(to: CGSize(width: 40, height: kHandleViewHeight))
 | 
						|
        handleView.layer.cornerRadius = kHandleViewHeight / 2
 | 
						|
        view.addSubview(handleView)
 | 
						|
        handleView.autoAlignAxis(.vertical, toSameAxisOf: sheetView)
 | 
						|
        handleView.autoPinEdge(.bottom, to: .top, of: sheetView, withOffset: -6)
 | 
						|
 | 
						|
        // Gestures
 | 
						|
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapBackground))
 | 
						|
        self.view.addGestureRecognizer(tapGesture)
 | 
						|
 | 
						|
        let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeDown))
 | 
						|
        swipeDownGesture.direction = .down
 | 
						|
        self.view.addGestureRecognizer(swipeDownGesture)
 | 
						|
    }
 | 
						|
 | 
						|
    // MARK: Present / Dismiss animations
 | 
						|
 | 
						|
    fileprivate func animatePresentation(completion: @escaping (Bool) -> Void) {
 | 
						|
        guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else {
 | 
						|
            owsFailDebug("sheetViewVerticalConstraint was unexpectedly nil")
 | 
						|
            return
 | 
						|
        }
 | 
						|
 | 
						|
        let backgroundDuration: TimeInterval = 0.1
 | 
						|
        UIView.animate(withDuration: backgroundDuration) {
 | 
						|
            let alpha: CGFloat = Theme.isDarkThemeEnabled ? 0.7 : 0.6
 | 
						|
            self.view.backgroundColor = UIColor.black.withAlphaComponent(alpha)
 | 
						|
        }
 | 
						|
 | 
						|
        self.sheetView.superview?.layoutIfNeeded()
 | 
						|
 | 
						|
        NSLayoutConstraint.deactivate([sheetViewVerticalConstraint])
 | 
						|
        self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(toSuperviewEdge: .bottom)
 | 
						|
        UIView.animate(withDuration: 0.2,
 | 
						|
                       delay: backgroundDuration,
 | 
						|
                       options: .curveEaseOut,
 | 
						|
                       animations: {
 | 
						|
                        self.sheetView.superview?.layoutIfNeeded()
 | 
						|
        },
 | 
						|
                       completion: completion)
 | 
						|
    }
 | 
						|
 | 
						|
    fileprivate func animateDismiss(completion: @escaping (Bool) -> Void) {
 | 
						|
        guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else {
 | 
						|
            owsFailDebug("sheetVerticalConstraint was unexpectedly nil")
 | 
						|
            return
 | 
						|
        }
 | 
						|
 | 
						|
        self.sheetView.superview?.layoutIfNeeded()
 | 
						|
        NSLayoutConstraint.deactivate([sheetViewVerticalConstraint])
 | 
						|
 | 
						|
        let dismissDuration: TimeInterval = 0.2
 | 
						|
        self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(.top, to: .bottom, of: self.view)
 | 
						|
        UIView.animate(withDuration: dismissDuration,
 | 
						|
                       delay: 0,
 | 
						|
                       options: .curveEaseOut,
 | 
						|
                       animations: {
 | 
						|
                        self.view.backgroundColor = UIColor.clear
 | 
						|
                        self.sheetView.superview?.layoutIfNeeded()
 | 
						|
        },
 | 
						|
                       completion: completion)
 | 
						|
    }
 | 
						|
 | 
						|
    // MARK: Actions
 | 
						|
 | 
						|
    @objc
 | 
						|
    func didTapBackground() {
 | 
						|
        // inform delegate to
 | 
						|
        delegate?.sheetViewControllerRequestedDismiss(self)
 | 
						|
    }
 | 
						|
 | 
						|
    @objc
 | 
						|
    func didSwipeDown() {
 | 
						|
        // inform delegate to
 | 
						|
        delegate?.sheetViewControllerRequestedDismiss(self)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
extension SheetViewController: UIViewControllerTransitioningDelegate {
 | 
						|
    public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
 | 
						|
        return SheetViewPresentationController(sheetViewController: self)
 | 
						|
    }
 | 
						|
 | 
						|
    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
 | 
						|
        return SheetViewDismissalController(sheetViewController: self)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
private class SheetViewPresentationController: NSObject, UIViewControllerAnimatedTransitioning {
 | 
						|
 | 
						|
    let sheetViewController: SheetViewController
 | 
						|
    init(sheetViewController: SheetViewController) {
 | 
						|
        self.sheetViewController = sheetViewController
 | 
						|
    }
 | 
						|
 | 
						|
    // This is used for percent driven interactive transitions, as well as for
 | 
						|
    // container controllers that have companion animations that might need to
 | 
						|
    // synchronize with the main animation.
 | 
						|
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
 | 
						|
        return 0.3
 | 
						|
    }
 | 
						|
 | 
						|
    // This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
 | 
						|
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
 | 
						|
        Logger.debug("")
 | 
						|
        transitionContext.containerView.addSubview(sheetViewController.view)
 | 
						|
        sheetViewController.view.autoPinEdgesToSuperviewEdges()
 | 
						|
        sheetViewController.animatePresentation { didComplete in
 | 
						|
            Logger.debug("completed: \(didComplete)")
 | 
						|
            transitionContext.completeTransition(didComplete)
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
private class SheetViewDismissalController: NSObject, UIViewControllerAnimatedTransitioning {
 | 
						|
 | 
						|
    let sheetViewController: SheetViewController
 | 
						|
    init(sheetViewController: SheetViewController) {
 | 
						|
        self.sheetViewController = sheetViewController
 | 
						|
    }
 | 
						|
 | 
						|
    // This is used for percent driven interactive transitions, as well as for
 | 
						|
    // container controllers that have companion animations that might need to
 | 
						|
    // synchronize with the main animation.
 | 
						|
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
 | 
						|
        return 0.3
 | 
						|
    }
 | 
						|
 | 
						|
    // This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
 | 
						|
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
 | 
						|
        Logger.debug("")
 | 
						|
        sheetViewController.animateDismiss { didComplete in
 | 
						|
            Logger.debug("completed: \(didComplete)")
 | 
						|
            transitionContext.completeTransition(didComplete)
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
private class SheetView: UIView {
 | 
						|
 | 
						|
    override init(frame: CGRect) {
 | 
						|
        super.init(frame: frame)
 | 
						|
        self.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_gray90
 | 
						|
            : UIColor.ows_gray05
 | 
						|
    }
 | 
						|
 | 
						|
    required init?(coder aDecoder: NSCoder) {
 | 
						|
        notImplemented()
 | 
						|
    }
 | 
						|
 | 
						|
    override var bounds: CGRect {
 | 
						|
        didSet {
 | 
						|
            updateMask()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private func updateMask() {
 | 
						|
        let cornerRadius: CGFloat = 16
 | 
						|
        let path: UIBezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
 | 
						|
        let mask = CAShapeLayer()
 | 
						|
        mask.path = path.cgPath
 | 
						|
        self.layer.mask = mask
 | 
						|
    }
 | 
						|
}
 |