【问题标题】:How to build a context menu like Facebook / Slack on iOS?如何在 iOS 上构建像 Facebook / Slack 这样的上下文菜单?
【发布时间】:2019-04-21 16:44:21
【问题描述】:

我只是在查看 Facebook 和/或 slack 的上下文菜单,并想在我的应用程序中创建类似的东西。

我尝试了两种方法。

第一种方法。有一个 in View Table View 并从底部滑动它以创建好像它是动画到视图上一样。但是这样做的问题是导航控制器和标签栏控制器没有隐藏,并且在黑色(Alpha 30 %)上显示了一个白色补丁。

我尝试的第二种方法是在当前视图控制器上显示一个新的视图控制器并显示为Modal presentation

  let vc = CustomActionTableViewController(nibName: "CustomActionTableViewController", bundle: nil)
    vc.modalPresentationStyle = .overFullScreen
    self.present(vc, animated: false, completion: nil)

这行得通,但是该方法太慢了,因为我必须处理大量通知(要将选定的索引发送到我的主视图然后执行操作)。它非常缓慢。

任何人都可以帮助我如何改进实施,以便我可以获得类似于 Facebook 的操作表,它非常流畅且非常流畅

【问题讨论】:

  • 在您的第一种方法中,为什么不在 tableView 出现时隐藏导航和标签栏,而在 didSelect 发生或 tableView 隐藏时再次取消隐藏?
  • 我认为这对你来说是个不错的选择。比第二个更省力、更快。
  • 我想到了,但我需要显示导航栏,因为我正在使用大图块。所以如果我把它隐藏在顶部的大部分将被隐藏,它会看起来很奇怪。
  • 为什么隐藏导航栏对你来说太有必要了?
  • 您最终得到了答案吗?我想做同样的事情。以前有一些开源代码,但我找不到。想知道你最后怎么样了。

标签: ios swift swift3 contextmenu uiactionsheet


【解决方案1】:

检查这个例子:Bottom pop Up 目前我在我的应用程序中使用它,它工作正常。

