【问题标题】:Dynamically change the size of the popover depending on the content根据内容动态改变弹出框的大小
【发布时间】:2020-04-30 13:21:23
【问题描述】:

我为我的 UIBarButtonItem 创建了一个弹出窗口。

 @objc func showPopup(_ sender: UIBarButtonItem) {
  let sb = UIStoryboard(name: "Main", bundle: nil)
        let ctrl = sb.instantiateViewController(withIdentifier: "Popup") as! Popup
        let nav = UINavigationController()
        nav.pushViewController(ctrl, animated: true)
        nav.modalPresentationStyle = .popover
        present(nav, animated: true)
        nav.popoverPresentationController?.barButtonItem = sender
  }

Here 是约束。 我在堆栈视图中放置了几个标签,它们的大小可能不同。

我看到可以为 UIViewController 设置首选的显式大小,但这会使我的布局静态化。

如何根据内容为弹出框设置动态宽度和高度?

【问题讨论】:

    标签: ios swift


    【解决方案1】:

    几天前我为此创建了一个开源snippet。它包括箭头和 passThroughViews,因此您可以在不关闭弹出框的情况下点击它们,但它们都不是必需的参数。

    您可以使用以下方式从任何视图控制器调用它:

    vc.showPopover(message: "Some message for a dynamically-sized popover with a left arrow.", sourceView: self.button, sourceRect: self.button.bounds, arrowDirection: .left, passthroughViews: [button])
    

    在导航栏中右侧 UIBarButtonItem 所在的下方显示一个弹出框:

        guard let navBarView = navigationController?.navigationBar.subviews.first else { return }
        let topPadding = self.navigationController?.view.safeAreaInsets.top ?? 0
        self.showPopover(message: "This will show up on your rightmost bar button item in your navigation.", sourceView: navBarView, sourceRect: CGRect.init(x: navBarView.frame.width - 40, y: navBarView.frame.height + topPadding, width: 0, height: 0), arrowDirection: .up, passthroughViews: (navigationController?.navigationBar.subviews)!)
    

    您需要的第一部分是 PopoverViewController 类:

    import Foundation
    import UIKit
    
    class PopoverViewController: UIViewController {
    
        @IBOutlet weak var labelMessage: UILabel!
        var message:String?
        weak var sendingViewController: UIViewController?
        var arrowDirection: UIPopoverArrowDirection = .any
    
        let ltPadding: CGFloat = 20 // leading/trailing padding
        let tbPadding: CGFloat = 14 // top/bottom padding
        let arrowPadding: CGFloat = 13 // popover arrow padding
        let dimLevel: CGFloat = 0.6
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            if let message = self.message {
                self.labelMessage.text = message
            }
            let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
            view.addGestureRecognizer(tap)
    
            // dim sendingViewController
            UIView.animate(withDuration: AntimationDuration.fadeInOut.rawValue, animations: {
                self.sendingViewController?.view.alpha = self.dimLevel
            })
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            print("arrowDirection: \(arrowDirection)")
    
            let item: UIView = self.labelMessage
    
            guard let superview = item.superview else { return }
    
            // all constaints must be set for a proper display
    
            // set centered constraincts
            item.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true
            item.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true
    
            // iOS grows view if there is an arrow, so adjust anchors to fix offset
            if arrowDirection.rawValue == 2 { //.down {
                item.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: ltPadding).isActive = true
                item.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: ltPadding).isActive = true
                item.topAnchor.constraint(equalTo: superview.topAnchor, constant: tbPadding).isActive = true
                item.bottomAnchor.constraint(greaterThanOrEqualTo: superview.bottomAnchor, constant: tbPadding + arrowPadding).isActive = true
                item.widthAnchor.constraint(equalTo: superview.widthAnchor, constant: -(ltPadding * 2)).isActive = true
                item.heightAnchor.constraint(equalTo: superview.heightAnchor, constant: -(tbPadding * 2) - arrowPadding).isActive = true
            } else if arrowDirection.rawValue == 1 { //.up {
                item.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: ltPadding).isActive = true
                item.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: ltPadding).isActive = true
                item.topAnchor.constraint(equalTo: superview.topAnchor, constant: tbPadding + arrowPadding).isActive = true
                item.bottomAnchor.constraint(greaterThanOrEqualTo: superview.bottomAnchor, constant: tbPadding).isActive = true
                item.widthAnchor.constraint(equalTo: superview.widthAnchor, constant: -(ltPadding * 2)).isActive = true
                item.heightAnchor.constraint(equalTo: superview.heightAnchor, constant: -(tbPadding * 2) - arrowPadding).isActive = true
            } else if arrowDirection.rawValue == 4 { //.left {
                item.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: ltPadding + arrowPadding).isActive = true
                item.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: ltPadding).isActive = true
                item.topAnchor.constraint(equalTo: superview.topAnchor, constant: tbPadding).isActive = true
                item.bottomAnchor.constraint(greaterThanOrEqualTo: superview.bottomAnchor, constant: tbPadding).isActive = true
                item.widthAnchor.constraint(equalTo: superview.widthAnchor, constant: -(ltPadding * 2) - arrowPadding).isActive = true
                item.heightAnchor.constraint(equalTo: superview.heightAnchor, constant: -(tbPadding * 2)).isActive = true
            } else if arrowDirection.rawValue == 8 { //.right {
                item.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: ltPadding).isActive = true
                item.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: ltPadding + arrowPadding).isActive = true
                item.topAnchor.constraint(equalTo: superview.topAnchor, constant: tbPadding).isActive = true
                item.bottomAnchor.constraint(greaterThanOrEqualTo: superview.bottomAnchor, constant: tbPadding).isActive = true
                item.widthAnchor.constraint(equalTo: superview.widthAnchor, constant: -(ltPadding * 2) - arrowPadding).isActive = true
                item.heightAnchor.constraint(equalTo: superview.heightAnchor, constant: -(tbPadding * 2)).isActive = true
            } else {
                // center if no arrows
                item.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: ltPadding).isActive = true
                item.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: ltPadding).isActive = true
                item.topAnchor.constraint(equalTo: superview.topAnchor, constant: tbPadding).isActive = true
                item.bottomAnchor.constraint(greaterThanOrEqualTo: superview.bottomAnchor, constant: tbPadding).isActive = true
                item.widthAnchor.constraint(equalTo: superview.widthAnchor, constant: -(ltPadding * 2)).isActive = true
                item.heightAnchor.constraint(equalTo: superview.heightAnchor, constant: -(tbPadding * 2)).isActive = true
            }
        }
    
        @objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
            self.dismiss(animated: true, completion: nil)
        }
    
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            UIViewController.existingPopover = nil
    
            // restore dimmed sendingViewController
            UIView.animate(withDuration: AntimationDuration.fadeInOut.rawValue, animations: {
                self.sendingViewController?.view.alpha = 1
            })
        }
    }
    

    您还需要 AlwaysPresentAsPopover 类:

    import Foundation
    import UIKit
    
    class AlwaysPresentAsPopover : NSObject, UIPopoverPresentationControllerDelegate {
    
        // `sharedInstance` because the delegate property is weak - the delegate instance needs to be retained.
        private static let sharedInstance = AlwaysPresentAsPopover()
    
        private override init() {
            super.init()
        }
    
        func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
            return .none
        }
    
        static func configurePresentation(forController controller : UIViewController) -> UIPopoverPresentationController {
            controller.modalPresentationStyle = .popover
            let presentationController = controller.presentationController as! UIPopoverPresentationController
            presentationController.delegate = AlwaysPresentAsPopover.sharedInstance
            return presentationController
        }
    }
    

    以及扩展功能:

    extension UIViewController {
        static var existingPopover: PopoverViewController?
    
        func showPopover(message: String, sourceView: UIView, sourceRect: CGRect, arrowDirection: UIPopoverArrowDirection = [], passthroughViews: [UIView]? = []) {
            DispatchQueue.main.async {
    
                // init view controller
                let storyboard = UIStoryboard(name: "Main", bundle: nil)
                let controller = storyboard.instantiateViewController(withIdentifier: "popover") as! PopoverViewController
                controller.message = message
                controller.sendingViewController = self
                controller.arrowDirection = arrowDirection
    
                // set preferred size
                let margins = CGPoint(x: 20, y: 14)
                let textSize = message.size(width: 220)
                let adjustedSize = CGSize(width: textSize.width + (margins.x * 2), height: textSize.height + (margins.y * 2))
                controller.preferredContentSize = adjustedSize
    
                // create view controller as popover style presentation instead of a normal view controller
                let presentationController = AlwaysPresentAsPopover.configurePresentation(forController: controller)
                presentationController.sourceView = sourceView
                presentationController.sourceRect = sourceRect
                presentationController.permittedArrowDirections = arrowDirection
    
                // passthroughViews are views that can be tapped without dismissing the popover
                if let views = passthroughViews {
                    presentationController.passthroughViews = views
                }
    
                // if there is an existing presenting view controller, dismiss it before presenting this popover
                if let existing = UIViewController.existingPopover {
                    existing.dismiss(animated: true, completion: {
                        self.present(controller, animated: true, completion: {
                            UIViewController.existingPopover = controller
                        })
                    })
                } else {
                    self.present(controller, animated: true, completion: {
                        UIViewController.existingPopover = controller
                    })
                }
            }
        }
    }
    extension String {
        func size(width:CGFloat = 220.0, font: UIFont = UIFont.systemFont(ofSize: 17.0, weight: .regular)) -> CGSize {
            let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
            label.numberOfLines = 0
            label.lineBreakMode = NSLineBreakMode.byWordWrapping
            label.font = font
            label.text = self
    
            label.sizeToFit()
    
            return CGSize(width: label.frame.width, height: label.frame.height)
    
            let squared = square(5)
            print(squared) // prints 25
        }
    
        func square(_ number: Int) -> Int {
            return number * number
        }
    }
    

    【讨论】:

    • 是否可以在右侧 BarButtonItem 上添加弹出框?看来我无法计算它的矩形。
    • 例如,如果你有一个堆栈视图,里面有 4 个标签。你会如何调整它的大小?
    • 据我所知,自 iOS 11 以来,无法为正确的 UIBarButtonItem 获取 UIView。但是,您可以这样做以显示导航栏下方距右边距 40 点的弹出框。这也将允许用户在不关闭弹出框的情况下仍然点击导航栏,如果您不希望这样做,请删除 passthroughViews 参数。我已经用代码更新了我的答案。
    • 如果您想在堆栈视图中的一个标签附近放置一个弹出框,请使用标签的视图作为源视图。
    • 如果它解决了您的问题,请不要忘记接受这个答案。 :-)
    猜你喜欢
    • 1970-01-01
    • 2020-12-06
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-23
    • 1970-01-01
    • 2020-06-26
    相关资源
    最近更新 更多