GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

ShareLink Rendering Code executes before User "shares"

Forums > SwiftUI

This view for the results of a regatta can be shared using ShareLink. It is based on Paul's code for iOS16. The View takes 15sec to appear, and when I set breakpoints, I discovered that the two rendering methods (render() and ResultsAsText() ) are executing 3 times each before the View appears and before the .onAppear is triggerred, and then continuing to execute several times after the view appears. Is this normal? How can I remove the delays it is causing? Thanks

import SwiftUI
import SwiftData
import Observation

struct RegattaResults: View {
    @Environment(\.modelContext) private var modelContext
    @Environment(\.dismiss) var dismiss
    var regatta : Regatta

    @Query var theRaces : [Race]
    init(regatta: Regatta) {
        let id = regatta.persistentModelID
        let predicate = #Predicate<Race> { race in
            race.regatta?.persistentModelID == id
        }
        _theRaces = Query(filter: predicate, sort: [SortDescriptor(\.raceNum, order: .reverse)])
        self.regatta = regatta
    }

    var body: some View {
        Spacer()
        VStack {
            HStack {
                Button("Done") {
                    dismiss()
                }
                .buttonStyle(.bordered)
                .padding(5)
                Spacer()
                ShareLink("TXT", item: ResultsAsText(regatta: regatta))
                ShareLink("PDF", item: render())

            }
            Spacer()
            Text(regatta.name).font(.title2)
            Spacer()
            ResultsView(regatta: regatta)
        }
        .onAppear {
            regatta.processScores()
        }
    }

    @MainActor func render() -> URL {
        // 1: Render Results with some modifiers
        let myRenderer = ImageRenderer(content: ResultsViewForSharing(regatta: regatta)
            .modelContext(modelContext)
        )

        // 2: Save it to our documents directory
        let url = URL.documentsDirectory.appending(path: "regattaScores.pdf")

        // 3: Start the rendering process
        myRenderer.render { size, context in
            // 4: Tell SwiftUI our PDF should be the same size as the views we're rendering
            var box = CGRect(x: 0, y: 0, width: size.width, height: size.height)

            // 5: Create the CGContext for our PDF pages
            guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else {
                return
            }

            // 6: Start a new PDF page
            pdf.beginPDFPage(nil)

            // 7: Render the SwiftUI view data onto the page
            context(pdf)

            // 8: End the page and close the file
            pdf.endPDFPage()
            pdf.closePDF()
        }

        return url
    }

    func ResultsAsText(regatta: Regatta) -> String {

        var formatter: NumberFormatter {
            let formatter = NumberFormatter()
            formatter.minimumFractionDigits = 0
            formatter.maximumFractionDigits = 1
            return formatter
        }

        let showDesign = (regatta.skippers!.reduce(0) { $0 + $1.boatDesign.count}) > regatta.skippers!.count //ignore if only one stray space char in each skipper's design

        var scoreSheet = "\(regatta.name) : \(regatta.startDate.formatted(date: .long, time: .omitted))\n"
        if regatta.finalFleets == .Static && regatta.scoreType != .OneFleet  && regatta.scoreType != .TwoOfThree {
            scoreSheet += "Fleet\t"
        }
        scoreSheet += "Place\tSail\tName\t"
        if showDesign {
            scoreSheet += "Design\t"
        }
        scoreSheet += "Net\tGross\t"
        for race in theRaces {
            scoreSheet += "R \(race.raceNum)\t"
        }
        scoreSheet += "\n"
        for skip in (regatta.skippers!.sorted {$0.skipperRank < $1.skipperRank})  {
            if regatta.finalFleets == .Static && regatta.scoreType != .OneFleet  && regatta.scoreType != .TwoOfThree {
                let skipHeat = regatta.heatNames[skip.finalFleet]
                scoreSheet += "\(skipHeat)\t"
            }
            scoreSheet += "\(skip.regattaPlace)\t\(skip.sailNum)\t\(skip.firstName) \(skip.lastName)\t"

            if showDesign {
                scoreSheet += "\(skip.boatDesign)\t"
            }

            scoreSheet += "\(formatter.string(from: NSNumber(value: skip.regattaNet))!)\t\(formatter.string(from: NSNumber(value: skip.regattaGross))!)\t"

            for race in (theRaces) {
                guard let skipScore = race.raceScores!.first(where: {$0.skipper == skip && $0.raceRawScore > 0 }  ) else {
                    scoreSheet += "•\t"
                    continue
                }
                scoreSheet += "\(formatter.string(from: NSNumber(value: skipScore.raceRawScore))!)\t"
            }
            scoreSheet += "\n"
        }
        scoreSheet += "\nCreated: \(Date.now.formatted(date: .long, time: .omitted))"
        scoreSheet += "\nScoring by ezRegatta, a Broadreach Services Application"

        return scoreSheet
    }

}

   

I've had issues lately when I'm using the ShareLink to share a PDF I generate too. Although I've not noticed it doing this repeat calling you have, but my PDFs are single-page and light, so perhaps I've just not noticed the delays as it's so fast :/

I'd much rather they had made ShareLink a type of standard button with an actions closure. As you've noted: the PDF needing to exist made this all more annoying than it feels it needs to be. As it stands, a computed property/ func are the options I went with too.

Presumably it's not something simple like your .onAppear regatta.processScores func calling into the render() function etc in error? Or modifying the state objects on the page during processScores? That might trigger a view re-render.

   

Thanks for the input. It doesn't seem to be related to processScores, or the .onAppear in general, as the breakpoints in render trigger several times before it even gets to the .onAppear. This code actually stopped working in 17.4 until I found a suggestion to add the .modelContext modifier to the imageRenderer. I was not sure why that was needed as of 17.4, so I am a little in the dark as to how this code works and what to expect.

   

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's all new Paywall Editor allow you to remotely configure your paywall view without any code changes or app updates.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.