I've made a little app where I have categories, and in each categories I have different products, that works well with barcoded data, but now I want to write them in Firestore and fetch them in my application.
I have 2 Struct, one for the products :
struct Product : Identifiable, Hashable {
var id = UUID().uuidString
var type : ProductType
var title : String
var subtitle : String
var description : String = ""
var price : String
var productImage : String = ""
var quantity : Int = 1
}
and one for the category type
enum ProductType : String, CaseIterable {
case Wearable = "Wearable"
case Laptops = "Laptops"
case Phones = "Phones"
case Tablets = "Tablets"
}
This is how I wrote the Product in Firestore : Picture 1, but I do not know how to write the enum.
Also, this is how I'm getting the data from the firestore, but I have an error at:
return Product(id: d.documentID, type: d["type"] as? String ?? ""
Cannot convert value of type 'String' to expected argument type 'ProductType'
func getData() {
FirebaseManager.shared.firestore.collection("products").getDocuments { snapshot, error in
if error == nil {
if let snapshot = snapshot {
DispatchQueue.main.async {
self.products = snapshot.documents.map { d in
return Product(id: d.documentID, type: d["type"] as? String ?? ""
, title: d["title"] as? String ?? "",
subtitle: d["subtitle"] as? String ?? "",
price: d["price"] as? String ?? "", productImage: d["productImage"] as? String ?? ""
)
}
}
}
}
else {
}
}
}
This is for "Lore Ipsum comment "
func getData() {
FirebaseManager.shared.firestore.collection("products").getDocuments { snapshot, error in
if error == nil {
if let snapshot = snapshot {
DispatchQueue.main.async {
self.products = snapshot.documents.compactMap { document in
try? document.data(as: Product.self)
} ?? []
}
}
}
else {
}
}
}
Also, this is my FirebaseManager :
class FirebaseManager : NSObject {
let auth : Auth
let storage : Storage
let firestore : Firestore
static let shared = FirebaseManager()
override init() {
self.auth = Auth.auth()
self.storage = Storage.storage()
self.firestore = Firestore.firestore()
}
}
Question, how can I write that enum into firestore?
I think it could work like this
Even if the code readability is reduced
type: ProductType(rawValue: d["type"] as? String ?? "") ?? .Wearable // default value for ProductType
This ViewModel is about saving data in Firestore with specific data types.
import Foundation
import Firebase
import FirebaseStorage
import FirebaseStorageSwift
import UIKit
class UploadViewModel : ObservableObject {
#Published var loading : Bool = false
func storeImageWithUrl(images : [UIImage], completion : #escaping (_ urls : [String]) -> ()) {
self.loading = true
var count = 0
var urls : [String] = []
for image in images {
let ref = Storage.storage().reference(withPath: UUID().uuidString)
guard let imageData = image.jpegData(compressionQuality: 0.5) else {return}
ref.putData(imageData, metadata: nil) { metaData, error in
if let error = error {
print("failed to push image cause of error")
return
}
ref.downloadURL { url, error in
if let error = error {
print("error to make url")
return
}
guard let url = url else {return}
count += 1
urls.append(url.absoluteString)
if count == images.count {
completion(urls)
}
}
}
}
}
func storeItemInformation(title : String, description : String, category : String, contactInfo : String, price : String, imageUrls : [String], timeStamp : Date = Date(), saved : Bool = false, seller : String, completion : #escaping (_ result : Bool) -> ()) {
let uid = AuthService.instance.makeUid()
guard let userData = ["title": title, "description" : description, "category" : category, "price" : price, "imageURL" : imageUrls, "timestamp" : timeStamp, "contactInfo" : contactInfo, "saved" : saved, "seller" : seller] as? [String : Any] else { return }
Firestore.firestore()
.collection("Wholeitems")
.document(category)
.collection(uid)
.document(title)
.setData(userData) { error in
if let error = error {
print("Error to save whole Data")
completion(false)
return
}
print("Success to save whole data")
self.loading = false
completion(true)
}
}
}
Below code is about fetching data from Firestore with ItemModel.
import Foundation
import SwiftUI
import Firebase
import FirebaseAuth
class FeedViewModel : ObservableObject {
#Published var feeds : [ItemModel] = []
init() {
fetchItems()
print(feeds)
}
func fetchItems() {
Firestore.firestore()
.collection("Wholeitems")
.getDocuments { snapshot, error in
if let error = error {
print("error to get data")
return
}
if let snapshot = snapshot {
DispatchQueue.main.async {
self.feeds = snapshot.documents.map({ d in
return ItemModel(id: d.documentID,
category: d["category"] as? String ?? "",
contactInfo: d["contactInfo"] as? String ?? "",
description: d["description"] as? String ?? "",
price: d["price"] as? String ?? "",
timestamp: d["timestamp"] as? String ?? "",
title: d["title"] as? String ?? "",
saved : d["saved"] as? Bool ?? false,
seller: d["seller"] as? String ?? "",
imageURL: d["imageURL"] as? [String] ?? []
)
})
}
}
}
}
}
The problem is that I don't know why the feeds array is empty.
I tried to get all of the documents in the Firestore collection named Wholeitems. But, the array is empty.
Could you help me? Thanks!
I'm trying to show the data I fetched from my Firebase database. I tried creating #State var variables and add them to my function but it didn't work. I tried printing my function output in a button to print it to console and it works. I just don't know how to show them in my view my code
import SwiftUI
import Firebase
struct ProfileView: View {
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text("hello")
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
print(name)
// ...
}) { error in
print(error.localizedDescription)
}
}
}
Just create a #State variable, which contains the name. If your function changes that variable, your view will updates.
struct profile: View {
#State var name : String = ""
var body: some View {
Text("Hello " + self.name)
And then in your function, instead of printing you will assign it to your state.
let name = value?["fullName"] as? String ?? ""
print(name)
self.name = name
That should work. I do not have an example with Firebase at the moment, so I can not test it. If it is not working, please describe the behavior.
Adding an #State property profileName and assigning it in the network request function will work after tapping the Button.
// ProfileView.swift
//
//
// Created by Shahin Bararesh on 2020-09-07.
//
import SwiftUI
import Firebase
struct ProfileView: View {
#State var profileName: String = ""
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text(profileName)
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
self.profileName = name
// ...
}) { error in
print(error.localizedDescription)
}
}
}
Hi I am currently making a dating app's chat page that you can have different rooms for every match using SwiftUI and Cloud Firestore.
I would like to show different chat room every time you tap different user on the top page depending on the matchId.
For now, I need to type the right one in the View file in order to make it work correctly, however, Id like to assign it dynamically.
How can I add the correct matchId to the instance in the View file? Or, should I try different ways?
First, this is the top page.
VStack{
Text("Match Users")
List(self.shareData.matchUserArray){ user in
NavigationLink(destination: MessageView(matchUserInfo: user)){
HStack{
Text(user.name)
Text(user.age)
}
}
}
}
And this is the View file. Without typing "Ll73RINefGxEcYQJoWSE" in the MessageViewModel instance and instead giving it "", I can see the messages in the debug area but don't see any in List.
struct MessageView: View {
var matchUserInfo: User
#ObservedObject var msgVM = MessageViewModel(matchId: "Ll73RINefGxEcYQJoWSE")
#EnvironmentObject var shareData : ShareData
#State var text = ""
#State var matchId = ""
var body: some View {
VStack{
List(self.msgVM.messages, id: \.id){ i in
if i.fromUser == self.shareData.currentUserData["id"] as? String ?? ""
{
MessageRow(message: i.msg, isMyMessage: true)
} else if i.toUser == self.shareData.currentUserData["id"] as? String ?? ""
{
MessageRow(message: i.msg, isMyMessage: false)
}
}
.onAppear { UITableView.appearance().separatorStyle = .none }
.onDisappear { UITableView.appearance().separatorStyle = .singleLine }
HStack{
TextField("message here", text: $text).textFieldStyle(RoundedBorderTextFieldStyle()).padding()
Button(action: {
if self.text.count > 0 {
self.msgVM.sendMsg(msg: self.text, toUser: self.matchUserInfo.id, fromUser: self.shareData.currentUserData["id"] as! String, matchId: self.msgVM.matchId)
self.text = ""
}
}) {
Image(systemName: "paperplane")
}.padding(.trailing)
}
}
.navigationBarTitle("\(self.matchUserInfo.name)", displayMode: .inline)
.onAppear{
DispatchQueue.global().async{
self.getMatchId(partner: self.matchUserInfo)
}
_ = MessageViewModel(matchId: self.matchId)
}
.onDisappear{
print(self.msgVM.messages)
}
}
func getMatchId(partner: User){
Firestore.firestore().collection("MatchTable").document(self.shareData.currentUserData["id"] as? String ?? "").collection("MatchUser").whereField("MatchUserId", isEqualTo: partner.id).getDocuments { (snap, err) in
if let snap = snap {
for id in snap.documents{
self.msgVM.matchId = id.data()["MatchRoomId"] as? String ?? ""
_ = MessageViewModel(matchId: self.msgVM.matchId)
self.matchId = self.msgVM.matchId
}
}
}
}
}
Also this is the firebase part.
import Foundation
import FirebaseFirestore
struct Message: Identifiable {
var id: String
var msg: String
var fromUser: String
var toUser: String
var date: Timestamp
var matchId : String
}
class MessageViewModel: ObservableObject {
var datas = FirebaseData()
let db = Firestore.firestore()
#Published var matchId:String
#Published var messages = [Message]()
init(matchId: String){
self.matchId = matchId
self.db.collection("Messages").whereField("matchId", isEqualTo: self.matchId).order(by: "date").addSnapshotListener { (snap, error) in
if let error = error {
print(error.localizedDescription)
return
}
if let snap = snap {
for i in snap.documentChanges {
if i.type == .added{
let toUser = i.document.get("toUser") as! String
let fromUser = i.document.get("fromUser") as! String
let message = i.document.get("message") as! String
let id = i.document.documentID
let date = i.document.get("date") as! Timestamp
let matchId = i.document.get("matchId") as! String
self.messages.append(Message(id: id, msg: message, fromUser: fromUser, toUser: toUser, date: date, matchId: matchId))
}
}
}
}
}
func sendMsg(msg: String, toUser: String, fromUser: String, matchId: String){
let data = [
"message": msg,
"toUser": toUser,
"fromUser": fromUser,
"date": Timestamp(),
"matchId": matchId
] as [String : Any]
Firestore.firestore().collection("Messages").addDocument(data: data){ error in
if let err = error {
print(err.localizedDescription)
return
}
print("Sent message")
}
}
}
Thank you
All you should really need is to construct your ObservedObject in an init function:
let matchUserInfo: User
#ObservedObject private var msgVM: MessageViewModel
init(_ user: User) {
self.matchUserInfo = user
self._msgVM = ObservedObject(initialValue: MessageViewModel(matchId: user.matchId))
}
Assuming, of course, that the matchId you care about is passed in via your User type. You know your data structures better than I do, the key here is to simply create your observed object based on your passed in User.
I have a pretty complex data structure in my app, which I need to manipulate. I am trying to keep track of how many types of bugs a player has in thier garden. There are ten types of bugs, each with ten patterns, each pattern having ten colors. So there are 1000 unique bugs possible, and I want to track how many of each of these types the player has. The nested dictionary looks like:
var colorsDict: [String : Int]
var patternsDict: [String : Any] // [String : colorsDict]
var bugsDict: [String : Any] // [String : patternsDict]
I do not get any errors or complaints with this syntax.
When I want to increment the player's bug collection though, doing this:
bugs["ladybug"]["spotted"]["red"]++
I get this error: String is not convertible to 'DictionaryIndex< String, Any >' with the error's carrot under the first string.
Another similar post suggested using "as Any?" in the code, but the OP of that post only had a dictionary one deep so could do that easily with: dict["string"] as Any? ...
I am not sure how to do this with a multilevel dictionary. Any help would be appreciated.
When working with dictionaries you have to remember that a key might not exist in the dictionary. For this reason, dictionaries always return optionals. So each time you access the dictionary by key you have to unwrap at each level as follows:
bugsDict["ladybug"]!["spotted"]!["red"]!++
I presume you know about optionals, but just to be clear, use the exclamation mark if you are 100% sure the key exists in the dictionary, otherwise it's better to use the question mark:
bugsDict["ladybug"]?["spotted"]?["red"]?++
Addendum: This is the code I used for testing in playground:
var colorsDict = [String : Int]()
var patternsDict = [String : [String : Int]] ()
var bugsDict = [String : [String : [String : Int]]] ()
colorsDict["red"] = 1
patternsDict["spotted"] = colorsDict
bugsDict["ladybug"] = patternsDict
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 1
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 2
bugsDict["ladybug"]!["spotted"]!["red"]!++ // Prints 3
bugsDict["ladybug"]!["spotted"]!["red"]! // Prints 4
Another option: You could try calling dict.value( forKeyPath: "ladybug.spotted.red" )!
So I just tried this with Swift 5:
import Foundation
var d = [ "ladybug" : [ "spotted" : [ "red" : 123 ] ] ] as [String:Any]
(d as NSDictionary).value(forKeyPath: "ladybug.spotted.red")
and it works, but this is probably the best way:
d["ladybug"]?["spotted"]?["red"]
I had the same issue, where I wanted to get boolValue nested in dictionary.
{
"Level1": {
"leve2": {
"code": 0,
"boolValue": 1
}
}
}
I tried a lot of solution but those didn't worked for me as i was missing type casting. So I used following code to get the boolValue from json, where json is a nested dictionary of type [String:Any].
let boolValue = ((json["level1"]
as? [String: Any])?["level2"]
as? [String: Any])?["boolValue"] as? Bool
My primary use case was reading ad-hoc values from a deep dictionary. None of the answers given worked for me in my Swift 3.1 project, so I went looking and found Ole Begemann's excellent extension for Swift dictionaries, with a detailed explanation on how it works.
I've made a Github gist with the Swift file I made for using it, and I welcome feedback.
To use it, you can add the Keypath.swift into your project, and then you can simply use a keyPath subscript syntax on any [String:Any] dictionary as follows.
Considering you have a JSON object like so:
{
"name":"John",
"age":30,
"cars": {
"car1":"Ford",
"car2":"BMW",
"car3":"Fiat"
}
}
stored in a dictionary var dict:[String:Any]. You could use the following syntax to get to the various depths of the object.
if let name = data[keyPath:"name"] as? String{
// name has "John"
}
if let age = data[keyPath:"age"] as? Int{
// age has 30
}
if let car1 = data[keyPath:"cars.car1"] as? String{
// car1 has "Ford"
}
Note that the extension supports writing into nested dictionaries as well, but I haven't yet used this.
I still haven't found a way to access arrays within dictionary objects using this, but it's a start! I'm looking for a JSON Pointer implementation for Swift but haven't found one, yet.
If it's only about retrieval (not manipulation) then here's a Dictionary extension for Swift 3 (code ready for pasting into Xcode playground) :
//extension
extension Dictionary where Key: Hashable, Value: Any {
func getValue(forKeyPath components : Array<Any>) -> Any? {
var comps = components;
let key = comps.remove(at: 0)
if let k = key as? Key {
if(comps.count == 0) {
return self[k]
}
if let v = self[k] as? Dictionary<AnyHashable,Any> {
return v.getValue(forKeyPath : comps)
}
}
return nil
}
}
//read json
let json = "{\"a\":{\"b\":\"bla\"},\"val\":10}" //
if let parsed = try JSONSerialization.jsonObject(with: json.data(using: .utf8)!, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<AnyHashable,Any>
{
parsed.getValue(forKeyPath: ["a","b"]) //-> "bla"
parsed.getValue(forKeyPath: ["val"]) //-> 10
}
//dictionary with different key types
let test : Dictionary<AnyHashable,Any> = ["a" : ["b" : ["c" : "bla"]], 0 : [ 1 : [ 2 : "bla"]], "four" : [ 5 : "bla"]]
test.getValue(forKeyPath: ["a","b","c"]) //-> "bla"
test.getValue(forKeyPath: ["a","b"]) //-> ["c": "bla"]
test.getValue(forKeyPath: [0,1,2]) //-> "bla"
test.getValue(forKeyPath: ["four",5]) //-> "bla"
test.getValue(forKeyPath: ["a","b","d"]) //-> nil
//dictionary with strings as keys
let test2 = ["one" : [ "two" : "three"]]
test2.getValue(forKeyPath: ["one","two"]) //-> "three"
Unfortunately none of these methods worked for me, so I built my own to use a simple string path like "element0.element1.element256.element1", etc. Hope this save a time for others. (just use a dots between name of elements in string)
Json example:
{
"control": {
"type": "Button",
"name": "Save",
"ui": {
"scale": 0.5,
"padding": {
"top": 24,
"bottom": 32
}
}
}
}
Step 1, convert json String to Dictionary
static func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
Step 2, helper to get a nested objects
//path example: "control.ui.scale"
static func getDictValue(dict:[String: Any], path:String)->Any?{
let arr = path.components(separatedBy: ".")
if(arr.count == 1){
return dict[String(arr[0])]
}
else if (arr.count > 1){
let p = arr[1...arr.count-1].joined(separator: ".")
let d = dict[String(arr[0])] as? [String: Any]
if (d != nil){
return getDictValue(dict:d!, path:p)
}
}
return nil
}
Step 3, use helper
let controlScale = getDictValue(dict:dict, path: "control.ui.scale") as! Double?
print(controlScale)
let controlName = getDictValue(dict:dict, path: "control.name") as! String?
print(controlName)
Returns
0.5
Save
The Swift 4 default: subscript for Dictionaries makes makes updating values in nested Dictionaries much more concise.
Get and Set a default value rather than dealing with optionals:
var dict = [String : [String : String]]()
dict["deep", default: [:]]["nested"] = "dictionary"
print(dict)
// ["deep": ["nested": "dictionary"]]
https://swift.org/blog/dictionary-and-set-improvements/
You can use this extension:
extension Dictionary {
/// - Description
/// - The function will return a value on given keypath
/// - if Dictionary is ["team": ["name": "KNR"]] the to fetch team name pass keypath: team.name
/// - If you will pass "team" in keypath it will return team object
/// - Parameter keyPath: keys joined using '.' such as "key1.key2.key3"
func valueForKeyPath <T> (_ keyPath: String) -> T? {
let array = keyPath.components(separatedBy: ".")
return value(array, self) as? T
}
/// - Description:"
/// - The function will return a value on given keypath. It keep calling recursively until reach to the keypath. Here are few sample:
/// - if Dictionary is ["team": ["name": "KNR"]] the to fetch team name pass keypath: team.name
/// - If you will pass "team" in keypath it will return team object
/// - Parameters:
/// - keys: array of keys in a keypath
/// - dictionary: The dictionary in which value need to find
private func value(_ keys: [String], _ dictionary: Any?) -> Any? {
guard let dictionary = dictionary as? [String: Any], !keys.isEmpty else {
return nil
}
if keys.count == 1 {
return dictionary[keys[0]]
}
return value(Array(keys.suffix(keys.count - 1)), dictionary[keys[0]])
}
}
Usage:
let dictionary = ["values" : ["intValue": 3]]
let value: Int = dictionary.valueForKeyPath("values.intValue")
You can use the following syntax on Swift 3/4:
if let name = data["name"] as? String {
// name has "John"
}
if let age = data["age"] as? Int {
// age has 30
}
if let car = data["cars"] as? [String:AnyObject],
let car1 = car["car1"] as? String {
// car1 has "Ford"
}
Yet another approach using various overloaded Dictionary subscript implementations:
let dict = makeDictionary(fromJSONString:
"""
{
"control": {
"type": "Button",
"name": "Save",
"ui": {
"scale": 0.5,
"padding": {
"top": 24,
"bottom": 32
}
}
}
}
""")!
dict[Int.self, ["control", "ui", "padding", "top"]] // 1
dict[Int.self, "control", "ui", "padding", "top"] // 2
dict[Int.self, "control.ui.padding.top"] // 3
And the actual implementations:
extension Dictionary {
// 1
subscript<T>(_ type: T.Type, _ pathKeys: [Key]) -> T? {
precondition(pathKeys.count > 0)
if pathKeys.count == 1 {
return self[pathKeys[0]] as? T
}
// Drill down to the innermost dictionary accessible through next-to-last key
var dict: [Key: Value]? = self
for currentKey in pathKeys.dropLast() {
dict = dict?[currentKey] as? [Key: Value]
if dict == nil {
return nil
}
}
return dict?[pathKeys.last!] as? T
}
// 2. Calls 1
subscript<T>(_ type: T.Type, _ pathKeys: Key...) -> T? {
return self[type, pathKeys]
}
}
extension Dictionary where Key == String {
// 3. Calls 1
subscript<T>(_ type: T.Type, _ keyPath: String) -> T? {
return self[type, keyPath.components(separatedBy: ".")]
}
}
func makeDictionary(fromJSONString jsonString: String) -> [String: Any]? {
guard let data = jsonString.data(using: .utf8)
else { return nil}
let ret = try? JSONSerialization.jsonObject(with: data, options: [])
return ret as? [String: Any]
}
Yet another Dictionary extension
public extension Dictionary where Key: Hashable, Value: Any {
subscript(keyPath path: String) -> Value? {
self[keyPath: path.components(separatedBy: ".").compactMap { $0 as? Key }]
}
private subscript(keyPath keys: [Key]) -> Value? {
var keys = keys
switch keys.first {
case .some(let key) where keys.count == 1:
return self[key]
case .some(let key) where keys.count > 1:
keys.removeFirst()
return (self[key] as? Dictionary<Key, Value>)?[keyPath: keys]
default:
return nil
}
}
}
Test code:
let dict: [String: Any] = [
"user": [
"name": "Giorgio",
"surname": "Baldazzi"
]
]
let keyPath = "user.name"
print(String(describing: dict[keyPath: keyPath]))