swiftswiftuiphotosui

How to display PhotosPicker via swiftUI Image LongPressGesture?


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.


Solution

  • 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())
                    }
                }
            }
        }
    }