Проблема в том, что я пытаюсь загрузить видео из галереи в свое приложение, но у меня ничего не получается. Вот соответствующие фрагменты кода
Это моя переносимая структура
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. Не могли бы вы указать мне, что я сделал не так? Спасибо!
Привет, Свипер! Спасибо за предложение, обязательно попробую.
Я попробовал ваш код в своих тестах, и у меня все работает хорошо. В MacOS 14.5 с использованием Xcode 15.4, протестировано на реальных устройствах iOS 17 и macCatalyst. В старых системах все может быть по-другому. Если вам интересно, я могу показать свой тестовый код.
@workingdogsupportUkraine Здравствуйте! Спасибо за усилия! Очень интересно.. Не могли бы вы поделиться этим со мной?
У меня точно такие же настройки, как и у вас..
Привет @Sweeper! Я изучаю DataRepresentation и не понимаю, как он должен решить эту проблему. Не могли бы вы дать мне некоторые рекомендации по этому поводу? Большое спасибо!





Ну, теперь это работает. Почему это? Без понятия. Единственное, что у меня есть, это то, что я добавил 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()
}
}
Большое спасибо за ваши усилия, очень ценно!
Есть аналогичная проблема здесь. Как насчет использования
DataRepresentationвместо этого?