【讨论】:

    【解决方案2】:

    既然你提到了 Slack,他们实际上已经开源了他们的底层实现,PanModal

    【讨论】:

      【解决方案3】:

      使用 UIPresentationController 和 UIPanGestureRecognizer

      1- 创建 BottomMenu 演示控制器,它将处理视图控制器的高度和模糊

      class BottomMenuPresentationController: UIPresentationController {
      
          // MARK: - Properties
              var blurEffectView: UIVisualEffectView?
              var tapGestureRecognizer = UITapGestureRecognizer()
      
          private var topHeightRatio: Float
          private var bottomHeightRatio: Float
      
           init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, topHeightRatio: Float, bottomHeightRatio: Float) {
                  let blurEffect = UIBlurEffect(style: .systemThickMaterialDark)
                  blurEffectView = UIVisualEffectView(effect: blurEffect)
      
                  self.topHeightRatio = topHeightRatio
                  self.bottomHeightRatio = bottomHeightRatio
                  super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
                  blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
                  tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
                  self.blurEffectView?.isUserInteractionEnabled = true
                  self.blurEffectView?.addGestureRecognizer(tapGestureRecognizer)
              }
      
              override var frameOfPresentedViewInContainerView: CGRect {
                  CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height * CGFloat(topHeightRatio)),
                         size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height * CGFloat(bottomHeightRatio)))
              }
      
              override func presentationTransitionWillBegin() {
                  self.blurEffectView?.alpha = 0
                  if let blurEffectView = blurEffectView {
                  self.containerView?.addSubview(blurEffectView)
                  }
                  self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (_) in
                      self.blurEffectView?.alpha = 0.66
                  }, completion: { (_) in })
              }
      
              override func dismissalTransitionWillBegin() {
                  self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (_) in
                          self.blurEffectView?.alpha = 0
                      }, completion: { (_) in
                          self.blurEffectView?.removeFromSuperview()
                      })
              }
      
              override func containerViewWillLayoutSubviews() {
                  super.containerViewWillLayoutSubviews()
                  presentedView!.roundCorners([.topLeft, .topRight], radius: 14)
      
              }
      
              override func containerViewDidLayoutSubviews() {
                  super.containerViewDidLayoutSubviews()
                  presentedView?.frame = frameOfPresentedViewInContainerView
                  blurEffectView?.frame = containerView!.bounds
              }
      
              @objc func dismissController() {
                  self.presentedViewController.dismiss(animated: true, completion: nil)
              }
          }
      

      2- 创建你的 ViewController

      class BottomMenuVC: UIViewController {
      
          // MARK: - Instances
          var hasSetPointOrigin = false
          var pointOrigin: CGPoint?
      
          // MARK: - Properties
          let topDarkLine: UIView = {
              let view = UIView()
              view.backgroundColor = UIColor(hexString: "#E1E1E1")
              view.layer.cornerRadius = 2
              return view
          }()
          let cancelButn: UIButton = {
              let button = UIButton(type: .custom)
              button.setAttributedTitle(NSAttributedString(string: "Cancel", attributes: [NSAttributedString.Key.font: UIFont.LatoMedium(size: 17),
                                                                                          NSAttributedString.Key.foregroundColor: UIColor(hexString: "#515151")
              ]), for: .normal)
              button.backgroundColor = UIColor(hexString: "#F1F3F4")
              button.layer.cornerRadius = 5.0
              button.addTarget(self, action: #selector(cancelButnPressed), for: .touchUpInside)
              return button
          }()
      
          // MARK: - viewLifeCycle
          override func viewDidLoad() {
              super.viewDidLoad()
              view.backgroundColor = .white
              view.isUserInteractionEnabled = true
              setupMenuView()
          }
      
      
          override func viewDidLayoutSubviews() {
              if !hasSetPointOrigin {
                  hasSetPointOrigin = true
                  pointOrigin = self.view.frame.origin
              }
          }
      
          // MARK: - SetupView
      
          func setupMenuView() {
              self.view.addSubview(topDarkLine)
              self.view.addSubview(cancelButn)
      
              let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerAction(_:)))
      
              view.addGestureRecognizer(panGesture)
      
              topDarkLine.constrainHeight(constant: 4)
              topDarkLine.constrainWidth(constant: view.frame.size.width * 0.10)
              topDarkLine.centerXInSuperview()
              topDarkLine.anchor(top: view.topAnchor, leading: nil, bottom: nil, trailing: nil, padding: .init(top: 8, left: 0, bottom: 0, right: 0))
      
      
              cancelButn.anchor(top:view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor,
                                padding: .init(top: 16, left: 16, bottom: 0, right: 16))
               cancelButn.constrainHeight(constant: 44)
      
          }
      
          // MARK: - Actions
      
          @objc func panGestureRecognizerAction(_ sender: UIPanGestureRecognizer) {
      
              let translation = sender.translation(in: view)
      
              // Not allowing the user to drag the view upward
              guard translation.y >= 0 else { return }
      
              // setting x as 0 because we don't want users to move the frame side ways!! Only want straight up or down in the y-axis
              view.frame.origin = CGPoint(x: 0, y: self.pointOrigin!.y + translation.y)
      
              if sender.state == .ended {
                  let dragVelocity = sender.velocity(in: view)
                  if dragVelocity.y >= 1300 {
                      // Velocity fast enough to dismiss the uiview
                      self.dismiss(animated: true, completion: nil)
                  } else {
                      // If the dragging isn’t too fast, resetting the view back to it’s original point
                      UIView.animate(withDuration: 0.3) {
                          self.view.frame.origin = self.pointOrigin ?? CGPoint(x: 0, y: 400)
                      }
                  }
              }
      
          }
      
          @objc func cancelButnPressed() {
      
              dismiss(animated: true, completion: nil)
          }
      
      }
      

      3- 使包含将显示您的菜单的按钮的 viewController 符合 UIViewControllerTransitioningDelegate

      extension viewController: UIViewControllerTransitioningDelegate {
      
          func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
      
      BottomMenuPresentationController(presentedViewController: presented, presenting: presenting, topHeightRatio: 0.6, bottomHeightRatio: 0.4)
      
          }
      
      }
      

      4- 将转换代理设置为 self 并呈现您的自定义演示控制器

      func showBottomMenu() {
          let menu = BottomMenuVC()
          menu.coordinator = self
          menu.modalPresentationStyle = .custom
          menu.transitioningDelegate = self        
          present(menu, animated: true, completion: nil)
      }
      

      查看这篇PanGesture Slidable View 文章

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-05
        • 1970-01-01
        • 2017-01-12
        • 1970-01-01
        • 2022-01-25
        • 1970-01-01
        • 2022-01-03
        • 1970-01-01
        相关资源
        最近更新 更多