How to properly use SwiftUI's Photo Picker to upload image? - swift

I am using SwiftUI's native image picker and selecting/loading images works great. What I can't figure out is how to call the selected image when uploading the image to firebase. In my UploadPostView, I can't figure out what to call for the image in viewModel.uploadPost(caption: caption, image: <??>)) I've tried calling the selectedImage but I get the error that it is not a UIImage.. I thought I had done the work to convert the data in to a UIImage in my imagepicker. Anyways, I am new to Swift and I feel like I'm missing something obvious so any help is appreciated!
UploadPostView
struct UploadPostView: View {
#StateObject var imagePicker = ImagePicker()
#State private var caption = ""
#Environment(\.presentationMode) var mode
#ObservedObject var viewModel = UploadPostViewModel()
var body: some View {
NavigationView {
VStack {
HStack(alignment: .top, spacing: 16) {
if let user = AuthViewModel.shared.currentUser {
KFImage(URL(string: user.profileImageUrl ?? ""))
.resizable()
.scaledToFill()
.frame(width: 64, height: 64)
.cornerRadius(10)
}
TextField("Enter your post here", text: $caption, axis: .vertical)
.lineLimit(5...10)
}.padding()
Spacer()
if let image = imagePicker.image {
HStack {
ZStack (alignment: .top) {
image
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.cornerRadius(4)
Button {
self.imagePicker.image = nil
} label: {
Image(systemName: "xmark")
.frame(width: 24, height: 24)
.foregroundColor(.white)
.padding(4)
.background(.black)
.clipShape(Circle())
}
.offset(x: 45, y: -15)
}
Spacer()
}
.padding()
}
HStack (spacing: 24) {
Text("Add to your post")
.foregroundColor(Color(.darkGray))
Spacer()
PhotosPicker(selection: $imagePicker.imageSelection) {
Image(systemName: "photo")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
}.foregroundColor(Color(.darkGray))
Button {
} label: {
Image(systemName: "at")
.resizable()
.scaledToFit()
.frame(width: 22, height: 22)
}.foregroundColor(Color(.darkGray))
}
.padding()
}
.onReceive(viewModel.$didUploadPost) { success in
if success {
mode.wrappedValue.dismiss()
}
}
.navigationTitle("Create Post")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
mode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.resizable()
.scaledToFit()
.frame(width: 18, height: 18)
.foregroundColor(Color(.darkGray))
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
viewModel.uploadPost(caption: caption, image: image))
} label: {
Text("Post")
.font(.system(size: 18))
.bold()
}
}
}
}
}
}
UploadPostViewModel
class UploadPostViewModel: ObservableObject {
#Published var didUploadPost = false
#Published var didDeletePost = false
let service = PostService()
func uploadPost(caption :String, image: UIImage?) {
service.uploadPost(caption: caption, image: image) { success in
if success {
//dismiss screen
self.didUploadPost = true
} else {
// show error message to user..
}
}
}
}
ImagePicker
#MainActor class ImagePicker: ObservableObject {
#Published var image: 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.image = uiImage
}
}
} catch {
print(error.localizedDescription)
image = nil
}
}
}

Related

How to transition to a new view in swiftui

