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
		
	
| 
											7 years ago
										 | // | ||
|  | //  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() | ||
| 
											7 years ago
										 |     private let handleView: UIView = UIView() | ||
| 
											7 years ago
										 | 
 | ||
|  |     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 | ||
|  |     } | ||
|  | 
 | ||
| 
											7 years ago
										 |     public required init?(coder aDecoder: NSCoder) { | ||
| 
											7 years ago
										 |         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) | ||
|  | 
 | ||
| 
											7 years ago
										 |         handleView.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_white : UIColor.ows_gray05 | ||
| 
											7 years ago
										 |         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) | ||
| 
											7 years ago
										 | 
 | ||
|  |         let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeDown)) | ||
|  |         swipeDownGesture.direction = .down | ||
|  |         self.view.addGestureRecognizer(swipeDownGesture) | ||
| 
											7 years ago
										 |     } | ||
|  | 
 | ||
|  |     // 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) | ||
|  |     } | ||
| 
											7 years ago
										 | 
 | ||
|  |     @objc | ||
|  |     func didSwipeDown() { | ||
|  |         // inform delegate to | ||
|  |         delegate?.sheetViewControllerRequestedDismiss(self) | ||
|  |     } | ||
| 
											7 years ago
										 | } | ||
|  | 
 | ||
|  | 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 | ||
|  |     } | ||
|  | } |