First of all ofc before posting I searched in Google for information related to this issue, but in my particular situation I did not find anything, except for a simple upload of images to Storage.
I have a simple app to search some Items from database, every user can upload some data to database (Firebase) via special form on home screen.
I need the user to be able to add a picture along in addition to text and keep it as one, so that when searching for this item, the name and description are displayed along with the image.
I guess need another field in collection, like “pictureId” (of course, first I need to upload this image along with the text to the database through a form on the click of a button) to call it from Storage everywhere I want?
(in this case, when searching).
In Firebase database I have only one collection “Items” with fields “title“, “userId“, “createdTime“, “description“, “author“.
Form to upload data:
enum Mode {
case new
case edit
}
enum Action {
case delete
case done
case cancel
}
struct AddItemView: View {
@Environment(\.presentationMode) private var presentationMode
@State var presentActionSheet = false
// MARK: - State (Initialiser-modifiable)
@ObservedObject var viewModel = NewItemView()
var mode: Mode = .new
var completionHandler: ((Result<Action, Error>) -> Void)?
// MARK: - UI Components
var cancelButton: some View {
Button(action: { self.handleCancelTapped() }) {
Text("Cancel")
}
}
var saveButton: some View {
Button(action: { self.handleDoneTapped() }) {
Text(mode == .new ? "Done" : "Save")
}
.disabled(!viewModel.modified)
}
var body: some View {
NavigationView {
Form {
Section(header: Text("New Item")) {
TextField("Title", text: $viewModel.signleitem.title)
TextField("Description", text: $viewModel.signleitem.description)
}
Section(header: Text("Author")) {
TextField("Author", text: $viewModel.signleitem.author)
}
if mode == .edit {
Section {
Button("Delete item") { self.presentActionSheet.toggle() }
.foregroundColor(.red)
}
}
}
.navigationTitle(mode == .new ? "Your Item" : viewModel.signleitem.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 handleCancelTapped() {
self.dismiss()
}
func handleDoneTapped() {
self.viewModel.handleDoneTapped()
self.dismiss()
}
func handleDeleteTapped() {
viewModel.handleDeleteTapped()
self.dismiss()
self.completionHandler?(.success(.delete))
}
func dismiss() {
self.presentationMode.wrappedValue.dismiss()
}
}
struct AddItemView_Previews: PreviewProvider {
static var previews: some View {
_ = SingleItem(title: "", author: "", description: "")
return
NavigationView {
AddItemView()
}
}
}
extension View {
func endTextEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
}
Firebase stuff:
class NewItemView: ObservableObject {
// MARK: - Public properties
@Published var signleitem: SingleItem
@Published var modified = false
// MARK: - Internal properties
private var cancellables = Set<AnyCancellable>()
// MARK: - Constructors
init(signleitem: SingleItem = SingleItem(title: "", author: "", description: "")) {
self.signleitem = signleitem
self.$signleitem
.dropFirst()
.sink { [weak self] signleitem in
self?.modified = true
}
.store(in: &self.cancellables)
}
// MARK: - Firestore
private var db = Firestore.firestore()
private func addItem(_ signleitem: SingleItem) {
do {
var addedItem = signleitem
addedItem.userId = Auth.auth().currentUser?.uid
_ = try db.collection("items").addDocument(from: addedItem)
}
catch {
print(error)
}
}
private func updateItem(_ signleitem: SingleItem) {
if let documentID = signleitem.id {
do {
try db.collection("items").document(documentID).setData(from: signleitem)
}
catch {
print(error)
}
}
}
private func updateOrAddItem() {
if signleitem.id != nil {
self.updateItem(self.signleitem)
}
else {
addItem(signleitem)
}
}
private func removeItem() {
if let documentID = signleitem.id {
db.collection("items").document(documentID).delete { error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
// MARK: - UI handlers
func handleDoneTapped() {
self.updateOrAddItem()
}
func handleDeleteTapped() {
self.removeItem()
}
}
class SettingsView: ObservableObject {
@Published var signleitems = [SingleItem]()
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
deinit {
unsubscribe()
}
func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
func subscribe() {
let userId = Auth.auth().currentUser?.uid
if listenerRegistration == nil {
listenerRegistration = db.collection("items")
.order(by: "createdTime")
.whereField("userId", isEqualTo: userId)
.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.signleitems = documents.compactMap { queryDocumentSnapshot in
try? queryDocumentSnapshot.data(as: SingleItem.self)
}
}
}
}
func removeItems(atOffsets indexSet: IndexSet) {
let signleitems = indexSet.lazy.map { self.signleitems[$0] }
signleitems.forEach { signleitem in
if let documentID = signleitem.id {
db.collection("items").document(documentID).delete { error in
if let error = error {
print("Unable to remove document: \(error.localizedDescription)")
}
}
}
}
}
}
struct SingleItem: Identifiable, Codable {
@DocumentID var id: String?
var title : String
var author : String
var description : String
@ServerTimestamp var createdTime: Timestamp?
var userId : String?
}
enum CodingKeys: String, CodingKey {
case id
case title
case author
case description = ""
}