[英]How to convert VNRectangleObservation item to UIImage in SwiftUI
[英]How to convert SwiftUI View body to UIImage in ViewController
我正在进行这种转换并尝试了很多解决方案(扩展和方法),因为有很多与此相关的问题和答案但没有任何帮助,就像我尝试了以下解决方案但没有帮助
尝试过的解决方案
https://stackoverflow.com/a/64005395/15023395
https://stackoverflow.com/a/41288197/15023395
下面摘自https://stackoverflow.com/a/59333377/12299030
extension View {
func asImage() -> UIImage {
let controller = UIHostingController(rootView: self)
// locate far out of screen
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
controller.view.bounds = CGRect(origin: .zero, size: size)
controller.view.sizeToFit()
let image = controller.view.asImage()
controller.view.removeFromSuperview()
return image
}
}
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
// [!!] Uncomment to clip resulting image
// rendererContext.cgContext.addPath(
// UIBezierPath(roundedRect: bounds, cornerRadius: 20).cgPath)
// rendererContext.cgContext.clip()
// As commented by @MaxIsom below in some cases might be needed
// to make this asynchronously, so uncomment below DispatchQueue
// if you'd same met crash
// DispatchQueue.main.async {
layer.render(in: rendererContext.cgContext)
// }
}
}
}
这个解决方案有帮助,但我不想将图像添加为 superView 的子视图
func extractView(){
let hostView = UIHostingController(rootView: ContentView())
hostView.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
hostView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostView.view.widthAnchor.constraint(equalTo: view.widthAnchor),
hostView.view.heightAnchor.constraint(equalTo: view.heightAnchor),
]
self.view.addSubview(hostView.view)
self.view.addConstraints(constraints)
}
我想做的事???
我有一个扩展 swiftUI View 的结构,我在其中有一个设计。 现在我想将 swiftUI View 转换为 storyboard 的 ViewController 中的 UIImage,当我的屏幕加载和viewDidLoad()
function 调用时,系统会更新故事板中 UIImageView 的图像
这是我的 SwiftUI 代码
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack(alignment: .center){
Rectangle()
.frame(width: 200, height: 75)
.cornerRadius(10)
.foregroundColor(.white)
Circle()
.stroke(lineWidth:5)
.foregroundColor(.red)
.frame(width: 75, height: 75, alignment: .leading)
.background(
Image("tempimage")
.resizable()
)
}
}
}
你可以这样做......但不是在viewDidLoad()
中——你必须至少等到viewDidLayoutSubviews()
。
而且,必须将视图添加到视图层次结构中——但是一旦我们生成图像就可以将其删除,因此它永远不会在“屏幕上”显示。
注意:此处所有“结果”图像均使用:
240 x 200
图像视图.contentMode =.center
并且我们给UIImage
从 SwiftUI ContentView
生成黄色背景,因为我们需要解决一些布局问题。
因此,要生成图像并将其设置为UIImageView
,我们可以这样做:
// we will generate the image in viewDidLayoutSubview()
// but that can be (and usually is) called more than once
// so we'll use this to make sure we only generate the image once
var firstTime: Bool = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// we only want this to run once
if firstTime {
firstTime = false
if let img = imageFromContentView() {
imgView.image = img
}
}
}
使用此imageFromContentView()
函数:
func imageFromContentView() -> UIImage? {
let swiftUIView = UIHostingController(rootView: ContentView())
// add as chlld controller
addChild(swiftUIView)
// make sure we can get its view (safely unwrap its view)
guard let v = swiftUIView.view else {
swiftUIView.willMove(toParent: nil)
swiftUIView.removeFromParent()
return nil
}
view.addSubview(v)
swiftUIView.didMove(toParent: self)
// size the view to its content
v.sizeToFit()
// force it to layout its subviews
v.setNeedsLayout()
v.layoutIfNeeded()
// if we want to see the background
v.backgroundColor = .systemYellow
// get it as a UIImage
let img = v.asImage()
// we're done with it, so get rid of it
v.removeFromSuperview()
swiftUIView.willMove(toParent: nil)
swiftUIView.removeFromParent()
return img
}
结果#1:
请注意顶部 20 磅的黄色带,并且内容未垂直居中...这是因为UIHostingController
应用了安全区域布局指南。
解决这个问题的几个选项......
如果我们添加这一行:
view.addSubview(v)
swiftUIView.didMove(toParent: self)
// add same bottom safe area inset as top
swiftUIView.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: v.safeAreaInsets.top, right: 0)
// size the view to its content
v.sizeToFit()
我们得到这个结果:
渲染图像现在有 20 磅的顶部和底部“安全区域”插图。
如果我们不想插入任何安全区域,我们可以使用这个扩展:
// extension to remove safe area from UIHostingController
// source: https://stackoverflow.com/a/70339424/6257435
extension UIHostingController {
convenience public init(rootView: Content, ignoreSafeArea: Bool) {
self.init(rootView: rootView)
if ignoreSafeArea {
disableSafeArea()
}
}
func disableSafeArea() {
guard let viewClass = object_getClass(view) else { return }
let viewSubclassName = String(cString: class_getName(viewClass)).appending("_IgnoreSafeArea")
if let viewSubclass = NSClassFromString(viewSubclassName) {
object_setClass(view, viewSubclass)
}
else {
guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return }
guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return }
if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) {
let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
return .zero
}
class_addMethod(viewSubclass, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method))
}
objc_registerClassPair(viewSubclass)
object_setClass(view, viewSubclass)
}
}
}
并将我们的函数中的第一行更改为:
let swiftUIView = UIHostingController(rootView: ContentView(), ignoreSafeArea: true)
我们得到这个结果:
由于 SwiftUI ContentView
布局使用的是zStack
,其内容(“环”)超出了其垂直边界,因此环的顶部和底部被“截断”。
我们可以通过更改ContentView
中的框架来解决这个问题:
或者通过增加加载视图的框架高度,例如:
// size the view to its content
v.sizeToFit()
// for this explicit example, the "ring" extends vertically
// outside the bounds of the zStack
// so we'll add 10-pts height
v.frame.size.height += 10.0
这是一个完整的实现(使用未修改的ContentView
):
class ViewController: UIViewController {
let imgView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
imgView.contentMode = .center
imgView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imgView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// let's put the imageView 40-pts from Top
imgView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
// centered horizontally
imgView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// width: 240
imgView.widthAnchor.constraint(equalToConstant: 240.0),
// height: 200
imgView.heightAnchor.constraint(equalToConstant: 200.0),
])
// show the image view background so we
// can see its frame
imgView.backgroundColor = .systemGreen
}
// we will generate the image in viewDidLayoutSubview()
// but that can be (and usually is) called more than once
// so we'll use this to make sure we only generate the image once
var firstTime: Bool = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// we only want this to run once
if firstTime {
firstTime = false
if let img = imageFromContentView() {
imgView.image = img
}
}
}
func imageFromContentView() -> UIImage? {
let swiftUIView = UIHostingController(rootView: ContentView(), ignoreSafeArea: true)
// add as chlld controller
addChild(swiftUIView)
// make sure we can get its view (safely unwrap its view)
guard let v = swiftUIView.view else {
swiftUIView.willMove(toParent: nil)
swiftUIView.removeFromParent()
return nil
}
view.addSubview(v)
swiftUIView.didMove(toParent: self)
// size the view to its content
v.sizeToFit()
// for this explicit example, the "ring" extends vertically
// outside the bounds of the zStack
// so we'll add 10-pts height
v.frame.size.height += 10.0
// force it to layout its subviews
v.setNeedsLayout()
v.layoutIfNeeded()
// if we want to see the background
v.backgroundColor = .systemYellow
// get it as a UIImage
let img = v.asImage()
// we're done with it, so get rid of it
v.removeFromSuperview()
swiftUIView.willMove(toParent: nil)
swiftUIView.removeFromParent()
return img
}
}
// extension to remove safe area from UIHostingController
// source: https://stackoverflow.com/a/70339424/6257435
extension UIHostingController {
convenience public init(rootView: Content, ignoreSafeArea: Bool) {
self.init(rootView: rootView)
if ignoreSafeArea {
disableSafeArea()
}
}
func disableSafeArea() {
guard let viewClass = object_getClass(view) else { return }
let viewSubclassName = String(cString: class_getName(viewClass)).appending("_IgnoreSafeArea")
if let viewSubclass = NSClassFromString(viewSubclassName) {
object_setClass(view, viewSubclass)
}
else {
guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return }
guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return }
if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) {
let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
return .zero
}
class_addMethod(viewSubclass, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method))
}
objc_registerClassPair(viewSubclass)
object_setClass(view, viewSubclass)
}
}
}
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: frame.size)
return renderer.image { context in
layer.render(in: context.cgContext)
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.