My goal is to have the user click a button that then gives them a choice of yes or cancel, this works fine. Is yes is selected it should move to the Camera View. I am getting the error: Result of 'NavigationLink<Label, Destination>' initializer is unused.
struct ContentView: View {
#State private var showAlert = false
var body: some View {
NavigationStack
{
VStack {
Button{
showAlert = true
} label: {
Text("+")
}
.frame(width: 40, height: 40)
.symbolVariant(.fill)
.background(.red)
.cornerRadius(15)
.foregroundColor(.white)
.padding(.trailing,300)
Spacer()
}
.alert("Create Event Here?", isPresented: $showAlert) {
Button("Yes"){NavigationLink("addCameraView", destination: CameraView())//*****gets the error
}
Button("Cancel", role: .cancel) { }
}
}
struct CameraView: View{
#State private var sourceType: UIImagePickerController.SourceType = .photoLibrary
#State private var selectedImage: UIImage?
#State private var imagePickerDisplay = false
var body: some View {
NavigationView {
VStack {
if selectedImage != nil {
Image(uiImage: selectedImage!)
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
.frame(width: 300, height: 300)
} else {
Image(systemName: "snow")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
.frame(width: 300, height: 300)
}
Button("Camera") {
self.sourceType = .camera
self.imagePickerDisplay.toggle()
}.padding()
}
.navigationBarTitle("Take a Photo of the Event")
.sheet(isPresented: self.$imagePickerDisplay) {
ImagePickerView(selectedImage: self.$selectedImage, sourceType: self.sourceType)
}
}
}
}
}
To navigate with a button, we need to utilize a variable as our trigger. Wrapping the button in a NavigationLink and updating the associated variable to the appropriate value will trigger the navigation.
Below you will find the updated ContentView. CameraView remains unchanged.
import SwiftUI
struct ContentView: View {
#State private var showAlert = false
#State var selection: Int? = nil
var body: some View {
NavigationStack
{
VStack {
Button{
showAlert = true
} label: {
Text("+")
}
.frame(width: 40, height: 40)
.symbolVariant(.fill)
.background(.red)
.cornerRadius(15)
.foregroundColor(.white)
.padding(.trailing,300)
Spacer()
}
.alert("Create Event Here?", isPresented: $showAlert) {
NavigationLink(destination: CameraView(), tag: 1, selection: $selection) {
Button("Yes"){
selection = 1
}
}
Button("Cancel", role: .cancel) {
selection = nil
}
}
}
}
}

Construct View Like Pedantix SwiftUI

The problem is quite simple. I want to build something like Pedantix https://cemantix.certitudes.org/pedantix in SwiftUI.
I've this already :
So, I try to have my RoundedRectangle overlay to totally hide my text. And I want blocks to go at the line if needed, etc. I tried LazyHGrid (actually this), LazyVGrid, custom grid. But no results ...
import SwiftUI
struct Word: Identifiable, Equatable {
var id = UUID()
var text: String
var isFramed: Bool
var isTouched: Bool
}
struct ContentView: View {
#EnvironmentObject var service: Service
let rows = [
GridItem(.adaptive(minimum: 30)),
]
var body: some View {
GeometryReader { gr in
ScrollView {
VStack(alignment: .leading) {
HStack {
Spacer()
Image(systemName: "arrow.counterclockwise.circle")
.resizable()
.scaledToFit()
.frame(width: 24)
.onTapGesture {
service.loadRandomMovies(page: 1, completion: { _ in
service.loadMovie(id: service.randomMovieId ?? 0, completion: { _ in })
service.loadCredits(id: service.randomMovieId ?? 0, completion: { _ in })
})
}
}
HStack {
VStack {
RoundedRectangle(cornerRadius: 8)
.frame(width: 150, height: 250)
}
.padding()
VStack(alignment: .center) {
customTextView(with: service.frame(in: .title))
.padding(.bottom, 8)
customTextView(with: service.frame(in: .genres))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.movie?.releaseDate ?? "")")
.font(.title3)
.padding(.bottom, 8)
if service.movie?.tagline != "" {
Text("\"\(service.movie?.tagline ?? "")\"")
.font(.title3)
.padding(.bottom, 8)
.frame(alignment: .center)
}
customTextView(with: service.frame(in: .overview))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.credits?.cast.map({ $0.name }).joined(separator: " - ") ?? "")")
.fontWeight(.bold)
}
}
}
.padding()
}
.frame(width: gr.size.width)
}
}
}
extension ContentView {
#ViewBuilder
func customTextView(with words: [Word]) -> some View {
VStack {
LazyHGrid(rows: rows, spacing: 2) {
ForEach(words) { word -> AnyView in
if word.isFramed {
return AnyView(
Text("\(word.text)")
.padding(2)
.overlay(RoundedRectangle(cornerRadius: 4))
.overlay {
if word.isTouched {
Text("\(word.text.count)")
.foregroundColor(Color.cyan)
}
}
)
}
return AnyView(Text(word.text))
}
}
}
}
}
Do you think you could post your code so that we can see what you have done?

SwiftUI How to Disable Button While Uploading Objects in an Array

