Разработка
Отображение панели выбора фотографий в SwiftUI
Мы расскажем о том, как это работает и как это можно использовать в собственных проектах.
Знаете ли вы, что Apple создала способ для SwiftUI получить доступ к фотографиям пользователя, не запрашивая разрешения и при этом сохраняя конфиденциальность? Мы расскажем о том, как это работает и как это можно использовать в собственных проектах.
Что говорит Apple
Apple рекомендует разработчикам использовать встроенный пикер, если вашему приложению «нужен доступ только для чтения, чтобы получить изображения для публикации в Интернете или вставки в документ или электронное письмо», и что «поскольку система управляет своим жизненным циклом в отдельном процессе, она по умолчанию является приватной. Пользователю не нужно явно авторизовывать ваше приложение для выбора фотографий, что обеспечивает более простой и оптимизированный пользовательский опыт». Для получения дополнительной информации и документации от Apple перейдите по следующим ссылкам:
- Delivering an Enhanced Privacy Experience in Your Photos App
- Bringing Photos picker to your SwiftUI app
Почему это более конфиденциально
Если бы мы запросили доступ к библиотеке фотографий пользователя, это дало бы нашему приложению доступ ко всем фотографиям в библиотеке. При использовании описанного ниже метода наше приложение будет иметь доступ только к тем фотографиям, которые пользователь явно решил поместить в наше приложение. Это удобно и для нас, разработчиков, поскольку мы предоставляем нашим приложениям только те фотографии, которые необходимы, и для пользователей, поскольку они могут быть уверены, что приложение не ползает по всем их фотографиям.
Доступ к фотографиям пользователя в SwiftUI
В нашем примере ниже мы создали модель представления и представление для работы со встроенным селектором фотографий.
В работе PhotosPicker API можно выделить несколько моментов. В нашем примере мы ограничиваем выбор фотографий только изображениями. Мы также устанавливаем максимальное количество фотографий, которые пользователь может выбрать. Если бы, например, мы хотели использовать этот код для того, чтобы пользователь мог выбрать фотографию профиля (или только одно изображение за раз), мы бы изменили ограничение на 1. Мы также попросили возвращать фотографии в том же порядке, в котором они были выбраны, что является приятной UX-функцией API.
В нашей модели представления мы создали несколько массивов, один из которых будет содержать изображения, которые мы хотим отобразить, а другой — объекты, выбранные в меню выбора фотографий.
Наличие двух разных массивов очень важно, поскольку выборка фотографий не всегда ограничивается только фотографиями. Это подводит нас к последней части модели представления — нашей функции, которая принимает выбранные фотоснимки и пытается преобразовать их в изображение. В нашем случае мы хотим ресетить выбранные фотографии в пикере и ранее выбранные изображения каждый раз, когда пользователь нажимает кнопку. Если вы не хотите или не нуждаетесь в сбросе этих значений в своем коде, вы можете удалить из функции два вызова *.removeAll()
.
Мы также проверяем, не пуст ли массив selectedPhotos, чтобы не тратить время на запуск задачи, если она нам не нужна. Если массив не пуст, мы попытаемся преобразовать данные из selectedPhotos в массив изображений, а затем добавим эти изображения в массив images.
Следует отметить, что, несмотря на то, что мы отображаем фотографии в LazyHGrid
, такой способ отображения не кажется очень производительным. В приведенном ниже коде с каждой добавленной фотографией быстро увеличивается потребление памяти. Это не затрагивает работу пикера фотографий, но включено в данное руководство для того, чтобы вы могли протестировать свой собственный код и убедиться, что он работает правильно в вашем проекте.
import SwiftUI import PhotosUI class PhotoSelectorViewModel: ObservableObject { @Published var images = [UIImage]() @Published var selectedPhotos = [PhotosPickerItem]() @MainActor func convertDataToImage() { // reset the images array before adding more/new photos images.removeAll() if !selectedPhotos.isEmpty { for eachItem in selectedPhotos { Task { if let imageData = try? await eachItem.loadTransferable(type: Data.self) { if let image = UIImage(data: imageData) { images.append(image) } } } } } // uncheck the images in the system photo picker selectedPhotos.removeAll() } } struct PhotoSelectorView: View { @StateObject var vm = PhotoSelectorViewModel() let maxPhotosToSelect = 10 var body: some View { VStack { ScrollView(.horizontal) { LazyHGrid(rows: [GridItem(.fixed(300))]) { ForEach(0..<vm.images.count, id: \.self) { index in Image(uiImage: vm.images[index]) .resizable() .scaledToFit() } } } PhotosPicker( selection: $vm.selectedPhotos, // holds the selected photos from the picker maxSelectionCount: maxPhotosToSelect, // sets the max number of photos the user can select selectionBehavior: .ordered, // ensures we get the photos in the same order that the user selected them matching: .images // filter the photos library to only show images ) { // this label changes the text of photo to match either the plural or singular case based on the value in maxPhotosToSelect Label("Select up to ^[\(maxPhotosToSelect) photo](inflect: true)", systemImage: "photo") } } .padding() .onChange(of: vm.selectedPhotos) { _, _ in vm.convertDataToImage() } } } #Preview { PhotoSelectorView() }
Если вы хотите посмотреть, как сделать то же самое в Android с помощью Jetpack Compose, ознакомьтесь с моей другой статьей здесь.
Если у вас есть вопросы по теме или вы знаете другой способ решения той же задачи, не стесняйтесь ответить на это сообщение или поделиться им с другом, чтобы узнать его мнение.