I am working on this convertion and tried many solutions (extensions and methods) as there are so many questions and answers related to this but nothing helped like I have tried following solutions but didn't helped
Tried Solutions
https://stackoverflow.com/a/64005395/15023395
https://stackoverflow.com/a/41288197/15023395
Below is taken from 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)
// }
}
}
}
This solution helped but I don't want to add image as subview of 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)
}
What I want to do ???
I have a struct which extends swiftUI View and I have a design in it. Now I want to convert that swiftUI View into UIImage inside ViewController of storyboard that when my screen loads and viewDidLoad() function calls then system updates image of UIImageView in story board
Here is my SwiftUI code
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()
)
}
}
}
You can do this... but not in viewDidLoad() -- you have to wait at least until viewDidLayoutSubviews().
And, the view must be added to the view hierarchy -- but it can be removed as soon as we generate the image so it's never seen "on-screen."
Note: all "result" images here use:
a 240 x 200 image view
.contentMode = .center
green background so we can see the frame
and we give the UIImage generate from the SwiftUI ContentView a yellow background, because we will need to address some layout quirks.
So, to generate the image and set it to a UIImageView, we can do this:
// 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
}
}
}
using this imageFromContentView() func:
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
}
Result #1:
Notice the 20-pt yellow band at the top, and the content is not vertically centered... that's because the UIHostingController applies a safe area layout guide.
Couple options to get around that...
If we add this line:
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()
we get this result:
the rendered image now has 20-pts Top and Bottom "safe area" insets.
If we don't want any safe area insets, we can use this extension:
// 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)
}
}
}
and change the first line in our func to:
let swiftUIView = UIHostingController(rootView: ContentView(), ignoreSafeArea: true)
and we get this result:
Because the SwiftUI ContentView layout is using a zStack where its content (the "ring") exceeds its vertical bounds, the top and bottom of the ring is "clipped."
We can fix that either by changing the framing in ContentView:
or by increasing the frame height of the loaded view, like this for example:
// 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
Here's a complete implementation (using your unmodified 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)
}
}
}
Related
I'm trying to build and integrate to a custom UIViewController with a tapRecognizer and an UITextView.
The issue I face is that the UITextView display part of the text off screen and I don't know what is causing this! I expect something simple or maybe including more classes but I haven't found anything pointing me in the right direction yet.
The size / position of a UITextView is working well when using UIViewRepresentable and a UITextView but not with a UIViewController / UIViewControllerRepresentable and a UITextView inside it for some reasons (see example bellow with both for comparison).
Here is the code of the test app I have:
import SwiftUI
import UIKit
struct TestingView: View {
var body: some View {
VStack {
WrappedUIViewController().padding()
Spacer()
WrappedUITextView(myText: "This is a long text to see if the word wrap work in this case better I hope so but I don't know. Is it? I hope it is, ! Do you? I do! Hope you do too, do you?").padding()
Spacer()
}
}
}
struct WrappedUITextView: UIViewRepresentable {
let myText: String
func makeUIView(context: Context) -> UITextViewPlus {
let view = UITextViewPlus()
view.isScrollEnabled = true
view.isEditable = false
view.isUserInteractionEnabled = true
view.font = UIFont(name: "Time New Roman", size: 20)
view.isOpaque = false
view.text = "WrappedUITextView \(myText)"
return view
}
func updateUIView(_ uiView: UITextViewPlus, context: Context) {}
}
struct WrappedUIViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = CustomUIViewController
func makeUIViewController(context: Context) -> CustomUIViewController {
return CustomUIViewController()
}
func updateUIViewController(_ uiViewController: CustomUIViewController, context: Context) {}
}
class CustomUIViewController: UIViewController {
var textView: UITextView = UITextView()
var tapGesture: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
self.textView.addGestureRecognizer(tapGesture)
self.textView.isEditable = false
self.textView.isSelectable = false
self.textView.clipsToBounds = true
self.textView.text = """
This is some text that will need to be displayed on multiple lines as it's longer than the screen size, will it wrapp correctly?
This is a great testing app
This is the end of this textView content
Here is a Potato
"""
self.textView.backgroundColor = UIColor.green
self.textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.textView.textContainer.lineBreakMode = .byWordWrapping
view.addSubview(self.textView)
}
#objc func handleTap(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: textView)
let position: CGPoint = CGPoint(x: location.x, y: location.y)
let tapPosition: UITextPosition = textView.closestPosition(to: position)!
guard let textRange: UITextRange = textView.tokenizer.rangeEnclosingPosition(tapPosition, with: UITextGranularity.word, inDirection: UITextDirection(rawValue: 1)) else { return }
let tappedWord: String = textView.text(in: textRange) ?? ""
print("tapped word -> \(tappedWord)")
}
override func viewWillLayoutSubviews() {
textView.sizeThatFits(self.view.bounds.size)
textView.sizeToFit()
}
}
The green textView is not wrapping the text properly or has a size that goes off screen for some reasons:
I'm new to swift and IOS so I would not be surprised if I do something wrong, any help is welcome!
EDIT: in case the code of the other file would help resolve this (I doubt it though):
import SwiftUI
#main
struct testAppApp: App {
var body: some Scene {
WindowGroup {
Text("Test App")
TestingView()
}
}
}
I have made changes to you CustomViewController class and added constraints to your textview to allow it to align properly. Methods and lines of code i have added are commented
You have to tell Auto Layout how you want your view to be aligned and positioned otherwise everything will be chaotic.
class CustomUIViewController: UIViewController {
var textView: UITextView = UITextView()
var tapGesture: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
self.textView.addGestureRecognizer(tapGesture)
self.textView.isEditable = false
self.textView.isSelectable = false
self.textView.clipsToBounds = true
self.textView.text = """
This is some text that will need to be displayed on multiple lines as it's longer than the screen size, will it wrapp correctly?
This is a great testing app
This is the end of this textView content
Here is a Potato
"""
self.textView.backgroundColor = UIColor.green
self.textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.textView.textContainer.lineBreakMode = .byWordWrapping
self.textView.translatesAutoresizingMaskIntoConstraints = false //ADDED: allows view to obey Auto Layout constraints.
view.addSubview(self.textView)
addConstraints() //ADDED: method for invoking constraints
}
func addConstraints(){
let margins = view.layoutMarginsGuide //safe area layout - esp for devices with notch
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: margins.topAnchor), //textview's top anchor = that of parent view
textView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
textView.heightAnchor.constraint(equalToConstant: 200), //gave textview a constant height
textView.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
])
}
#objc func handleTap(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: textView)
let position: CGPoint = CGPoint(x: location.x, y: location.y)
let tapPosition: UITextPosition = textView.closestPosition(to: position)!
guard let textRange: UITextRange = textView.tokenizer.rangeEnclosingPosition(tapPosition, with: UITextGranularity.word, inDirection: UITextDirection(rawValue: 1)) else { return }
let tappedWord: String = textView.text(in: textRange) ?? ""
print("tapped word -> \(tappedWord)")
}
override func viewWillLayoutSubviews() {
textView.sizeThatFits(self.view.bounds.size)
textView.sizeToFit()
}
}
After multiple iterations I managed to get something that would work, actually 2 options to solve this:
Changing "view.addSubview(self.textView)" to "view. = self.textView" in viewDidLoad. I'm not sure what this change but with this I have the UITextview bound ajusted by the system.
override func viewDidLoad() {
super.viewDidLoad()
...
view = self.textView
}
the second and most likely "better" solution: set a frame to my UITextView inside viewDidLoad. When "textView.sizeToFit()" the height is then addapted while the width is maintained to what I wanted:
override func viewDidLoad() {
super.viewDidLoad()
...
self.textView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 10000)
...
view.addSubview(self.textView)
}
I need to get a label's center.x directly aligned with the image inside a tabBar's imageView. Using the below code the label is misaligned, instead of the label's text "123" being directly over the bell inside the tabBar, it's off to the right.
guard let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { return }
guard let fourthTab = tabBarController?.tabBar.items?[3].value(forKey: "view") as? UIView else { return }
guard let imageView = fourthTab.subviews.compactMap({ $0 as? UIImageView }).first else { return }
guard let imageViewRectInWindow = imageView.superview?.superview?.convert(fourthTab.frame, to: keyWindow) else { return }
let imageRect = AVMakeRect(aspectRatio: imageView.image!.size, insideRect: imageViewRectInWindow)
myLabel.text = "123"
myLabel.textAlignment = .center // I also tried .left
myLabel.center.x = imageRect.midX
myLabel.center.y = UIScreen.main.bounds.height - 74
myLabel.frame.size.width = 50
myLabel.frame.size.height = 21
print("imageViewRectInWindow: \(imageViewRectInWindow)") // (249.99999999403948, 688.0, 79.00000000298022, 48.0)
print("imageRect: \(imageRect)") // (265.4999999955296, 688.0, 48.0, 48.0)
print("myLabelRect: \(myLabel.frame)") // (289.4999999955296, 662.0, 50.0, 21.0)
It might be a layout issue, as in, setting the coordinates before everything is laid out. Where do you call the code from? I was able to get it to work with the following, but got strange results with some if the functions you use, so cut out a couple of them. Using the frame of the tabview worked for me, and calling the coordinate setting from the view controller's viewDidLayoutSubviews function.
class ViewController: UIViewController {
var myLabel: UILabel = UILabel()
var secondTab: UIView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.addSubview(myLabel)
myLabel.textColor = .black
myLabel.text = "123"
myLabel.textAlignment = .center // I also tried .left
myLabel.frame.size.width = 50
myLabel.frame.size.height = 21
secondTab = tabBarController?.tabBar.items?[1].value(forKey: "view") as? UIView
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let secondTab = secondTab else {
return
}
myLabel.center.x = secondTab.frame.midX
myLabel.center.y = UIScreen.main.bounds.height - 70
}
}
Try below code to return a centreX by tabbar item index.
extension UIViewController {
func centerX(of tabItemIndex: Int) -> CGFloat? {
guard let tabBarItemCount = tabBarController?.tabBar.items?.count else { return nil }
let itemWidth = view.bounds.width / CGFloat(tabBarItemCount)
return itemWidth * CGFloat(tabItemIndex + 1) - itemWidth / 2
}
}
I have a SwiftUI app that includes a map. I want to capture an image of the map and display that image as a SwiftUI Image on another view. I have been unable to find any documentation on this. I tried two approaches at capturing and neither of them work. See the extensions below.
This is a simplified example:
ContentView:
struct ContentView: View {
#State private var showDetail: Bool = false
#State private var thumbImage: Image = Image(systemName: "gear")
var body: some View {
VStack {
Text("This is the ContentView")
if showDetail {
DetailMapView(thumbImage: $thumbImage)
}
if !showDetail {
Image(systemName: "gear")
.resizable()
.frame(width: 200, height: 200)
}
Button(action: {
self.showDetail.toggle()
}) {
Text("Tap for Map")
}
}
}
}
And the MapView:
struct DetailMapView: UIViewRepresentable {
typealias UIViewType = MKMapView
#Binding var thumbImage: Image
class Coordinator: NSObject, MKMapViewDelegate {
var parent: DetailMapView
init(_ parent: DetailMapView) {
self.parent = parent
}
}//coordinator
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.showsUserLocation = true
mapView.delegate = context.coordinator
// this does not work - it crashes
// let s = mapView.pb_takeSnapshot()
// self.thumbImage = Image(uiImage: s)
//this does not work either - it produces lots of console complaints
let t = mapView.screenshot
DispatchQueue.main.async {
self.thumbImage = Image(uiImage: t)
}
return mapView
}
}
extension UIView {
func pb_takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
extension UIView {
var screenshot: UIImage{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return UIImage() }
self.layer.render(in: context)
guard let screenShot = UIGraphicsGetImageFromCurrentImageContext() else { return UIImage() };
UIGraphicsEndImageContext()
return screenShot
}
}
Console output for the screenshot version:
[VKDefault] TextureAtlasPage: Atlas page destroyed with outstanding references.: Assertion with expression - _textureRefs == 0 : Failed in file - /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/VectorKit_Sim/VectorKit-1606.34.10.29.27/src/TextureAtlas.cpp line - 604
[VKDefault] TextureAtlasPage: Atlas page destroyed with outstanding references.: Assertion with expression - _textureRefs == 0 : Failed in file - /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/VectorKit_Sim/VectorKit-1606.34.10.29.27/src/TextureAtlas.cpp line - 604
Any guidance would be appreciated. Xcode 11.4 (11E146)
For others:
This works for me - forget the extensions above. The key is to use
mapViewDidFinishRenderingMap and that function needs to be inside the Coordinator class.
func mapViewDidFinishRenderingMap(_ mapView: MKMapView, fullyRendered: Bool) {
//setup whatever region you want to see :mapView.setRegion(region, animated: true)
let render = UIGraphicsImageRenderer(size: mapView.bounds.size)
let ratio = mapView.bounds.size.height / mapView.bounds.size.width
let img = render.image { (ctx) in
mapView.drawHierarchy(in: CGRect(x: 100, y: 100, width: 300, height: 300 * ratio), afterScreenUpdates: true)
}
DispatchQueue.main.async {
self.parent.thumbImage = Image(uiImage: img)
}
}
i would recommend to use MKMapSnapshotter from Apple because if you render the map manually yourself you always get the Apple symbol with it...which you "normally" do not want ;)
I refered to this question, and did add extension to UIView:
extension UIView {
// Using a function since `var image` might conflict with an existing variable
// (like on `UIImageView`)
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
}
Then i create very simple test view:
private class FakeTestView: BaseView {
override func prepare() {
backgroundColor = .blue
setup()
}
private func setup(){
let lbl = LabelSL.regular()
lbl.text = "LabelSL.regular()"
addSubview(lbl)
lbl.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
lbl.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
That view showing correctly when treated as UIView.
Finally, i tried:
let newSlideFrame = CGRect(x: CGFloat(i) * event.expectedWidth(),
y: 0,
width: event.expectedWidth(),
height: frame.size.height)
let imgView = UIImageView()
imgView.contentMode = .scaleAspectFit
imgView.frame = newSlideFrame
imgView.image = FakeTestView().asImage()
scroll.addSubview(imgView)
But there is nothing showing. Code from above work when i try to add UIView, or UIImageView with sample images.
I have multiple subviews on a parent view, and I need to convert the uiview to a uiimage but of only certain subviews. So I added a tag to the views I needed to take a screenshot of and added it to its own view, but when I try to screenshot that I get a black screen. However, when I use the regular parent view I get a photo with all the subviews.
let viewPic = UIView()
for subview in self.view.subviews {
if(subview.tag == 6) {
viewPic.addSubview(subview)
}
if(subview.tag == 8) {
viewPic.addSubview(subview)
}
}
let picImage = viewPic.getSnapshotImage() //This is a black screen
getSnapshotImage
extension UIView {
public func getSnapshotImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0)
self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
let snapshotItem: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return snapshotItem
}
}
First of all, your viewPic doesn't set the frame size so the default will be zero frame which may cause the issue.
Secondly, I tried to use your getSanpshotImage() on my sample project, but I always get the blank image.
Have a look at the demo code, I can get what you want (see the screenshot):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let viewPic = UIView()
viewPic.frame = self.view.frame
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
view1.backgroundColor = UIColor.red
viewPic.addSubview(view1)
let view2 = UIView()
view2.frame = CGRect(x: 0, y: 200, width: 100, height: 100)
view2.backgroundColor = UIColor.blue
viewPic.addSubview(view2)
let picImage = viewPic.convertToImage() //This is a black screen
print(picImage)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension UIView {
func convertToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}