I have a SwiftUI form where the user adds images to an array from a picker and then a function fires to upload each image. The images are displayed in a ForEach as either an upload spinner while the upload is happening OR the actual image once the upload has completed.
I'd like the NEXT button, which would remove the user from the view, to be disabled until all of the uploads have completed.
I'm not sure how to inform the State of the Parent View that all of the uploads are completed.
Here's what my Parent View looks like:
struct ProjectFormStep4: View {
#EnvironmentObject var authVM: AuthorizationClass
#EnvironmentObject var projectVM: ProjectsViewModel
#EnvironmentObject var uploadVM: UploadManager
#ObservedObject var mediaItems = PickedMediaItems()
#State private var showSheet: Bool = false
#State private var showUploadView: Bool = false
var body: some View {
ZStack{
Color("BrandGrey").ignoresSafeArea()
VStack{
HStack{
Button{
projectVM.showProjectFormStep1 = false
projectVM.showProjectFormStep2 = false
projectVM.showProjectFormStep3 = true
projectVM.showProjectFormStep4 = false
} label: {
Text("< Back")
.font(.headline)
.foregroundColor(Color("BrandLightBlue"))
}
Spacer()
}
Spacer()
.frame(height: 30)
ZStack{
Rectangle()
.fill(Color(.white))
.frame(width: 300, height: 100)
.cornerRadius(12)
Image(systemName: "camera")
.resizable()
.foregroundColor(Color("BrandLightBlue"))
.scaledToFit()
.frame(height: 60)
}.onTapGesture {
self.showSheet = true
}
Spacer()
.sheet(isPresented: $showSheet, content: {
PhotoPicker(mediaItems: mediaItems) { didSelectItem in
showUploadView = true
showSheet = false
}
})
Spacer()
ScrollView{
ForEach(uploadVM.uploadedMedia, id:\.id){ item in
ImageArea(
item: item,
items: uploadVM.uploadedMedia
)
}
}
Spacer()
if showUploadView {
ForEach(mediaItems.items, id: \.id) { item in
UploadView(item: item)
}
}
Button {
} label: {
Text("Next")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color("BrandOrange"))
.cornerRadius(15.0)
}
}.padding()
}
}
}
Here's my Child View of which handles the upload spinner and actual image:
struct ImageArea: View {
#EnvironmentObject var projectVM: ProjectsViewModel
#EnvironmentObject var uploadVM: UploadManager
#State private var showThumbnailOptions: Bool = false
var item: UploadedMedia
var items : [UploadedMedia]
var body: some View {
if item.secureUrl == "" {
ZStack{
UploadSpinner()
.frame(
width: 300,
height: 200
)
.cornerRadius(12)
VStack(alignment: .leading ){
HStack{
Spacer()
.frame(
width: 30
)
Image(systemName: self.getWaterMarkName(item: item))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24)
.padding(4)
.background(Color.black.opacity(0.5))
.foregroundColor(.white)
}
Spacer()
HStack{
Spacer()
Text("\(item.uploadProgress)%")
.foregroundColor(.black)
.font(.body)
.padding()
Spacer()
}
}
}
} else {
ZStack{
ProjectRemoteUploadImage(projectImageUrl: projectVM.standardizeThumbnail(thumbnailUrl: item.secureUrl))
.aspectRatio(contentMode: .fit)
.onTapGesture {
showThumbnailOptions.toggle()
}
if showThumbnailOptions {
Color(.black).opacity(0.8)
VStack{
Spacer()
HStack{
Spacer()
Image(systemName: "arrowshape.turn.up.backward.circle.fill")
.resizable()
.foregroundColor(.white)
.frame(width: 50, height: 50)
.padding()
.onTapGesture {
self.showThumbnailOptions.toggle()
}
Image(systemName: "trash.circle.fill")
.resizable()
.foregroundColor(.red)
.frame(width: 50, height: 50)
.padding()
.onTapGesture {
deleteThumbnail(
item: item
)
}
Spacer()
}
Spacer()
}
}
}
}
}
func getWaterMarkName(item: UploadedMedia) -> String {
if item.mediaType == "photo"{
return "photo"
} else if item.mediaType == "video" {
return "video"
} else {
return "photo"
}
}
func deleteThumbnail(item: UploadedMedia){
let itemID = item.id
guard let itemIndex = uploadVM.uploadedMedia.firstIndex(where: {$0.id == itemID}) else { return }
uploadVM.uploadedMedia.remove(at: itemIndex)
}
}
Adding model info for the uploadedMedia
struct UploadedMedia {
var id: String
var uploadProgress: Float
var secureUrl: String
var mediaType: String
var width: Int
var length: Double
init(
id: String,
uploadProgress: Float,
secureUrl: String,
mediaType: String,
width: Int,
length: Double
){
self.id = id
self.uploadProgress = uploadProgress
self.secureUrl = secureUrl
self.mediaType = mediaType
self.width = width
self.length = length
}
}

