I would like to display a PhotosUI
PhotosPicker
via a longPressGesture
of a UIImage
.
import SwiftUI
import PhotosUI
@MainActor
final class TestViewModelProfile: ObservableObject {
@Published private(set) var selectedImage: UIImage? = nil
@Published var imageSelection: PhotosPickerItem? = nil {
didSet {
setImage(from: imageSelection)
}
}
private func setImage(from selection: PhotosPickerItem?) {
guard let selection else { return }
Task {
if let data = try? await selection.loadTransferable(type: Data.self) {
if let uiImage = UIImage(data: data) {
selectedImage = uiImage
return
}
}
}
}
}
struct ProfilePageViewTest: View {
@State var profilePhoto: Image?
@StateObject private var viewModel = PhotoPickerViewModel()
var body: some View {
VStack {
if let image = viewModel.selectedImage {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
.gesture(
LongPressGesture(minimumDuration: 1.0)
.onEnded { _ in
// Perform image upload logic here
uploadProfilePhoto()
}
)
} else {
Image("No Profile Picture")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
.gesture(
LongPressGesture(minimumDuration: 1.0)
.onEnded { _ in
// Perform image upload logic here
uploadProfilePhoto()
}
)
//PhotosPicker can be presented like this, but just via selection of the text as its own link
PhotosPicker(selection: $viewModel.imageSelection, matching: .images) {
Text("Open the photoPicker")
}
}
}
}
func uploadProfilePhoto() {
//present the PhotosPicker
}
}
struct ProfilePageViewTest_Previews: PreviewProvider {
static var previews: some View {
ProfilePageViewTest().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
In the example code I have both the unsatisfactory current approach, where the PhotosPicker can be opened via selection of the "Open the photoPicker" text below the image, and a placeholder for a function which I would like to have open the photopicker via long press.
The issue is I do not how to simply present the PhotoPicker as a result of a function, rather than manually creating, effectively, a button via the body of PhotosPicker(...){ <body> }
.
The end goal is actually to have uploadProfilePhoto
first display a VStack
of a few buttons: "Change profile photo", "View", "Cancel" and have the first one actually display the PhotosPicker, but if I can understand how to simply display the PhotosPicker as result of function, I can incorporate it into this overlayed button view.
You could try a different approach to achieve your end goal,
of ...first display a VStack of a few buttons: "Change profile photo", "View", "Cancel" and have the first one actually display the PhotosPicker
Using a .sheet
and a PhotosPicker
as a Button, as shown in the example code
import Foundation
import SwiftUI
import PhotosUI
struct ContentView: View {
var body: some View {
ProfilePageViewTest()
}
}
@MainActor
final class PhotoPickerViewModel: ObservableObject {
@Published private(set) var selectedImage: UIImage? = nil
@Published var imageSelection: PhotosPickerItem? = nil {
didSet {
setImage(from: imageSelection)
}
}
private func setImage(from selection: PhotosPickerItem?) {
guard let selection else { return }
Task {
if let data = try? await selection.loadTransferable(type: Data.self) {
if let uiImage = UIImage(data: data) {
selectedImage = uiImage
}
}
}
}
}
struct ProfilePageViewTest: View {
@State var profilePhoto: Image?
@StateObject private var viewModel = PhotoPickerViewModel()
@State var showSelector = false
var body: some View {
VStack {
if let image = viewModel.selectedImage {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
} else {
Image(systemName: "photo") // for testing
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
}
}
.onLongPressGesture {
showSelector = true
}
.sheet(isPresented: $showSelector) {
VStack (spacing: 55){
PhotosPicker(selection: $viewModel.imageSelection, matching: .images) {
Text("Change profile photo")
}
Button("View") {
// ...
}
Button("Cancel") {
// ...
showSelector = false
}
}.buttonStyle(.bordered)
.onChange(of: viewModel.imageSelection) { //<-- here
showSelector = false
}
// .onChange(of: viewModel.imageSelection) { _ in // <-- here
// showSelector = false
// }
}
}
}
EDIT-1
You can of course use a simple if
, such as:
struct ProfilePageViewTest: View {
@State var profilePhoto: Image?
@StateObject private var viewModel = PhotoPickerViewModel()
@State var showSelector = false
var body: some View {
VStack {
if let image = viewModel.selectedImage {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
} else {
Image(systemName: "photo") // for testing
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
}
Spacer()
if showSelector { // <--- here
VStack (spacing: 55){
PhotosPicker(selection: $viewModel.imageSelection, matching: .images) {
Text("Change profile photo")
}
Button("View") {
// ...
showSelector = false
}
Button("Cancel") {
// ...
showSelector = false
}
}
.buttonStyle(.bordered)
}
}
.padding(.top, 30)
.onLongPressGesture{
showSelector = true
}
}
}
EDIT-2:
using a simple tap on the picture, to choose/change the profile photo.
struct ProfilePageViewTest: View {
@State var profilePhoto: Image?
@StateObject private var viewModel = PhotoPickerViewModel()
var body: some View {
VStack {
PhotosPicker(selection: $viewModel.imageSelection, matching: .images) {
if let image = viewModel.selectedImage {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
} else {
Image(systemName: "photo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
}
}
}
}
}