skip to Main Content

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.

2

Answers


  1. 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") {
                        // ...
                    }
                }.buttonStyle(.bordered)
            }
        }
        
    }
    

    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
            }
        }
    }
    
    Login or Signup to reply.
  2. You can use

    .photosPicker(isPresented: $isShowingPicker, selection: $viewModel.imageSelection)
    

    Instead of

     PhotosPicker(selection: $viewModel.imageSelection, matching: .images)
    

    Here is a full set of code.

    import SwiftUI
    import PhotosUI
    
    @MainActor
    final class TestViewModelProfile: ObservableObject {
        @Published private(set) var selectedImage: UIImage? = nil
        @Published var imageSelection: PhotosPickerItem? = nil
        
        func setImage() async throws {
            guard let imageSelection else { return }
            
            guard let data = try await imageSelection.loadTransferable(type: Data.self) 
            else {throw LocalError.missingImageData}
            
            guard let uiImage = UIImage(data: data) 
            else { throw LocalError.unableToCreateImage }
            
            selectedImage = uiImage
        }
        private enum LocalError: LocalizedError {
            case missingImageData
            case unableToCreateImage
        }
    }
    
    
    struct ProfilePageViewTest: View {
        @State private var profilePhoto: Image?
        @StateObject private var viewModel = TestViewModelProfile()
        @State private var isShowingPicker: Bool = 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())
                        .gesture(
                            LongPressGesture(minimumDuration: 1.0)
                                .onEnded { _ in
                                    // Perform image upload logic here
                                    uploadProfilePhoto()
                                }
                        )
                    
                } else {
                    Image(systemName: "person")
                        .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(isPresented: $isShowingPicker, selection: $viewModel.imageSelection)
                .task(id: viewModel.imageSelection) {
                    do {
                        try await viewModel.setImage()
                    } catch {
                        print (error) //Show error to user.
                    }
                }
        }
        
        func uploadProfilePhoto() {
            isShowingPicker.toggle()
        }
    }
    
    
    #Preview {
        ProfilePageViewTest()
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search