SwiftUI: Have the ImagePicker save photo and set the binding variable to true and update views across the system. saved photo and variable persistent

I need the image picker to set a binding variable to True after an image is selected. After that image is selected, it needs to be saved. But after I select the image, the views changes. It isn't until I kill the app that the image and variable are changed back.
For the image picker, An error comes out "Instance member 'TrueBadge' of type 'PhotoPicker' cannot be used on instance of nested type 'PhotoPicker.Coordinator'"
Here's BadgeScreen View:
import Foundation
var PresentedBadge = UIImage(systemName: "questionmark")!
func loadImage() -> UIImage {
do {
if let furl = fileURL {
let data = try Data(contentsOf: furl)
if let img = UIImage(data: data) {
return img
}
}
} catch {
print("error: \(error)") // todo
}
return UIImage()
}
var IsDone = false
struct Badge: View {
#Binding var TrueBadge: Bool //Need help switching this Binding to true
#State private var ComplianceBadgeIsPicking = UIImage(named: "BlankComplianceBadge")!
#State private var isShwoingPhotoPicker = false
#State private var ShowInstruction = false
#State private var AlertToReplaceBade = false
var body: some View {
//The beginning
if TrueBadge {
Color("MainBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Text("Clearance Status")
.font(.title)
.fontWeight(.semibold)
.offset(y: -15)
.foregroundColor(.white)
Text("Vaccine Compliant")
.foregroundColor(.white)
.bold()
.font(.system(size: 30))
Image(uiImage: ContentViewBadge)
.resizable()
.aspectRatio(contentMode: .fit)
.scaledToFit()
Button(action: {
AlertToReplaceBade.toggle()
}) {
Image(systemName: "trash" )
Text("Remove")
}
.foregroundColor(.white)
.padding()
.background(Color.red)
.cornerRadius(15)
.offset(y: 13)
}.alert(isPresented: $AlertToReplaceBade, content: {
Alert(title: Text("Are you sure you would like to remove your current badge?"),
message: Text("Remeber that this badge is and will be permanently removed"),
primaryButton: .default(Text("Yes"), action: {
// Somehow need to remove the image and activate the UIImagePickerController
}), secondaryButton: .cancel(Text("No, I do not")))
})
)}
else {
Color("ExpiredBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Image(systemName: "person.crop.circle.badge.questionmark.fill")
.font(.system(size:150))
.offset(y: -10)
.foregroundColor(.black)
Text("Compliance Badge")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.black)
.offset(y: -2)
Text("You do not have a current vaccine compliant badge. Please upload one that shows you are vaccine compliant or within 'green' status")
.font(.system(size: 15))
.foregroundColor(.black)
.fontWeight(.bold)
.multilineTextAlignment(.center)
.frame(width: 270, height: 140, alignment: .center)
.offset(y: -26)
Button(action: {
ShowInstruction.toggle()
}) {
Image(systemName: "questionmark.circle")
Text("How to upload")
.bold()
.font(.system(size:20))
}
.offset(y: -40)
Button(action: {
isShwoingPhotoPicker.toggle()
}) {
Image(systemName: "square.and.arrow.up")
Text("Upload Badge")
.bold()
.font(.system(size:20))
}
.offset(y: -10)
}.sheet(isPresented: $ShowInstruction, content: {
Instruction()
})
.sheet(isPresented: $isShwoingPhotoPicker, content: {
PhotoPicker(TrueBadge: $TrueBadge, Badge: $ComplianceBadgeIsPicking)
})
.accentColor(.black)
)
}
//The End
}
}
var ContentViewBadge = UIImage(systemName: "questionmark")!
var fileURL: URL?
func saveImage() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("imageFile")
.appendingPathExtension("png")
fileURL = furl
try ContentViewBadge.pngData()?.write(to: furl)
} catch {
print("could not create imageFile")
}
}
struct PhotoPicker: UIViewControllerRepresentable {
//Since the Binding cannot be created above, I have no choice but to put it here
#Binding var TrueBadge: Bool
#Binding var Badge: UIImage
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.allowsEditing = true
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(photoPicker: self)
}
final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let photoPicker: PhotoPicker
init(photoPicker: PhotoPicker){
self.photoPicker = photoPicker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage{
photoPicker.Badge = image
ContentViewBadge = photoPicker.Badge
// This is where I am having trouble at. I am unable to change the Bool type of "TrueBadge" to true and have it stay on even after the application
// Is killed
// I also need the selected image to remain here. If i kill the application, the image is removed and the Boolean and set to false
//Error is being created below
TrueBadge = true
}
picker.dismiss(animated: true)
}
}
}```
And here's MainScreen view:
```import SwiftUI
import SafariServices
struct ContentView: View {
#State var ShowInstruction = false
#State var ShowBadge = false
#State var ShowPortal = false
#State var ShowDetails = false
#State var ViewAlert = false
#State var TrueBadge = false
var body: some View {
NavigationView{
Color("BackgroundMain")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
//Need the if Bool condition to set to true after an image is safe and the variable is set to true
if TrueBadge {
Button(action: {
ShowBadge.toggle()
}) {
Image(systemName: "doc.text.image")
.font(.system(.largeTitle))
.foregroundColor(.white)
.frame(width: 200, height: 100, alignment: .center)
.background(Color("MainBadgeScreen"))
.cornerRadius(15)
}
}
else {
Button(action: {
self.ViewAlert = true
}) {
Image(systemName: "doc.text.image")
.font(.system(.largeTitle))
.foregroundColor(.white)
.frame(width: 200, height: 100, alignment: .center)
.background(Color("ExpiredBadgeScreen"))
.cornerRadius(15)
}
}
}
.alert(isPresented: $ViewAlert, content: {
Alert(title: Text("You agree to use this app responsibly?"), primaryButton: .default(Text("Yes"), action: {
ShowBadge = true
}), secondaryButton: .cancel(Text("No, I do not")))
}))
.sheet(isPresented: $ShowBadge, content: {
Badge(TrueBadge: $TrueBadge)
})
.navigationTitle("Home")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Button(action: {
ShowPortal = true
}) {
Text("Student Health Portal")
Image(systemName: "heart.fill")
}
Button(action: {
ShowInstruction = true
}) {
Text("Instructions")
Image(systemName: "questionmark.circle")
}
Button(action: {
ShowDetails = true
}) {
Image(systemName: "info.circle")
Text("About")
}
}
label: {
icon: do {
Image(systemName: "gearshape.fill")
.foregroundColor(Color("WithSystem"))
}
}
}
}
.sheet(isPresented: $ShowPortal, content: {
safari()
})
.sheet(isPresented: $ShowInstruction, content: {
Instruction()
})
.sheet(isPresented: $ShowDetails, content: {
Details()
})
}
.accentColor(Color(.label))
}
struct safari : UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<safari>) -> SFSafariViewController{
let controller = SFSafariViewController(url: URL(string: "https://patientportal.bowiestate.edu/login_directory.aspx")!)
return controller
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<safari>) {
}
}
}```

Image not appearing when using .transition

struct BEHOLD: View {
#State private var showdeatail = false
var body: some View {
VStack {
Button(action: {
showdeatail.toggle()
}) {
Text("S U M M O N\nH I M")
}
.frame(width: 100, height: 50)
.background(Color.red)
.cornerRadius(10)
if showdeatail {
Image("HIM")
.frame(width: 100, height: 100)
.transition(.scale)
}
}
}
So I'm trying to make this image appear when you hit a button and the code seems to be working except the Image seems to be invisible and I'm not sure why
Try this code, it would work!
struct BEHOLD: View {
#State private var showdeatail = false
var body: some View {
VStack {
Button(action: {
showdeatail.toggle()
}) {
Text("S U M M O N\nH I M")
}
.background(Color.red)
.cornerRadius(10)
if showdeatail {
Image(systemName: "person")
.resizable()
.frame(width: 100, height: 100)
.transition(.scale)
}
}
.animation(.default, value: showdeatail)
}
}