PhotosPicker delay

struct ImageTest: View {
    
    @State private var selectedPhotos: [PhotosPickerItem] = []
    @State private var photosData: [Data] = []
    
    @State private var text: String = ""
    
    var body: some View {
        NavigationStack{
            Form {
                Section {
                    HStack {
                        PhotosPicker(selection: $selectedPhotos,
                                     maxSelectionCount: 10,
                                     matching: .images,
                                     photoLibrary: .shared()) {
                            AddPhotoIcon(numOfImages: photosData.count)
                                
                        }
                                     .task(id: selectedPhotos) {
                                         for selectedPhoto in selectedPhotos {
                                             if let data = try? await selectedPhoto.loadTransferable(type: Data.self) {
                                                 self.photosData.append(data)
                                             }
                                         }
                                         self.selectedPhotos = []
                                     }
                        
                        if !photosData.isEmpty {
                            ScrollView(.horizontal) {
                                HStack {
                                    ForEach(photosData, id: \.self) { data in
                                        if let image = UIImage(data: data) {
                                            Image(uiImage: image)
                                                .resizable()
                                                .scaledToFill()
                                                .frame(width: 50, height: 50)
                                        }
                                    }
                                }
                            }
                        }
                        
                        Spacer()
                    }
                }
                
                Section {
                    TextField("Any", text: $text)
                }
            }
        }
    }
}

Here with SwiftData I'm making the model that contains images and title. There is a delay when entering just ten-character text after selecting photos for like 2~3 seconds. In the debug the memory usage it soars to 700MB. How can I improve it?

Accepted Reply

The delay and memory usage spike you're experiencing are likely due to the process of loading and displaying high-resolution images directly in the SwiftUI view. Each time you pick an image, it's being loaded in its original size which consumes a large amount of memory, especially if you are selecting multiple images. Moreover, the ForEach loop rebuilds each time photosData changes, which also could be causing a delay.

Before appending the image data to photosData, reduce the image resolution to a reasonable size that fits your UI. And load the images in a background thread to prevent UI freezing. In addition, use Lazy Loading: In SwiftUI, you can use LazyHStack instead of HStack to load the images lazily.

For example :

struct ImageTest: View {
    
    @State private var selectedPhotos: [PhotosPickerItem] = []
    @State private var photosData: [UIImage] = []
    
    @State private var text: String = ""
    
    var body: some View {
        NavigationStack{
            Form {
                Section {
                    HStack {
                        PhotosPicker(selection: $selectedPhotos,
                                     maxSelectionCount: 10,
                                     matching: .images,
                                     photoLibrary: .shared()) {
                            AddPhotoIcon(numOfImages: photosData.count)
                        }
                        .task(id: selectedPhotos) {
                            for selectedPhoto in selectedPhotos {
                                if let data = try? await selectedPhoto.loadTransferable(type: Data.self) {
                                    if let image = UIImage(data: data) {
                                        self.photosData.append(image.resized(to: CGSize(width: 50, height: 50))!)
                                    }
                                }
                            }
                            self.selectedPhotos = []
                        }
                        
                        if !photosData.isEmpty {
                            ScrollView(.horizontal) {
                                LazyHStack {
                                    ForEach(photosData, id: \.self) { image in
                                        Image(uiImage: image)
                                            .resizable()
                                            .scaledToFill()
                                            .frame(width: 50, height: 50)
                                    }
                                }
                            }
                        }
                        
                        Spacer()
                    }
                }
                
                Section {
                    TextField("Any", text: $text)
                }
            }
        }
    }
}

extension UIImage {
    func resized(to size: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        self.draw(in: CGRect(origin: .zero, size: size))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage
    }
}

Replies

The delay and memory usage spike you're experiencing are likely due to the process of loading and displaying high-resolution images directly in the SwiftUI view. Each time you pick an image, it's being loaded in its original size which consumes a large amount of memory, especially if you are selecting multiple images. Moreover, the ForEach loop rebuilds each time photosData changes, which also could be causing a delay.

Before appending the image data to photosData, reduce the image resolution to a reasonable size that fits your UI. And load the images in a background thread to prevent UI freezing. In addition, use Lazy Loading: In SwiftUI, you can use LazyHStack instead of HStack to load the images lazily.

For example :

struct ImageTest: View {
    
    @State private var selectedPhotos: [PhotosPickerItem] = []
    @State private var photosData: [UIImage] = []
    
    @State private var text: String = ""
    
    var body: some View {
        NavigationStack{
            Form {
                Section {
                    HStack {
                        PhotosPicker(selection: $selectedPhotos,
                                     maxSelectionCount: 10,
                                     matching: .images,
                                     photoLibrary: .shared()) {
                            AddPhotoIcon(numOfImages: photosData.count)
                        }
                        .task(id: selectedPhotos) {
                            for selectedPhoto in selectedPhotos {
                                if let data = try? await selectedPhoto.loadTransferable(type: Data.self) {
                                    if let image = UIImage(data: data) {
                                        self.photosData.append(image.resized(to: CGSize(width: 50, height: 50))!)
                                    }
                                }
                            }
                            self.selectedPhotos = []
                        }
                        
                        if !photosData.isEmpty {
                            ScrollView(.horizontal) {
                                LazyHStack {
                                    ForEach(photosData, id: \.self) { image in
                                        Image(uiImage: image)
                                            .resizable()
                                            .scaledToFill()
                                            .frame(width: 50, height: 50)
                                    }
                                }
                            }
                        }
                        
                        Spacer()
                    }
                }
                
                Section {
                    TextField("Any", text: $text)
                }
            }
        }
    }
}

extension UIImage {
    func resized(to size: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        self.draw(in: CGRect(origin: .zero, size: size))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage
    }
}