Nano Hash - криптовалюты, майнинг, программирование

Почему постоянно обновляется просмотр при добавлении фотографий из средства выбора фотографий в мою модель в SwiftUI?

У меня проблемы с пониманием комбайнов и издателей. У меня есть PhotoPicker, который выбирает одно или несколько изображений из библиотеки фотографий. Эти изображения следует добавить в мою основную модель данных. Это обрабатывается в моем модельном классе.

Однако я попадаю в бесконечный цикл, которого я не понимаю. View продолжает обновляться, а метод onReceive продолжает выполняться снова и снова, снова и снова визуализируя View. Ниже приведен пример кода (без основных данных), который ведет себя так же

Образец кода:

struct MyImage: Hashable {
    var id: String
    var uiImage: UIImage
}

class Model: ObservableObject {
    
    @Published var images: [MyImage] = []
    
    func add(uiImage: UIImage) {
        let id = UUID().uuidString
        let image = MyImage(id: id, uiImage: uiImage)
        self.images.append(image)
    }
    
}

struct ContentView: View {
    
    @StateObject var model = Model()
    
    @State var showPhotoPicker = false
    @State var pickerResult: [UIImage] = []
    
    var body: some View {
        
        VStack {
            
            ForEach(model.images, id: \.self) { image in
                Text(image.id)
            }
            
            Button(action: { showPhotoPicker.toggle() }) {
                Text("ADD")
            }
            .fullScreenCover(isPresented: $showPhotoPicker) {
                let config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
                PhotoPicker(configuration: config, pickerResult: $pickerResult)
            }
            
        }
        
        .onReceive(pickerResult.publisher, perform: { image in
            model.add(uiImage: image)
        })
    }
}

struct PhotoPicker: UIViewControllerRepresentable {
    
    @Environment(\.presentationMode)
    var presentationMode
    
    let configuration: PHPickerConfiguration
    
    @Binding var pickerResult: [UIImage]
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> PHPickerViewController {
        let controller = PHPickerViewController(configuration: configuration)
        controller.delegate = context.coordinator
        return controller
    }
    
    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
        
    }
    
    class Coordinator: PHPickerViewControllerDelegate {
      
        private let parent: PhotoPicker
        
        init(_ parent: PhotoPicker) {
            self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            
            self.parent.pickerResult = []
            
            results.forEach { (image) in
                
                if image.itemProvider.canLoadObject(ofClass: UIImage.self)  {
                    image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
                        if let error = error {
                            print(error.localizedDescription)
                        } else {
                            self.parent.pickerResult.append(newImage as! UIImage)
                        }
                    }
                    
                } else {
                    print("Loaded Assest is not a Image")
                }
            }
            
            self.parent.presentationMode.wrappedValue.dismiss()
            
        }
        
    }
    
}

Может кто-нибудь объяснить, что происходит?

29.11.2020

  • Что такое PhotoPicker? 30.11.2020
  • Это настраиваемая реализация PHPickerViewController в SwiftUI. Добавил выше. 30.11.2020

Ответы:


1

Минимальный воспроизводимый пример вашей проблемы:

struct ContentView: View {
    @State var pickerResult: [Int] = [1]
    
    var body: some View {
        Text("Test")
            .onReceive(pickerResult.publisher, perform: { image in
                self.pickerResult.append(1)
                print(self.pickerResult.count)
            })
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Итак, почему этот код заканчивается бесконечным циклом? Начнем с немного измененной версии:

struct ContentView: View {
    @State var pickerResult: [Int] = [1]
    
    var body: some View {
        Text("Test")
            .onReceive(pickerResult.publisher, perform: { image in
                print(image)
            })
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Как видите, издатель pickerResult выдаст значение (здесь 1), если массив не пуст. Изменение состояния на

@State var pickerResult: [Int] = [1, 2]

будет в случае, если издатель выдаст два значения (1 и 2).

А теперь возникает проблема: каждый раз, когда издатель передает значение, вызывается onReceive. И этот обратный вызов добавит значение в pickerResult, который, в свою очередь, сообщает SwiftUI перерисовать представление. Если представление перерисовано, издатель снова выдаст его значения. Это закончится бесконечным циклом.

Я настоятельно рекомендую разделить вашу точку зрения и логическую часть. Не используйте метод onReceive представления для выполнения бизнес-логики.

02.12.2020
  • Не могли бы вы поделиться образцом кода, как это сделать правильно? 04.12.2020

  • 2

    Мне удалось решить эту проблему с некоторыми изменениями. Не знаю, можно ли было сделать лучше. Но это работает.

    class Model: ObservableObject {
        
        @Published private(set) var images: [MyImage] = []
        
        func didSelectImages(images: [UIImage]) {
            images.forEach { (uiimage) in
                add(uiImage: uiimage)
            }
        }
        
        private func add(uiImage: UIImage) {
            let id = UUID().uuidString
            let image = MyImage(id: id, uiImage: uiImage)
            self.images.append(image)
        }
        
    }
    
    struct ContentView: View {
        
        @StateObject var model = Model()
        
        @State var showPhotoPicker = false
        
        var config: PHPickerConfiguration = {
            var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
            config.selectionLimit = 3
            return config
        }()
        
        var body: some View {
            
            VStack {
                
                ForEach(model.images, id: \.self) { image in
                    Text(image.id)
                }
                
                Button(action: { showPhotoPicker.toggle() }) {
                    Text("ADD")
                }
                .fullScreenCover(isPresented: $showPhotoPicker) {
                    PhotoPicker(configuration: config, completionHandler: model.didSelectImages)
                }
                
            }
            
        }
        
    }
    
    05.12.2020
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..