PhotosPicker зависает на этапе загрузки (SwiftUI)

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

Это моя переносимая структура

struct Movie: Transferable {
    let url: URL

    static var transferRepresentation: some TransferRepresentation {
        FileRepresentation(contentType: .movie) { movie in
            SentTransferredFile(movie.url)
        } importing: { received in
            let copy = URL.documentsDirectory.appending(path: "movie.mp4")

            if FileManager.default.fileExists(atPath: copy.path()) {
                try FileManager.default.removeItem(at: copy)
            }

            try FileManager.default.copyItem(at: received.file, to: copy)
            return Self.init(url: copy)
        }
    }
}

Это мой код SwiftUI

struct GalleryView: View {
    
    
    @State private var selectedItem: PhotosPickerItem?

    ....

    // Somewhere in ZStack of GalleryView
    HStack {
                
        Spacer()
                 
        VStack {
                    
            Spacer()
                    
            PhotosPicker(
               selection: $selectedItem,
               matching: .videos)
            {
                Image("btn_plus")
            }
            .padding(.bottom, 16)
                    
            }
            .padding(.trailing, 16)
      }

      ..........

      .onChange(of: selectedItem) { newValue in
            Task {
                do {
                    print("loading!!!...")
                    
                    if let movie = try await selectedItem?.loadTransferable(type: Movie.self) {
                        print("loaded!!!...")
                    } else {
                        print("failed!!!...")
                    }
                } catch {
                    print("failed with ex!!!...")
                }
            }
        }

Итак, когда я запускаю код, я вижу свою кнопку, нажимаю ее, Галерея открывается только с видео (как и предполагалось). Затем я выбираю какое-нибудь видео и вижу этот лог

loading!!!...

И выполнение переходит в loadTransferable и никогда не возвращается. Я почти уверен, что допустил ошибку новичка, потому что впервые добавляю эту функциональность в SwiftUI. Не могли бы вы указать мне, что я сделал не так? Спасибо!

Есть аналогичная проблема здесь. Как насчет использования DataRepresentation вместо этого?

Sweeper 10.05.2024 11:23

Привет, Свипер! Спасибо за предложение, обязательно попробую.

Eugene Alexeev 10.05.2024 15:30

Я попробовал ваш код в своих тестах, и у меня все работает хорошо. В MacOS 14.5 с использованием Xcode 15.4, протестировано на реальных устройствах iOS 17 и macCatalyst. В старых системах все может быть по-другому. Если вам интересно, я могу показать свой тестовый код.

workingdog support Ukraine 10.05.2024 16:13

@workingdogsupportUkraine Здравствуйте! Спасибо за усилия! Очень интересно.. Не могли бы вы поделиться этим со мной?

Eugene Alexeev 13.05.2024 08:23

У меня точно такие же настройки, как и у вас..

Eugene Alexeev 13.05.2024 08:23

Привет @Sweeper! Я изучаю DataRepresentation и не понимаю, как он должен решить эту проблему. Не могли бы вы дать мне некоторые рекомендации по этому поводу? Большое спасибо!

Eugene Alexeev 14.05.2024 08:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
149
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ну, теперь это работает. Почему это? Без понятия. Единственное, что у меня есть, это то, что я добавил photoLibrary: .shared()), поэтому код PhotosPicker выглядит следующим образом:

PhotosPicker(
    selection: $viewModel.videoPicker.videoSelection,
    matching: .videos,
    photoLibrary: .shared())
    {
        Image("btn_plus")
    }
        .padding(.bottom, 16)

Но когда я удалил photoLibrary: .shared(), просто чтобы убедиться, что причина в этом, код тоже работает... Может быть, первый вызов с photoLibrary: .shared() включал предоставление приложению разрешений на просмотр файлов в Галерее? Я не знаю... но вот так. Я не отмечаю этот пост как ответ на свой вопрос, потому что это явно не так.

ОБНОВЛЯТЬ:

О боже, как непоследовательно PhotosPicker в SwiftUI, вы, ребята, даже не представляете. Код выше больше не работает — снова. Иногда загружается видео, иногда нет. Иногда кажется, что это зависит от продолжительности или размера видео, но иногда он не может загрузить 5-секундное видео, но может загрузить 10-минутное видео.

Наконец я исправил это, реализовав решение UIKit с помощью PHPickerViewController. Он очень стабилен и загружает абсолютно любое видео, которое я выбираю из Галереи, и делает это стабильно. Не стесняйтесь использовать его:

