1

I found this code for taking a snapshot of a view in SwiftUI, and also found this gist for how to bring up UIActivityController in SwiftUI. It works ok but the biggest issue I am having is when you tap share the UIActivityController is blank, if you tap share again it will work as expected but I can't figure out why it doesn't work the first time? If I change to a static image or text to share it works as expected? Any thoughts?

 import SwiftUI

//construct enum to decide which sheet to present:
enum ActiveSheet: String, Identifiable { // <--- note that it's now Identifiable
    case photoLibrary, shareSheet
    var id: String {
        return self.rawValue
    }
}

struct ShareHomeView: View {
    
    @State private var shareCardAsImage: UIImage? = nil
    
    @State var activeSheet: ActiveSheet? = nil // <--- now an optional property
    
    var shareCard: some View {
        ZStack {
            VStack {
                Spacer()
                LinearGradient(
                    gradient: Gradient(colors: [.black, .red]),
                    startPoint: .topLeading,
                    endPoint: .bottomTrailing
                )
                    .cornerRadius(10.0)
                    .padding(.horizontal)
                Spacer()
            }
            SubView()
                .padding(.horizontal)
            VStack {
                HStack {
                    HStack(alignment: .center) {
                        Image(systemName: "gamecontroller")
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(height: 40)
                            .padding(.leading)
                        VStack(alignment: .leading, spacing: 3) {
                            Text("My App")
                                .foregroundColor(.white)
                                .font(.headline)
                                .fontWeight(.bold)
                            Text("Wed 30 Mar 22")
                                .foregroundColor(.white)
                                .font(.headline)
                            // .fontWeight(.bold)
                        }
                    }
                    Spacer()
                }
                .padding([.leading, .top])
                Spacer()
            }
            
        } //End of ZStack
        .frame(height: 350)
    }
    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    Spacer()
                    Button {
                        self.activeSheet = .photoLibrary
                    } label: {
                        Image(systemName: "photo")
                            .resizable()
                            .scaledToFit()
                            .frame(height: 40)
                    }
                    .padding(.trailing)
                }
                //GeometryReader { geometry in
                shareCard
                // } //End of GeometryReader
                Button(action: {
                    
                    shareCardAsImage = shareCard.asImage()
                    self.activeSheet = .shareSheet
                    
                }) {
                    HStack {
                        Image(systemName: "square.and.arrow.up")
                            .font(.system(size: 20))
                        Text("Share")
                            .font(.headline)
                    }
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 50, maxHeight: 50)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(20)
                }
                .padding(.horizontal)
            } //End of Master VStack
            //sheet choosing view to display based on selected enum value:
            .sheet(item: $activeSheet) { sheet in // <--- sheet is of type ActiveSheet and lets you present the appropriate sheet based on which is active
                switch sheet {
                case .photoLibrary:
                    Text("TODO")
                case .shareSheet:
                    if let unwrappedImage = shareCardAsImage {
                        ShareSheet(photo: unwrappedImage)
                    }
                    
                }
            }
            //Needed to Wrap in a Navigation View and hide title so that dark mode would work, otherwise this sheet was always in the iPhone's light or dark mode
            .navigationBarHidden(true)
            .navigationTitle("")
        }
    }
}

struct RecoveryShareHomeView_Previews: PreviewProvider {
    static var previews: some View {
        ShareHomeView().preferredColorScheme(.dark)
        ShareHomeView().preferredColorScheme(.light)
    }
}


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)
            //            }
        }
    }
}






import LinkPresentation


//This code is from https://gist.github.com/tsuzukihashi/d08fce005a8d892741f4cf965533bd56

struct ShareSheet: UIViewControllerRepresentable {
    let photo: UIImage
    
    func makeUIViewController(context: Context) -> UIActivityViewController {
        //let text = ""
        //let itemSource = ShareActivityItemSource(shareText: text, shareImage: photo)
        
        let activityItems: [Any] = [photo]
        
        let controller = UIActivityViewController(
            activityItems: activityItems,
            applicationActivities: nil)
        
        return controller
    }
    
    func updateUIViewController(_ vc: UIActivityViewController, context: Context) {
        
    }
}



struct SubView: View {
    var body: some View {
        HStack {
            Image(systemName: "star")
            Text("Test View")
            Image(systemName: "star")
        }
        
        
    }
}
10
  • Use sheet(item:) instead of sheet(isPresented). See stackoverflow.com/questions/66162219/… Commented Apr 1, 2022 at 20:32
  • @jnpdx thanks my my problem isn't having multiple sheets, it's specifically related to the snapshot -> UIActivityController flow. Commented Apr 1, 2022 at 22:00
  • I understand what the issue is and don't think that it's related to multiple sheets. I think that the problem is that the sheet content is being rendered for your isPresented is set to true, so it has stale data. This is prevented by the solutions I've mentioned and linked to. Commented Apr 1, 2022 at 22:33
  • @jnpdx thanks I changed around to using sheet(item:) instead but still same behavior as far as the first tap it's blank, then the 2nd tap it works as expected. Commented Apr 1, 2022 at 22:49
  • Are you really using the item parameter to render? Can you edit to include the attempt? Commented Apr 1, 2022 at 22:50

1 Answer 1

3

Add [shareCardAsImage] so that the current value is captured inside sheet:

.sheet(item: $activeSheet) { [shareCardAsImage] sheet in

This is necessary because your item doesn't capture it explicitly, which is generally how item is used. You could also solve it by adding an associated value on your ActiveSheet that stores the image in item.

Sign up to request clarification or add additional context in comments.

1 Comment

That was it, thank you for the help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.