Im tryna to upload some data and Picked Image to Storage, but its uploads like unknown format to Storage. Everything else is fine with the rest of the data.
This is Add Item View, I have form with text fields title, description and author, then I tapped on button "Done" its saves to collection "Items" in Firebase database, but it doesn't save Picked Images, its only saves like "unknown type" to Storage. What I missed?
I cleaned the code as much as possible and left only the relevant lines of code for easier reading.
struct AddItemView: View {
@Environment(\.presentationMode) private var presentationMode
@State var presentActionSheet = false
@State var showPicker: Bool = false
@State var pickedImages: [UIImage] = []
@State var picData : Data = .init(count: 0)
@State var image : UIImage?
// MARK: - State (Initialiser-modifiable)
@ObservedObject var viewModel = NewItemView()
var mode: Mode = .new
var completionHandler: ((Result<Action, Error>) -> Void)?
// MARK: - UI Components
var saveButton: some View {
Button(action: {
self.handleDoneTapped() // - look at the end of the code
}) {
Text(mode == .new ? "Done" : "Save")
}
.disabled(!viewModel.modified)
}
var body: some View {
NavigationView {
Group {
Section(header: Text("New Item")) {
TextField("Title", text: $viewModel.singleitem.title)
TextField("Description", text: $viewModel.singleitem.description)
}
Section(header: Text("Author")) {
TextField("Author", text: $viewModel.singleitem.author)
}
if mode == .edit {
Section {
Button("Delete item") { self.presentActionSheet.toggle() }
.foregroundColor(.red)
}
}
Button {
showPicker.toggle()
} label: {
Image(systemName: "plus")
}
TabView {
ForEach(pickedImages, id: \.self) { image in
GeometryReader { proxy in
let size = proxy.size
Image(uiImage: image)
}
.padding()
}
}
.frame(height: 450)
//.tabViewStyle(.page(indexDisplayMode: pickedImages.isEmpty ? .never : .always))
}
.navigationBarItems(leading: cancelButton, trailing: saveButton)
}
.popupImagePicker(show: $showPicker) { assets in
// MARK: Example
let manager = PHCachingImageManager.default()
let options = PHImageRequestOptions()
options.isSynchronous = true
DispatchQueue.global(qos: .userInteractive).async {
assets.forEach { asset in
manager.requestImage(for: asset, targetSize: .init(), contentMode: .default, options: options) { image, _ in
guard let image = image else { return }
DispatchQueue.main.async {
self.pickedImages.append(image)
}
}
}
}
}
// MARK: Grid Image Content
.navigationTitle(mode == .new ? "Item" : viewModel.singleitem.title)
.navigationBarTitleDisplayMode(mode == .new ? .inline : .large)
.navigationBarItems(
leading: cancelButton,
trailing: saveButton
)
.actionSheet(isPresented: $presentActionSheet) {
ActionSheet(title: Text("Are you sure?"),
buttons: [
.destructive(Text("Delete item"),
action: { self.handleDeleteTapped() }),
.cancel()
])
}
}
// MARK: - Action Handlers
func handleDoneTapped() { // then I press Done it should save all data from the View to database and Storage.
self.viewModel.handleDoneTapped()
self.uploadImage() // This function below (to upload image to storage)
self.dismiss()
}
func uploadImage() { // Not saves images from Picked Images, just unknown files.
let storage = Storage.storage().reference()
let userId = Auth.auth().currentUser?.uid
storage.child("itemImages").child(userId ?? "").putData(self.picData, metadata: nil) {
(_, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
storage.child("itemImages").child(userId ?? "").downloadURL {(url, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
}
}
}
}
Also I have New Item Class with all function to save it into database:
class NewItemView: ObservableObject {
// MARK: - Public properties
@Published var singleitem: SingleItem
@Published var modified = false
// MARK: - Internal properties
private var cancellables = Set<AnyCancellable>()
// MARK: - Constructors
init(singleitem: SingleItem = SingleItem(title: "", author: "", description: "", image: "")) {
self.singleitem = singleitem
self.$singleitem
.dropFirst()
.sink { [weak self] singleitem in
self?.modified = true
}
.store(in: &self.cancellables)
}
// MARK: - Firestore
private var db = Firestore.firestore()
private func addItem(_ singleitem: SingleItem) {
do {
var addedItem = singleitem
addedItem.userId = Auth.auth().currentUser?.uid
_ = try db.collection("items").addDocument(from: addedItem)
}
catch {
print(error)
}
}
and SingleItem with all var's:
struct SingleItem: Identifiable, Codable {
@DocumentID var id: String?
var title : String
var author : String
var description : String
@ServerTimestamp var createdTime: Timestamp?
var userId : String?
var image : String
}
enum CodingKeys: String, CodingKey {
case id
case title
case author
case description = ""
case image
}
CodePudding user response:
In view of your new code/picture. The error tells you that in func uploadImage()
, you are assigning a binding array of pickedImages: [UIImage]
to a single picData
.
Use let picData: Data = pickedImages[0].jpegData(compressionQuality: 1)
, no $
, and change [0]
to whatever image index you want. You will have to check that this index exist, otherwise you will get an out of index error.
Note this let picData..
. in func uploadImage()
is completely different to the @State var picData...
you declare in AddItemView
.