//
//  PickVideoFromGalleryView.swift
//  spintip-iOS-app
//
//  Created by Yevhen Alieksieiev on 14.05.2024.
//

import SwiftUI
import PhotosUI
import AVKit
import Combine

final class PickVideoFromGalleryCoordinator: NSObject {
    var parent: PickVideoFromGalleryView
 
    init(_ parent: PickVideoFromGalleryView) {
        self.parent = parent
    }
}

extension PickVideoFromGalleryCoordinator: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true)
        
        guard let pickedSelection = results.first else {
            self.parent.errorMsg = "No video was selected"
            return
        }
        
        let itemProvider = pickedSelection.itemProvider
        
        if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
            let progress = itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { [weak self] url, error in
                do {
                    guard let url = url, error == nil else {
                        throw error ?? NSError(domain: NSFileProviderErrorDomain, code: -1, userInfo: nil)
                    }
                    let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
                    try? FileManager.default.removeItem(at: localURL)
                    try FileManager.default.copyItem(at: url, to: localURL)
                    
                    self?.parent.videoUrl = IdentifiableURL(url: localURL)
                    

                } catch let catchedError {
                    
                    self?.parent.errorMsg = catchedError.localizedDescription
                }
            }
        } else {
            self.parent.errorMsg = "You can process videos only"
        }
    }
}

struct PickVideoFromGalleryView: UIViewControllerRepresentable {
    
    typealias UIViewControllerType = PHPickerViewController
    
    @Binding var videoUrl: IdentifiableURL?
    @Binding var errorMsg: String?
    
    public init(videoUrl: Binding<IdentifiableURL?>, errorMsg: Binding<String?>) {
        _videoUrl = videoUrl
        _errorMsg = errorMsg
    }
    
    func makeUIViewController(context: Context) -> UIViewControllerType {
        
        var configuration = PHPickerConfiguration(photoLibrary: .shared())
        
        // Set the filter type according to the user’s selection.
        configuration.filter = PHPickerFilter.videos
        // Set the mode to avoid transcoding, if possible, if your app supports arbitrary image/video encodings.
        configuration.preferredAssetRepresentationMode = .current
        // Set the selection behavior to respect the user’s selection order.
        configuration.selection = .ordered
        // Set the selection limit to enable multiselection.
        configuration.selectionLimit = 1
        
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator
        
        return picker
    }
    
    // In our case we do not need to update our `AVPlayerViewController` when AVPlayer changes
    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
    
    // Creates the coordinator that is used to handle and communicate changes in `AVPlayerViewController`
    func makeCoordinator() -> PickVideoFromGalleryCoordinator {
        PickVideoFromGalleryCoordinator(self)
    }
}

Вот мой тестовый код, чтобы выбрать видео с помощью PhotosPicker и отобразить его в представлении.

struct Movie: Transferable {
    let url: URL
    
    static var transferRepresentation: some TransferRepresentation {
        FileRepresentation(contentType: .movie) { movie in
            SentTransferredFile(movie.url)
        } importing: { received in
            
            print("----> in Movie received: \(received.file)")
            
            let copy = URL.documentsDirectory.appending(path: "movie.mp4")
            
            if FileManager.default.fileExists(atPath: copy.path()) {
                try FileManager.default.removeItem(at: copy)
            }
            
            try FileManager.default.copyItem(at: received.file, to: copy)
            return Self.init(url: copy)
        }
    }
}

struct GalleryView: View {
    @State private var selectedItem: PhotosPickerItem?
    @State private var player = AVPlayer()
    
    var body: some View {
        VStack {
            VideoPlayer(player: player)
                .frame(width: 345, height: 345)
                .padding(20)
            
            PhotosPicker(selection: $selectedItem, matching: .videos) {
                Image(systemName: "video.circle").resizable()
                    .frame(width: 55, height: 55)
            }
        }
        .onChange(of: selectedItem) {
            Task {
                do {
                    if let movie = try await selectedItem?.loadTransferable(type: Movie.self) {
                        print("----> in GalleryView movie: \(movie.url)")
                        player = AVPlayer(url: movie.url)
                    } else {
                        print("----> GalleryView failed...")
                    }
                } catch {
                    print("----> GalleryView error: \(error)")
                }
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        GalleryView()
    }
}

Большое спасибо за ваши усилия, очень ценно!

Eugene Alexeev 13.05.2024 13:17

Другие вопросы по теме