简体   繁体   English

根据内容动态改变弹出框的大小

[英]Dynamically change the size of the popover depending on the content

I created a pop over for my UIBarButtonItem .我为我的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 are the constraints.是限制条件。 I have several labels placed in my stack view and they can vary in size.我在堆栈视图中放置了几个标签,它们的大小可能会有所不同。

I saw that it is possible to set a preferred explicit size for the UIViewController but this would make my layout static.我看到可以为UIViewController设置首选的显式大小,但这会使我的布局为 static。

How can I set a dynamic width and height for the popover depending on its content?如何根据其内容为弹出框设置动态宽度和高度?

I created an open-source snippet for this a few days ago.几天前,我为此创建了一个开源代码片段 It includes arrows and passThroughViews so that you can tap them without dismissing the popover, but neither are required parameters.它包括箭头和 passThroughViews,因此您可以在不关闭弹出框的情况下点击它们,但它们都不是必需的参数。

You can call it from any view controller using:您可以使用以下命令从任何视图 controller 调用它:

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])

Show a popover below where a right UIBarButtonItem would be in the nav bar:在导航栏中右侧 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)!)

The first part you need is the PopoverViewController class:您需要的第一部分是 PopoverViewController class:

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
        })
    }
}

You also need the AlwaysPresentAsPopover class:您还需要 AlwaysPresentAsPopover class:

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
    }
}

And the extension functions:以及扩展功能:

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
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM