Как перенести выбор изображения в другое представление?
У меня есть вид с камеры, где пользователи могут либо сделать фотографию с помощью своей камеры (которая открывается вместе с представлением), либо щелкнуть значок библиотеки фотографий и выбрать изображение из своей библиотеки. Если они сделают фотографию прямо сейчас, я смогу связатьcapturedImage
в UploadPostView, но я не могу понять, как сделать то же самое, если они выберут фотографию из своей библиотеки. Я создаю что-то похожее на истории Instagram или Snapchat, где после того, как вы сделаете/выберете фотографию, вы сможете отредактировать ее (UploadPostView) перед публикацией.
Привязка переменной таким же образом, как и для captureImage, не работает. Я не очень хорошо знаком, но решил, что это произошло потому, что я привязывал настоящий класс ImagePicker. но когда я пытаюсь связать ImagePicker.image или ImagePicker.imageSelection... это тоже ничего не делает. Спасибо!!
CustomCameraView (когда пользователь делает фотографию или выбирает ее из своей библиотеки)
import SwiftUI
import PhotosUI
struct CustomCameraView: View {
let cameraService = CameraService()
@Environment(\.dismiss) private var dismiss
@StateObject var imagePicker = ImagePicker()
@Binding var capturedImage: UIImage?
var body: some View {
//if photo taken
if (imagePicker.image != nil) || (capturedImage != nil) {
UploadPostView(capturedImage: $capturedImage)
}
//if photo not taken yet
else {
ZStack (alignment: .topLeading) {
CameraView(cameraService: cameraService) { result in
switch result {
case .success(let photo):
if let data = photo.fileDataRepresentation() {
capturedImage = UIImage(data: data)
} else {
print("Error: no image data found")
}
case .failure(let err):
print(err.localizedDescription)
}
}
VStack (alignment: .leading) {
Button {
dismiss()
} label: {
Image("xmark")
.renderingMode(.template)
.resizable()
.frame(width: 28, height: 28)
.foregroundColor(.white)
}
.padding()
Spacer()
HStack {
PhotosPicker(selection: $imagePicker.imageSelection) {
Image("image-square")
.renderingMode(.template)
.resizable()
.frame(width: 32, height: 28)
.foregroundColor(.white)
}
Spacer()
Button {
cameraService.capturePhoto()
} label: {
Image(systemName: "circle")
.font(.system(size: 72))
.foregroundColor(.white)
}
Spacer()
Rectangle()
.foregroundColor(.clear)
.frame(width: 32, height: 28)
}
.padding()
}
}
.cornerRadius(6)
.background(.black)
}
}
}
Выбор изображения
import SwiftUI
import PhotosUI
@MainActor
class ImagePicker: ObservableObject {
@Published var image: Image?
@Published var uiImage: UIImage?
@Published var imageSelection: PhotosPickerItem? {
didSet {
if let imageSelection {
Task {
try await loadTransferable(from: imageSelection)
}
}
}
}
func loadTransferable(from imageSelection: PhotosPickerItem?) async throws {
do {
if let data = try await imageSelection?.loadTransferable(type: Data.self) {
if let uiImage = UIImage(data: data) {
self.uiImage = uiImage
self.image = Image(uiImage: uiImage)
}
}
} catch {
print(error.localizedDescription)
image = nil
}
}
}
UploadPostView (где пользователи могут редактировать свои фотографии перед загрузкой)
import SwiftUI
import Kingfisher
import PhotosUI
struct UploadPostView: View {
@Environment(\.dismiss) private var dismiss
@ObservedObject var viewModel = UploadPostViewModel()
@State var caption = ""
@State var rating = 0
@StateObject var imagePicker = ImagePicker()
@Binding var capturedImage: UIImage?
var body: some View {
VStack {
if let image = imagePicker.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
.cornerRadius(6)
.clipped()
}
else {
if let image = capturedImage {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
.cornerRadius(6)
.clipped()
}
}
HStack {
Spacer()
Button {
if let uiimage = imagePicker.uiImage {
viewModel.uploadPost(caption: caption, image: uiimage, rating: rating)
viewModel.loading = true
} else if let uiimage = capturedImage {
viewModel.uploadPost(caption: caption, image: uiimage, rating: rating)
viewModel.loading = true
}
} label: {
if viewModel.loading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.frame(width: 24, height: 24)
.padding()
.background(Color.accentColor)
.foregroundColor(.white)
.clipShape(Circle())
} else {
Image("send-fill")
.renderingMode(.template)
.resizable()
.frame(width: 24, height: 24)
.padding()
.background(Color.accentColor)
.foregroundColor(.white)
.clipShape(Circle())
}
}
}.padding(8)
}
.background(.black)
.onReceive(viewModel.$didUploadPost) { success in
if success {
dismiss()
}
}
}
}
1 ответ
С вашим кодом есть 3 проблемы.
- У вас есть несколько источников истины. Например, несколько экземпляров и наличие нескольких переменных для изображения (,
uiImage
иimage
)
Каждый раз, когда ты звонишьImagePicker()
вы создаете другой экземпляр, и один не знает о другом.
- Множественные источники истины приводят ко второй проблеме — слишком большому количеству условных обозначений.
Есть много способов решить эту проблему, но я предпочитаю, когда это возможно, автономные модули многократного использования.
PhotosPicker { result in
switch result {
case .success(let image):
capturedImage = image
case .failure(let error):
print(error)//Provides a better description of an error.
capturedImage = nil
}
}
Этого можно добиться, поместив весь код Picker в отдельный файл .
import PhotosUI
struct PhotosPicker: View{
@State private var imageSelection: PhotosPickerItem?
//
let action: (Result<UIImage, Error>) async -> Void
var body: some View{
PhotosUI.PhotosPicker(selection: $imageSelection, matching: .images) {
Image(systemName: "photo")
.renderingMode(.template)
.resizable()
.frame(width: 32, height: 28)
.foregroundColor(.white)
}.task(id: imageSelection) {//Will trigger when there is a change to imageSelection
if let _ = imageSelection{
do{
let image = try await loadTransferable()
await action(.success(image))
}catch{
await action(.failure(error))
}
}
}
}
func loadTransferable() async throws -> UIImage {
guard let data = try await imageSelection?.loadTransferable(type: Data.self) else{
throw PickerError.unableGetData
}
//Make sure you don't overlook `else` when dealing with conditionals. The user should always be informed
guard let uiImage = UIImage(data: data) else {
throw PickerError.unableToCreateUIImage
}
return uiImage
}
enum PickerError: LocalizedError{
case unableGetData
case unableToCreateUIImage
}
}
Используя описанный выше подход, вы устраняете необходимость вImagePicker
и начать работать напрямую с .
Теперь это приводит к третьей проблеме. Ваш код сейчас небольшой, и он свеж в вашей памяти, но если вам придется вернуться к этому коду через несколько месяцев, вы, вероятно, столкнетесь с хрупкими областями. Например, условные предложения, которые не проходят или, другими словами, вы упускаете из видуelse
. Первый был вloadTransferable
функция, а следующая находится в .
Предполагая, что ваш план состоит в том, чтобы никогда не показыватьUploadPostView
если нетcapturedImage
ты можешь улучшить свойnil
проверьте, чтобы исключить необязательное в этом
struct CustomCameraView: View {
@Environment(\.dismiss) private var dismiss
@Binding var capturedImage: UIImage?
var body: some View {
//Unwrap Binding, to get rid of the optional below
if let b = Binding($capturedImage) {
UploadPostView(capturedImage: b)
}else {
ZStack (alignment: .topLeading) {
VStack (alignment: .leading) {
Button {
dismiss()
} label: {
Image(systemName: "xmark")
.renderingMode(.template)
.resizable()
.frame(width: 28, height: 28)
.foregroundColor(.white)
}
.padding()
Spacer()
HStack {
PhotosPicker { result in
switch result {
case .success(let image):
capturedImage = image
case .failure(let error):
print(error)
capturedImage = nil
}
}
Spacer()
Button {
// cameraService.capturePhoto()
} label: {
Image(systemName: "circle")
.font(.system(size: 72))
.foregroundColor(.white)
}
Spacer()
Rectangle()
.foregroundColor(.clear)
.frame(width: 32, height: 28)
}
.padding()
}
}
.cornerRadius(6)
.background(.black)
}
}
}
Это очиститView
существенно.
struct UploadPostView: View {
@Environment(\.dismiss) private var dismiss
@Binding var capturedImage: UIImage //Optional removed
var body: some View {
VStack {
Image(uiImage: capturedImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
.cornerRadius(6)
.clipped()
}
.background(.black)
}
}