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.
214 lines
7.8 KiB
Swift
214 lines
7.8 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.backgroundColor
|
|
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)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|