[英]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.