Добавление нескольких изображений в представление из библиотеки фотографий — SwiftUI

Я хочу добавить изображения из фотобиблиотеки телефона в макет коллажа, который я сделал. Сначала я сделал макет коллажа в виде отдельного представления в SwiftUI под названием CollageLayoutOne.

      import SwiftUI

struct CollageLayoutOne: View {
    
    var uiImageOne: UIImage
    var uiImageTwo: UIImage
    var uiImageThree: UIImage
    
    var body: some View {
        
        Rectangle()
            .fill(Color.gray)
            .aspectRatio(1.0, contentMode: .fit)
            .overlay {
                HStack {
                    Rectangle()
                        .fill(Color.gray)
                        .overlay {
                            Image(uiImage: uiImageOne)
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                        }
                        .clipped()
                    VStack {
                        Rectangle()
                            .fill(Color.gray)
                            .overlay {
                                Image(uiImage: uiImageTwo)
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                            }
                            .clipped()
                        Rectangle()
                            .fill(Color.gray)
                            .overlay {
                                Image(uiImage: uiImageThree)
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                            }
                            .clipped()
                    }
                }
                .padding()
            }
    }
}

Затем у меня есть отдельное представление (PageView), где я хочу показать представление CollageLayoutOne, и оно также содержит кнопку для доступа к библиотеке изображений.

      struct PageView: View {
    
    @State private var photoPickerIsPresented = false
    @State var pickerResult: [UIImage] = []
    
    var body: some View {
      NavigationView {
        ScrollView {
            if pickerResult.isEmpty {
                
            } else {
                CollageLayoutOne(uiImageOne: pickerResult[0], uiImageTwo: pickerResult[1], uiImageThree: pickerResult[2])
            }
            
            
        }
        .edgesIgnoringSafeArea(.bottom)
        .navigationBarTitle("Select Photo", displayMode: .inline)
        .navigationBarItems(trailing: selectPhotoButton)
        .sheet(isPresented: $photoPickerIsPresented) {
          PhotoPicker(pickerResult: $pickerResult,
                      isPresented: $photoPickerIsPresented)
        }
      }
    }
    
    @ViewBuilder
    private var selectPhotoButton: some View {
      Button(action: {
        photoPickerIsPresented = true
      }, label: {
        Label("Select", systemImage: "photo")
      })
    }
    
}

Моя проблема в том, что по какой-то неизвестной причине приложение вылетает каждый раз, когда я выбираю фотографии и пытаюсь их добавить. Если я делаю для всех трех, он работает нормально, но отображает только первую выбранную фотографию во всех трех местах. Также, если я начну со всех 3 как pickerResult[0]а затем изменить их на [0], [1], [2]во время предварительного просмотра он не падает и отображается правильно.

Я только начинаю со Swift и SwiftUI, так что извините, если это какая-то элементарная ошибка. Ниже я также добавляю свой код для PhotoPicker, который я получил из статьи, которую я нашел.

PhotoPicker.swift:

      import SwiftUI
import PhotosUI

struct PhotoPicker: UIViewControllerRepresentable {
  @Binding var pickerResult: [UIImage]
  @Binding var isPresented: Bool
  
  func makeUIViewController(context: Context) -> some UIViewController {
    var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
    configuration.filter = .images // filter only to images
    if #available(iOS 15, *) {
        configuration.selection = .ordered //number selection
    }
    configuration.selectionLimit = 3 // ignore limit
    
    let photoPickerViewController = PHPickerViewController(configuration: configuration)
    photoPickerViewController.delegate = context.coordinator
    return photoPickerViewController
  }
  
  func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
  
  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
  class Coordinator: PHPickerViewControllerDelegate {
    private let parent: PhotoPicker
    
    init(_ parent: PhotoPicker) {
      self.parent = parent
    }
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
      parent.pickerResult.removeAll()
      
      for image in results {
        if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
          image.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] newImage, error in
            if let error = error {
              print("Can't load image \(error.localizedDescription)")
            } else if let image = newImage as? UIImage {
              self?.parent.pickerResult.append(image)
            }
          }
        } else {
          print("Can't load asset")
        }
      }
      
      parent.isPresented = false
    }
  }
}

1 ответ

image.itemProvider.loadObjectявляется асинхронной функцией и загружает изображения одно за другим.

Когда первое изображение обработано, вы добавляете его в pickerResultи ваш pickerResult.isEmptyпроверка становится false, но ваш массив пока содержит только один элемент.

Безопаснее всего здесь проверить счет:

      if pickerResult.count == 3 {
    CollageLayoutOne(uiImageOne: pickerResult[0], uiImageTwo: pickerResult[1], uiImageThree: pickerResult[2])
}

Кроме того, в таких случаях рекомендуется дождаться завершения всех асинхронных запросов, прежде чем обновлять пользовательский интерфейс, например, так:

      var processedResults = [UIImage]()
var leftToLoad = results.count
let checkFinished = { [weak self] in
    leftToLoad -= 1
    if leftToLoad == 0 {
        self?.parent.pickerResult = processedResults
        self?.parent.isPresented = false
    }
}
for image in results {
    if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
        image.itemProvider.loadObject(ofClass: UIImage.self) { newImage, error in
            if let error = error {
                print("Can't load image \(error.localizedDescription)")
            } else if let image = newImage as? UIImage {
                processedResults.append(image)
            }
            checkFinished()
        }
    } else {
        print("Can't load asset")
        checkFinished()
    }
}
Другие вопросы по тегам