skip to Main Content

My code looks like this and there’s a concurrency warning when accessing the loadTransferable for the selectedPhoto from PhotosPicker

struct SampleCode: View {


    @State var showOptionSheet = false
    @State var showPhotoPicker = false
    @State var selectedPhoto: PhotosPickerItem?
    @State var showProgressView = false

    var body: some View {
        Button {
            showOptionSheet = true
        } label: {
            CustomAvatar(
                imageURL: guestProfile.avatarUrl,
                width: 40,
                height: 40
            )

        }.confirmationDialog("Upload new Avatar", isPresented: $showOptionSheet, titleVisibility: .visible, actions: {
            Button {
                showPhotoPicker = true
            } label: {
                Text("Photos Library")
            }

        })
        .photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhoto, matching: .images)
        .task(id: selectedPhoto) {
            showProgressView = true
            if let data = try? await selectedPhoto?.loadTransferable(type: Data.self) { // WARNING: Passing argument of non-sendable type 'PhotosPickerItem' outside of main actor-isolated context may introduce data races
                let result = await uploadUserAvatar(data: data)
                if let result {
                    guestProfile.avatarUrl = result
                }
            }
            showProgressView = false
        }
    }


}

We recently set the concurrency warnings to complete and I’m not really sure what to do with this

I tried to make PhotosPickerItem conform to Sendable but it produces a different warning and I’m not sure if this is safe

extension PhotosPickerItem: Sendable {} // Conformance to 'Sendable' must occur in the same source file as struct 'PhotosPickerItem'; use '@unchecked Sendable' for retroactive conformance

What’s the best way to solve this?

2

Answers


  1. This will work if you move the code inside a .onChange modifier instead and use Task

    .onChange(of: selectedPhoto) { _, photo in
        Task {
            showProgressView = true
            if let data = try? await photo?.loadTransferable(type: Data.self) { 
                let result = await uploadUserAvatar(data: data)
                if let result {
                    guestProfile.avatarUrl = result
                }
            }
            showProgressView = false
        }
    }
    

    Here we don’t pass selectedPhoto outside the main actor instead we have a constant local variable photo that we pass to the task.

    Login or Signup to reply.
  2. You can tell your task view modifier to use its own copy of selectedPhoto with a capture list:

    .task(id: selectedPhoto) { [selectedPhoto] in                  // note the capture list
        showProgressView = true
        if let data = try? await selectedPhoto?.loadTransferable(type: Data.self) {
            …
        }
        showProgressView = false
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search