mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Generic SheetViewController
This commit is contained in:
parent
6b9133e5e6
commit
95a6df6496
|
@ -429,6 +429,7 @@
|
|||
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; };
|
||||
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; };
|
||||
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; };
|
||||
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */; };
|
||||
4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */; };
|
||||
4C3EF7FD2107DDEE0007EBF7 /* ParamParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */; };
|
||||
4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; };
|
||||
|
@ -1115,6 +1116,7 @@
|
|||
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
|
||||
4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = "<group>"; };
|
||||
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = "<group>"; };
|
||||
4C23A5F1215C4ADE00534937 /* SheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetViewController.swift; sourceTree = "<group>"; };
|
||||
4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarTableViewCell.swift; sourceTree = "<group>"; };
|
||||
4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParamParserTest.swift; sourceTree = "<group>"; };
|
||||
4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKProtoEnvelopeTest.swift; sourceTree = "<group>"; };
|
||||
|
@ -1972,6 +1974,7 @@
|
|||
34AC09DC211B39B100997B47 /* SharingThreadPickerViewController.m */,
|
||||
34AC09BF211B39AE00997B47 /* ViewControllerUtils.h */,
|
||||
34AC09D1211B39B000997B47 /* ViewControllerUtils.m */,
|
||||
4C23A5F1215C4ADE00534937 /* SheetViewController.swift */,
|
||||
);
|
||||
path = ViewControllers;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3219,6 +3222,7 @@
|
|||
346129C91FD2072E00532771 /* NSString+OWS.m in Sources */,
|
||||
347850691FD9B78A007B8332 /* AppSetup.m in Sources */,
|
||||
346941A3215D2EE400B5BFAD /* Theme.m in Sources */,
|
||||
4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */,
|
||||
34AC0A14211B39EA00997B47 /* ContactCellView.m in Sources */,
|
||||
34AC0A15211B39EA00997B47 /* ContactsViewHelper.m in Sources */,
|
||||
346129FF1FD5F31400532771 /* OWS103EnableVideoCalling.m in Sources */,
|
||||
|
|
213
SignalMessaging/ViewControllers/SheetViewController.swift
Normal file
213
SignalMessaging/ViewControllers/SheetViewController.swift
Normal file
|
@ -0,0 +1,213 @@
|
|||
//
|
||||
// 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()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
// self.delegate?.sheetViewDidHide(self)
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue