2

I am grabbing a screenshot of a sub-view in my SwiftUI View to immediately pass to a share sheet in order to share the image.

The view is of a set of questions from a text array rendered as a stack of cards. I am trying to get a screenshot of the question and make it share-able along with a link to the app (testing with a link to angry birds).

I have been able to capture the screenshot using basically Asperi's answer to the below question: How do I render a SwiftUI View that is not at the root hierarchy as a UIImage?

My share sheet launches, and I've been able to use the "Copy" feature to copy the image, so I know it's actually getting a screenshot, but whenever I click "Message" to send it to someone, or if I just leave the share sheet open, the app crashes.

The message says it's a memory issue, but doesn't give much description of the problem. Is there a good way to troubleshoot this sort of thing? I assume it must be something with how the screenshot is being saved in this case.

enter image description here

Here are my extensions of View and UIView to render the image:

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            
            layer.render(in: rendererContext.cgContext)
        }
    }
}


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()
        controller.view.backgroundColor = .clear
        
        let image = controller.view.asImage()
        controller.view.removeFromSuperview()
        return image
    }
}

Here's an abbreviated version of my view - the button is about halfway down, and should call the private function at the bottom that renders the image from the View/UIView extensions, and sets the "questionScreenShot" variable to the rendered image, which is then presented in the share sheet.

struct TopicPage: View {
    var currentTopic: Topic
    @State private var currentQuestions: [String]
    @State private var showShareSheet = false
    @State var questionScreenShot: UIImage? = nil
    
    var body: some View {
        GeometryReader { geometry in
                Button(action: {
                    self.questionScreenShot = render()
                    if self.questionScreenShot != nil {
                        self.showShareSheet = true
                    } else {
                        print("Did not set screenshot")
                    }

                }) {
                    Text("Share Question").bold()
                }
                .sheet(isPresented: $showShareSheet) {
                    ShareSheet(activityItems: [questionScreenShot!])
                }
        }
    }

    
    private func render() -> UIImage {
        QuestionBox(currentQuestion: self.currentQuestions[0]).asImage()
    }
}
Charlie Page
  • 541
  • 2
  • 17
  • i am looking for same thing though i want to just convert a stack and not the whole screen. good luck. – diogenes Oct 20 '20 at 01:59
  • What is your situation? I think if you pull out your stack as a separate View struct, this solution will work, like how "QuestionBox" is set in my example. I reset my phone and tried again and this issue was actually resolved. So this one might work. – Charlie Page Oct 21 '20 at 02:28
  • still working on it. will let you know when i figure it out. good luck. – diogenes Oct 22 '20 at 01:55
  • Hey @AndiAna where do you redeclaration error showing up? Wondering if you have another function with that name somewhere? – Charlie Page Feb 20 '21 at 19:04

1 Answers1

0

I've found a solution that seems to be working here. I start the variable where the questionScreenShot gets stored as nil to start:

    @State var questionScreenShot: UIImage? = nil

Then I just make sure to set it to 'render' when the view appears, which means it loads the UIImage so if the user clicks "Share Question" it will be ready to be loaded (I think there was an issue earlier where the UIImage wasn't getting loaded in time once the sharing was done).

It also sets that variable back to nil on disappear.

.onAppear {
                self.currentQuestions = currentTopic.questions.shuffled()
                self.featuredQuestion = currentQuestions.last!
                self.questionScreenShot = render()
            }
            .onDisappear {
                self.questionScreenShot = nil
                self.featuredQuestion = nil
            }
Charlie Page
  • 541
  • 2
  • 17