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")
        }
        
        
    }
}
GarySabo
  • 5,806
  • 5
  • 49
  • 124
  • Use `sheet(item:)` instead of `sheet(isPresented)`. See https://stackoverflow.com/questions/66162219/swiftui-switch-sheet-on-enum-does-not-work/66162319#66162319 – jnpdx Apr 01 '22 at 20:32
  • @jnpdx thanks my my problem isn't having multiple sheets, it's specifically related to the snapshot -> UIActivityController flow. – GarySabo Apr 01 '22 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. – jnpdx Apr 01 '22 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. – GarySabo Apr 01 '22 at 22:49
  • Are you really *using* the `item` parameter to render? Can you edit to include the attempt? – jnpdx Apr 01 '22 at 22:50
  • @jnpdx yes here's a gist https://gist.github.com/gesabo/3a1607edc6cb5e8b189f92a3c6c7523c – GarySabo Apr 01 '22 at 23:10
  • Tried your original code and it worked fine for me (once I removed the parts that weren't included so they didn't compile) – jnpdx Apr 01 '22 at 23:16
  • @jnpdx it works on the first tap? On an iPhone 13 Mini or simulator, the first tap brings up and empty white modal. Swipe it down to dismiss the press share again and it works as expected. ‍♂️ – GarySabo Apr 03 '22 at 22:58
  • Works on first tap for me. Perhaps your surrounding code is different? Including a [mre] would guarantee that our setups are the same. – jnpdx Apr 03 '22 at 23:10
  • @jnpdx I've updated the code in my question so you should be able to drop it in a project and see the same behavior. – GarySabo Apr 04 '22 at 00:07

1 Answers1

2

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.

jnpdx
  • 45,847
  • 6
  • 64
  • 94