swiftswiftuiswiftui-sharelink

How can I export a specific portion of a SwiftUI view and retain all styling and layout using ShareLink?


The goal is to utilize SwiftUI ShareLink to render, and share a specific portion of a SwiftUI View to Messages, Instagram, and other social media applications. My question is, what is the appropriate method to do so and retain all styling of that portion of the view?

struct SelectedEntryShareView: View {
    let title: String
    let entry: String
    let entryDate: Date
    @State private var renderedImage = Image("")

    var body: some View {
        VStack {
            Spacer()
            content
            Spacer()
        }
        .frame(maxWidth: .infinity)
        .background(Color("Base"))
        .safeAreaInset(edge: .bottom) {
            shareButtons
        }
        .task {
            if !title.isEmpty && !entry.isEmpty {
                renderedImage = renderContent(content: content)
            }
        }
    }
}

extension SelectedEntryShareView {
    var content: some View {
        ScrollView {
            VStack {
                Text(entryDate.formatted(date: .abbreviated, time: .shortened))
                    .font(.callout)
                    .fontWeight(.semibold)
                    .foregroundStyle(.accent)
                    .padding(.top)
                Text(title)
                    .font(.title)
                    .bold()
                    .padding(.bottom, 5)
                Text(entry)
                    .frame(alignment: .leading)
                SelectedEntryShareViewFooter()
            }
            .padding(.horizontal)
        }
        .frame(minWidth: 300, maxWidth: 400, minHeight: 200, maxHeight: 500)
        .background {
            RoundedRectangle(cornerRadius: 8)
                .foregroundStyle(Color("Highlight"))
        }
    }
    var shareButtons: some View {
        HStack {
            ShareLink(item: renderedImage, preview: SharePreview(title, image: renderedImage)) {
                Image(systemName: "message.circle")
                    .padding()
                    .foregroundStyle(.white)
                    .background {
                        RoundedRectangle(cornerRadius: 8)
                            .foregroundStyle(.secondary)
                    }
            }
            
            Button {
                
            } label: {
                Image(systemName: "ellipsis.circle")
                    .padding()
                    .foregroundStyle(.white)
                    .background {
                        RoundedRectangle(cornerRadius: 8)
                            .foregroundStyle(.secondary)
                    }
            }
        }
        .font(.title)
    }
    
    @MainActor func renderContent(content: some View) -> Image {
        let renderer = ImageRenderer(content: content)
        guard let image = renderer.uiImage else { fatalError() }
        return Image(uiImage: image)
    }
}

As you can see, I have a function which renders the content into a UIImage, then converts it to an Image. I wasn't sure what to do to pass the image to the ShareLink so I opted for a @State variable which is then changed. The current method leaves me with a blank rectangle shown below. I have also included a picture of what I want my desired output to be similar to.

Current OutputDesired Outcome


Solution

  • Using this Hacking with Swift article. I was able to determine that I needed to add @MainActor to the view I was attempting to use ImageRenderer on. This allowed me to then call my asynchronous ImageRenderer function directly inside of the ShareLink.