123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- //
- // NCPopupViewController.swift
- //
- // Based on EzPopup by Huy Nguyen
- // Modified by Marino Faggiana for Nextcloud progect.
- //
- // Author Marino Faggiana <marino.faggiana@nextcloud.com>
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program. If not, see <http://www.gnu.org/licenses/>.
- //
- import UIKit
- public protocol NCPopupViewControllerDelegate: AnyObject {
- func popupViewControllerDidDismissByTapGesture(_ sender: NCPopupViewController)
- }
- // optional func
- public extension NCPopupViewControllerDelegate {
- func popupViewControllerDidDismissByTapGesture(_ sender: NCPopupViewController) {}
- }
- public class NCPopupViewController: UIViewController {
- private var centerYConstraint: NSLayoutConstraint?
- // Popup width, it's nil if width is determined by view's intrinsic size
- private(set) public var popupWidth: CGFloat?
- // Popup height, it's nil if width is determined by view's intrinsic size
- private(set) public var popupHeight: CGFloat?
- // Background alpha, default is 0.3
- public var backgroundAlpha: CGFloat = 0.2
- // Background color, default is black
- public var backgroundColor = UIColor.black
- // Allow tap outside popup to dismiss, default is true
- public var canTapOutsideToDismiss = true
- // Corner radius, default is 10 (0 no rounded corner)
- public var cornerRadius: CGFloat = 10
- // Shadow enabled, default is true
- public var shadowEnabled = true
- // Border enabled, default is false
- public var borderEnabled = false
- // Move the popup position H when show/hide keyboard
- public var keyboardPosizionEnabled = true
- // The pop up view controller. It's not mandatory.
- private(set) public var contentController: UIViewController?
- // The pop up view
- private(set) public var contentView: UIView?
- // The delegate to receive pop up event
- public weak var delegate: NCPopupViewControllerDelegate?
- private var containerView = UIView()
- // MARK: - View Life Cycle
- // NOTE: Don't use this init method
- required public init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- }
- /**
- Init with content view controller. Your pop up content is a view controller (easiest way to design it is using storyboard)
- - Parameters:
- - contentController: Popup content view controller
- - popupWidth: Width of popup content. If it isn't set, width will be determine by popup content view intrinsic size.
- - popupHeight: Height of popup content. If it isn't set, height will be determine by popup content view intrinsic size.
- */
- public init(contentController: UIViewController, popupWidth: CGFloat? = nil, popupHeight: CGFloat? = nil) {
- super.init(nibName: nil, bundle: nil)
- self.contentController = contentController
- self.contentView = contentController.view
- self.popupWidth = popupWidth
- self.popupHeight = popupHeight
- modalPresentationStyle = .overFullScreen
- modalTransitionStyle = .crossDissolve
- }
- /**
- Init with content view
- - Parameters:
- - contentView: Popup content view
- - popupWidth: Width of popup content. If it isn't set, width will be determine by popup content view intrinsic size.
- - popupHeight: Height of popup content. If it isn't set, height will be determine by popup content view intrinsic size.
- */
- public init(contentView: UIView, popupWidth: CGFloat? = nil, popupHeight: CGFloat? = nil) {
- super.init(nibName: nil, bundle: nil)
- self.contentView = contentView
- self.popupWidth = popupWidth
- self.popupHeight = popupHeight
- modalPresentationStyle = .overFullScreen
- modalTransitionStyle = .crossDissolve
- }
- override public func viewDidLoad() {
- super.viewDidLoad()
- setupUI()
- setupViews()
- addDismissGesture()
- NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
- }
- // MARK: - Setup
- private func addDismissGesture() {
- let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissTapGesture(gesture:)))
- tapGesture.delegate = self
- view.addGestureRecognizer(tapGesture)
- }
- private func setupUI() {
- containerView.translatesAutoresizingMaskIntoConstraints = false
- contentView?.translatesAutoresizingMaskIntoConstraints = false
- view.backgroundColor = backgroundColor.withAlphaComponent(backgroundAlpha)
- if cornerRadius > 0 {
- contentView?.layer.cornerRadius = cornerRadius
- contentView?.layer.masksToBounds = true
- }
- if shadowEnabled {
- containerView.layer.shadowOpacity = 0.5
- containerView.layer.shadowColor = UIColor.black.cgColor
- containerView.layer.shadowOffset = CGSize(width: 5, height: 5)
- containerView.layer.shadowRadius = 5
- }
- if borderEnabled {
- containerView.layer.cornerRadius = cornerRadius
- containerView.layer.borderWidth = 0.3
- containerView.layer.borderColor = NCBrandColor.shared.textColor2.cgColor
- }
- }
- private func setupViews() {
- if let contentController = contentController {
- addChild(contentController)
- }
- addViews()
- addSizeConstraints()
- addCenterPositionConstraints()
- }
- private func addViews() {
- view.addSubview(containerView)
- if let contentView = contentView {
- containerView.addSubview(contentView)
- let topConstraint = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: containerView, attribute: .top, multiplier: 1, constant: 0)
- let leftConstraint = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: containerView, attribute: .left, multiplier: 1, constant: 0)
- let bottomConstraint = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: 0)
- let rightConstraint = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: containerView, attribute: .right, multiplier: 1, constant: 0)
- NSLayoutConstraint.activate([topConstraint, leftConstraint, bottomConstraint, rightConstraint])
- }
- }
- // MARK: - Add constraints
- private func addSizeConstraints() {
- if let popupWidth = popupWidth {
- let widthConstraint = NSLayoutConstraint(item: containerView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: popupWidth)
- NSLayoutConstraint.activate([widthConstraint])
- }
- if let popupHeight = popupHeight {
- let heightConstraint = NSLayoutConstraint(item: containerView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: popupHeight)
- NSLayoutConstraint.activate([heightConstraint])
- }
- }
- private func addCenterPositionConstraints() {
- let centerXConstraint = NSLayoutConstraint(item: containerView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
- centerYConstraint = NSLayoutConstraint(item: containerView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
- NSLayoutConstraint.activate([centerXConstraint, centerYConstraint!])
- }
- // MARK: -
- func addPath() {
- let balloon = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 250))
- balloon.backgroundColor = UIColor.clear
- let path = UIBezierPath()
- path.move(to: CGPoint(x: 0, y: 0))
- path.addLine(to: CGPoint(x: 200, y: 0))
- path.addLine(to: CGPoint(x: 200, y: 200))
- // Draw arrow
- path.addLine(to: CGPoint(x: 120, y: 200))
- path.addLine(to: CGPoint(x: 100, y: 250))
- path.addLine(to: CGPoint(x: 80, y: 200))
- path.addLine(to: CGPoint(x: 0, y: 200))
- path.close()
- let shape = CAShapeLayer()
- // shape.backgroundColor = UIColor.blue.cgColor
- shape.fillColor = UIColor.blue.cgColor
- shape.path = path.cgPath
- balloon.layer.addSublayer(shape)
- // [self.view addSubview:balloonView];
- }
- // MARK: - Actions
- @objc func dismissTapGesture(gesture: UIGestureRecognizer) {
- dismiss(animated: true) {
- self.delegate?.popupViewControllerDidDismissByTapGesture(self)
- }
- }
- // MARK: - Keyboard notification
- @objc internal func keyboardWillShow(_ notification: Notification?) {
- var keyboardSize = CGSize.zero
- if let info = notification?.userInfo {
- let frameEndUserInfoKey = UIResponder.keyboardFrameEndUserInfoKey
- // Getting UIKeyboardSize.
- if let keyboardFrame = info[frameEndUserInfoKey] as? CGRect {
- let screenSize = UIScreen.main.bounds
- // Calculating actual keyboard displayed size, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381)
- let intersectRect = keyboardFrame.intersection(screenSize)
- if intersectRect.isNull {
- keyboardSize = CGSize(width: screenSize.size.width, height: 0)
- } else {
- keyboardSize = intersectRect.size
- }
- if keyboardPosizionEnabled {
- let popupDiff = screenSize.height - ((screenSize.height - (popupHeight ?? 0)) / 2)
- let keyboardDiff = screenSize.height - keyboardSize.height
- let diff = popupDiff - keyboardDiff
- if centerYConstraint != nil && diff > 0 {
- centerYConstraint?.constant = -(diff + 15)
- }
- }
- }
- }
- }
- @objc func keyboardWillHide(_ notification: Notification) {
- if keyboardPosizionEnabled {
- if centerYConstraint != nil {
- centerYConstraint?.constant = 0
- }
- }
- }
- }
- // MARK: - UIGestureRecognizerDelegate
- extension NCPopupViewController: UIGestureRecognizerDelegate {
- public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
- guard let touchView = touch.view, canTapOutsideToDismiss else {
- return false
- }
- return !touchView.isDescendant(of: containerView)
- }
- }
|