SwiftUI PhotosPicker how to clear the list of selected items after the selection is complete

145 Views Asked by At

I am using SwiftUI's PhotosPicker in my application and am trying to find a way to ensure that the next time the user opens PhotosPicker, the number of selected elements is zero.

Simply hare is the code:

PhotosPicker(
            selection: $selectedMediaitems,
            selectionBehavior: .ordered,
            photoLibrary: .shared()
        ) {
            Image(.addIcon)
                .resizable()
                .frame(width: 60, height: 60)
        }

And as I understood I need to set selectedMediaitems = [] somewhere, but I don't understand where and when. If I put this right in function that observes changes on selectedMediaitems I get an infinite function call loop.

Here how I observe changes on selectedMediaitems (this property is inside ViewModel so I made a combine subscription)

$selectedMediaitems
        .filter { !$0.isEmpty } // I tried to skip emits that clear array but this way items are still selected in PhotosPicker
        .sink(receiveCompletion: {_ in }) { [weak self] items in
            self?.saveSelectedMediaFilesFile(items)
            self?.deleteFromLibraryItemIds = items.map { $0.itemIdentifier }
            //self?.selectedMediaitems = [] // if put it here without .filter { !$0.isEmpty } it will call itself till run time exc.
        }
        .store(in: &cancellableBag)
2

There are 2 best solutions below

2
On

You can actually do that immediately after you use those images. I will provide you with a sample project. We first launch the app, we then click on the add button to select photos. We select 1 or more photos. When we do that we can click on display photos, It will display the photos and also make it selectedMediaitems = []. So that when we select photos again, we will not have any pre selected photos.

*import SwiftUI
import Photos
import PhotosUI
struct ContentView: View {
    @State var selectedMediaitems:[PhotosPickerItem]
    @State var selectedImages = [Image]()
    
    
   
    func loadImages() {
        for item in selectedMediaitems {
            item.loadTransferable(type: Data.self) { result in
                DispatchQueue.main.async {
                    switch result {
                    case .success(let data):
                        if let data = data, let image = UIImage(data: data) {
                            self.selectedImages.append(Image(uiImage: image))
                        } else {
                            print("No image data available or image conversion failed.")
                        }
                    case .failure(let error):
                        print("Error loading image: \(error.localizedDescription)")
                    }
                    selectedMediaitems = []
                }
            }
        }
        
    }
    
    var body: some View {
        VStack{
            PhotosPicker(
                selection: $selectedMediaitems,
                selectionBehavior: .ordered,
                photoLibrary: .shared()
            ) {
                Image(systemName: "plus")
                    .resizable()
                    .frame(width: 60, height: 60)
            }
            
            Button(action: {
               loadImages()
            }, label: {
                Text("Display image")
            })
            
            List {
                ForEach(0..<selectedImages.count, id: \.self) { index in
                   selectedImages[index]
                        .resizable()
                        .frame(width: 100, height: 100)
                }
            }
        }
        
    }
}
#Preview {
    ContentView(selectedMediaitems: [])
}*
0
On

I don't have a solution to the combine error, but you can do it just fine without it. And I think thats what most are looking for when searching for this question.

The approach was correct, just clear the array and check if its empty before doing your stuff with it, if it changed.

struct TestView74: View {

@State private var showPhotoPicker = false
@State private var images: [UIImage] = []
@State private var selectedItems: [PhotosPickerItem] = []

var body: some View {
    VStack {
        ScrollView(.horizontal) {
            HStack {
                ForEach(images, id: \.self) { image in
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFill()
                        .frame(width: 80, height: 80)
                        .clipShape(RoundedRectangle(cornerRadius: 8))
                }
                
                Button {
                    showPhotoPicker.toggle()
                } label: {
                    RoundedRectangle(cornerRadius: 8)
                        .foregroundStyle(Color(uiColor: .secondarySystemBackground))
                        .frame(width: 80, height: 80)
                        .overlay {
                            Image(systemName: "plus")
                                .imageScale(.large)
                        }
                }
            }
        }
    }
    .photosPicker(isPresented: $showPhotoPicker, selection: $selectedItems)
    .onChange(of: selectedItems) { _ in       
        if selectedItems.count == 0 {           // <-- Relevant Part
            return
        }
        
        selectedItems.forEach { item in
            item.loadTransferable(type: Data.self) { result in
                switch result {
                case .success(let imageData):
                    if let imageData, let image = UIImage(data: imageData) {
                        self.images.append(image)
                    } else {
                        print("No supported content type found.")
                    }
                case .failure(let error):
                    print(error)
                }
            }
        }
        
        selectedItems.removeAll()           // <-- Relevant Part
    }
}

}

Just change the selectedItems.forEach { item in block for your custom functions in your ViewModel.

And try to post a real minimal reproducible example next time.