[英]How to trigger auto layout offscreen without adding the view to view hierarchy
My scenario is that I need to generate PNG Image data from an UIView whose layout I will setup myself.我的场景是我需要从 UIView 生成 PNG 图像数据,我将自行设置其布局。 I create an UIView viewA in memory, which I will use to create PNG data.
我在 memory 中创建了一个 UIView viewA,我将使用它来创建 PNG 数据。 This happens in a framework I'm working on, meaning that my viewA can only exist in memory and will never get attached to view hierarchy/go on screen.
这发生在我正在处理的框架中,这意味着我的 viewA 只能存在于 memory 中,并且永远不会附加到视图层次结构/进入屏幕。
However, based on my current testing, autolayout can only be effective when the view is attached to view hierarchy so that the system can trigger layout pass and related events such as layoutSubview and updateConstraint.但是,根据我目前的测试,自动布局只有在视图附加到视图层次结构时才有效,以便系统可以触发布局传递和相关事件,例如 layoutSubview 和 updateConstraint。 Without being on screen, calls like setNeedsLayout and layoutSubview are ineffective because no layout pass will be run at all.
如果不在屏幕上,像 setNeedsLayout 和 layoutSubview 这样的调用是无效的,因为根本不会运行任何布局传递。
I can still manually setup frame of ViewA, but just wondering is there a way to trigger autolayout even when the view stays offscreen.我仍然可以手动设置 ViewA 的框架,但只是想知道即使视图不在屏幕上也能触发自动布局。
It would help if you provided a more concrete description of what you want to do with your "offscreen view."如果您提供更具体的描述来说明您想用“屏幕外视图”做什么,将会有所帮助。
However, here's an example of getting a UIImage
from a UIView
that has never been added to the view hierarchy (so it is "offscreen").但是,这里有一个从从未添加到视图层次结构中的
UIView
获取UIImage
的示例(因此它是“离屏”)。
First, the simple view subclass:一、简单的视图子类:
class MySimpleView: UIView {
let label: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.numberOfLines = 0
v.backgroundColor = .yellow
v.text = "Multiline Label"
return v
}()
let containerView: UIView = {
let v = UIView()
v.backgroundColor = .systemBlue
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
[label, containerView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
containerView.addSubview(label)
self.addSubview(containerView)
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: self.topAnchor, constant: 12.0),
containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 12.0),
containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -12.0),
containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12.0),
label.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20.0),
label.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
label.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),
// label max width: 240
label.widthAnchor.constraint(lessThanOrEqualToConstant: 240.0),
])
self.backgroundColor = .systemRed
}
}
It has a label - .numberOfLines = 0
and max-width of 240
- as a subview of a "container" view, which is a subview of itself.它有一个 label -
.numberOfLines = 0
和240
的最大宽度 - 作为“容器”视图的子视图,它是自身的子视图。 The label is constrained inside the "container" view, with 20-pts "padding" on all 4 sides. label 被限制在“容器”视图内,所有 4 个侧面都有 20 点“填充”。 The "container" view is constrained with 12-pts "padding" on all 4 sides.
“容器”视图在所有 4 面都受到 12 点“填充”的约束。
It looks like this to start:它看起来像这样开始:
Changing the label text to "This string will likely need to wrap onto two lines."将 label 文本更改为“此字符串可能需要换行成两行”。 and it looks like this:
它看起来像这样:
So far, pretty basic.到目前为止,非常基本。
To get a UIImage
of it, we can add this property:要获得它的
UIImage
,我们可以添加以下属性:
var image: UIImage {
get {
self.setNeedsLayout()
self.layoutIfNeeded()
let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
return renderer.image { _ in
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
}
}
set {}
}
By including:通过包括:
self.setNeedsLayout()
self.layoutIfNeeded()
we can get the view to update itself, even if it's not in the view hierarchy.我们可以让视图自行更新,即使它不在视图层次结构中。
Here's the completed class, along with an example controller:这是完整的 class 以及示例 controller:
class MySimpleView: UIView {
var image: UIImage {
get {
self.setNeedsLayout()
self.layoutIfNeeded()
let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
return renderer.image { _ in
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
}
}
set {}
}
let label: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.numberOfLines = 0
v.backgroundColor = .yellow
v.text = "Multiline Label"
return v
}()
let containerView: UIView = {
let v = UIView()
v.backgroundColor = .systemBlue
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
[label, containerView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
containerView.addSubview(label)
self.addSubview(containerView)
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: self.topAnchor, constant: 12.0),
containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 12.0),
containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -12.0),
containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12.0),
label.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20.0),
label.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
label.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),
// label max width: 240
label.widthAnchor.constraint(lessThanOrEqualToConstant: 240.0),
])
self.backgroundColor = .systemRed
}
}
class OffScreenTestViewController: UIViewController {
let onScreentestStrings: [String] = [
"Short String",
"A bit longer String",
"This string will likely need to wrap onto two lines.",
"This string is going to be really, really long, and will almost certainly need to wrap onto more than two lines.",
]
let offScreentestStrings: [String] = [
"Off-screen String",
"A bit longer Off-screen String",
"Off-screen string will likely need to wrap onto two lines.",
"This Off-screen string is going to be really, really long, and will almost certainly need to wrap onto more than two lines.",
]
var onScreenIDX: Int = 0
var offScreenIDX: Int = 0
let onScreenTestView = MySimpleView()
let offScreenTestView = MySimpleView()
let resultsImageView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleAspectFit
v.backgroundColor = .systemGreen
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
onScreenTestView.translatesAutoresizingMaskIntoConstraints = false
resultsImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(onScreenTestView)
view.addSubview(resultsImageView)
let stack: UIStackView = {
let v = UIStackView()
v.spacing = 20
v.distribution = .fillEqually
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
["On Screen", "Off Screen"].forEach { title in
let b = UIButton()
b.backgroundColor = .systemBlue
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
b.setTitle(title, for: [])
b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
stack.addArrangedSubview(b)
}
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
onScreenTestView.topAnchor.constraint(equalTo: stack.bottomAnchor, constant: 20.0),
onScreenTestView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
resultsImageView.topAnchor.constraint(equalTo: onScreenTestView.bottomAnchor, constant: 20.0),
resultsImageView.widthAnchor.constraint(equalToConstant: 240.0),
resultsImageView.heightAnchor.constraint(equalTo: resultsImageView.widthAnchor),
resultsImageView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
// needs this, even though we're not adding it to the view hierarchy
offScreenTestView.translatesAutoresizingMaskIntoConstraints = false
// just to make it really, really clear that the
// off-screen view is being used to generate the image
offScreenTestView.label.backgroundColor = .blue
offScreenTestView.label.textColor = .yellow
if let font = UIFont(name: "SnellRoundhand-Black", size: 22.0) {
offScreenTestView.label.font = font
}
offScreenTestView.containerView.backgroundColor = .systemYellow
offScreenTestView.backgroundColor = .systemOrange
}
@objc func btnTapped(_ btn: UIButton) {
guard let t = btn.currentTitle else { return }
if t == "On Screen" {
onScreenTestView.label.text = onScreentestStrings[onScreenIDX % onScreentestStrings.count]
onScreenIDX += 1
} else {
offScreenTestView.label.text = offScreentestStrings[offScreenIDX % offScreentestStrings.count]
let img = offScreenTestView.image
resultsImageView.image = img
offScreenIDX += 1
}
}
}
When you run this, it will start out looking like this:当你运行它时,它会开始看起来像这样:
The green square is a UIImageView
set to .scaleAspectFit
with no image to begin with.绿色方块是一个
UIImageView
设置为.scaleAspectFit
没有图像开始。
Each time we tap the "On Screen" button, the text in the custom view's label will cycle through 4 sample strings:每次我们点击“On Screen”按钮时,自定义视图的 label 中的文本将循环显示 4 个示例字符串:
We've also created an instance of MySimpleView
called offScreenTestView
and changed some of its properties... label font and subview colors, just to make it abundantly clear it's not the same instance.我们还创建了一个名为
offScreenTestView
的MySimpleView
实例并更改了它的一些属性... label 字体和子视图 colors,只是为了清楚地表明它不是同一个实例。
Each tap on the "Off Screen" button will cycle through a similar set of strings for the label and set the green image view's .image
to offScreenTestView.image
:每次点击“Off Screen”按钮都会在 label 的一组类似字符串中循环,并将绿色图像视图的
.image
设置为offScreenTestView.image
:
All of the green image view updates are happening while offScreenTestView
- which is using constraints for its own sizing - has never been added to the view hierarchy.所有绿色图像视图更新都发生在
offScreenTestView
——它使用约束来调整自己的大小——从未添加到视图层次结构中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.