I am working on a classified ads app (something like craigslist) I am looking at how best to structure the data model using Firestore (NoSQL Database).
The issue I'm facing is structuring items that have additional fields. For instance, electronic items will have additional fields such as RAM, Storage, etc.
I currently have the following model
enum Category: Codable {
case standard
case phone(Phone)
case tv(TV)
}
struct Item: Codable {
var document_ID: String? = nil
var title: String
var created_at: Int
var category: Category
}
struct Phone: Codable {
var brand: String
var model: String
var RAM: String
var storage: String
}
struct TV: Codable {
var size: Int
var screen_technology: String
}
Although I am not sure this is the best way as the category object is being mapped too many times.
Related
I don't know how to write ForEach in Array in struct by SwiftUI.
struct Group: Identifiable,Decodable{
#DocumentID var id: String?
var name: String
var members: [[String:String]]
}
I want to display member's array by using ForEach.
#ObservedObject var groupVM: GroupViewModel
var body: some View {
NavigationView {
List{
ScrollView{
LazyVStack(alignment: .leading){
ForEach(groupVM.group.members){ member in
groupMemberCell()
}
}
}
}
}
}
I got this error Referencing initializer 'init(_:content:)' on 'ForEach' requires that '[String : String]' conform to 'Identifiable'.
Please tell me how to write the right way.
This is because the value for members (of type [[String: String]]) does not automatically conform to Identifiable.
It depends on your model as to how each individual member (with data [String: String]) needs to be identified, as it's not clear from this dictionary how you would do this (a dictionary is a bunch of keys and values for each member, so how do we know which member is which based off this data?).
I'd suggest modelling each member as its own object and include a field that allows you to uniquely identify each member (such as having an id for each member, which could be their user id in your application for example).
struct Member: Identifiable, Decodable {
var id: String
var data: [String: String]
}
Your group would then look like:
struct Group: Identifiable, Decodable {
#DocumentID var id: String?
var name: String
var members: [Member]
}
I'm trying to make a Storage Expiration Notification APP.
I create a class called Product, here are the properties.
#NSManaged public var productName: String?
#NSManaged public var quantity: String?
#NSManaged public var category = ["", "Food", "Daily", "Makeup"]
#NSManaged public var chooseCategory: Int16
#NSManaged public var purchaseDate: String?
#NSManaged public var expiredDate: String?
#NSManaged public var productID: String?
But there's an Error showed that #NSManaged property cannot have an initial value
Hence, I only can move the Category array(use picker controller to choose the values) to ViewContorller.swift. But I want to create a Customize Category array that users can change the value. For instance, the default category is ["Food", "Daily", "Makeup"], users can change the value to ["Drink", "Wine", "Battery"]. Should I use archiving, or create a new class? I have no idea how to implement it.
The Core Data way to do this is by overriding awakeFromInsert. That function is inherited from NSManagedObject and is called once when the object is first inserted into a managed object context. For this case it would look something like
func awakeFromInsert() {
category = ["", "Food", "Daily", "Makeup"]
}
It doesn't work in exactly the same way as a Swift initial value but it has the same effect since it happens when you create a new instance.
The error is right, an #NSManaged property cannot have an initial value.
A lightweight Swift solution is a JSON string attribute and a computed property for the conversion.
#NSManaged public var category : String
and
var categoryArray : [String] {
get { (try? JSONDecoder().decode([String].self, from: Data(category.utf8))) ?? [] }
set {
let data = try! JSONEncoder().encode(newValue)
category = String(data: data, encoding: .utf8)!
}
}
Set the default value
"[\"\",\"Food\",\"Daily\",\"Makeup\"]
either in awakeFromInsert or in Interface Builder in the model
I have a User object
#objc(User)
public class User: NSManagedObject {
#NSManaged public var firstname: String
#NSManaged public var lastname: String
#NSManaged public var country: String
#NSManaged public var friends: NSSet // of User objects
var full: String {
firstname + " " + lastname
}
var friendsArray: [User] {
friends.allObjects as? [User] ?? []
}
}
and at some point I want to map a large array of users (80k objects) to an array of View models
struct ItemViewModel: Hashable {
let id: UUID
let friendsName: [String]
}
Without lazy it takes a long time, so I have opted for the usag of lazy:
func prepareViewModel(users: [User]) -> [ItemViewModel] {
users.map { user in
let friendsName = user.friendsArray.lazy.filter{["en", "fr"].contains($0.country)}.map(\.full)
return ItemViewModel(id: UUID(), friendsName: friendsName)
}
}
But I get an error:
Cannot convert value of type 'LazyMapSequence<LazyFilterSequence<LazySequence<[User]>.Elements>.Elements, String>'
(aka 'LazyMapSequence<LazyFilterSequence<Array<User>>, String>') to expected argument type '[String]'
It makes sense because now the friends names array will be processed lazily later. I have tried to convert the view model struct to hold:
struct ItemViewModel: Hashable {
let id: UUID
let friendsName: LazyMapSequence<LazyFilterSequence<[User]>, String>
}
But now it's not Hashable is there a way to keep the auto-conformance to Hashable when using LazyMapSequence<LazyFilterSequence<[User]>, String> as type for ItemViewModel and any tips on how to improve performance of logic
I have a simple view below to display all of the contacts for the user:
struct AllContactsView: View {
static let withState: some View = AllContactsView().environmentObject(AllContactsProvider())
#EnvironmentObject var contactsProvider: AllContactsProvider
let title: UserListType = .selectInvitees
var body: some View {
List(self.contactsProvider.invitees) { invite in
self.row(for: invite)
}
.navigationBarTitle(Text(self.title.rawValue))
.onAppear(perform: self.contactsProvider.fetch)
}
func row(for invite: Invitee) -> some View {
// everything below is only printed out once!
print(self.contactsProvider.invitees.prettyPrinted) // shows correct array of contacts
print(type(of: self.contactsProvider.invitees)) // this is indeed an array
print(invite) // prints out the first item in the array (which is expected on first pass)
return UserRow(invitee: invite)
}
}
I am manipulating the array of CNContacts I get like this to an array of Invitees, which is what I am attempting to display in my list:
self?.invitees = contacts.asSimpleContacts.map({ $0.asUser.asInvitee })
Using the supporting functions and extensions below:
// Contact Value simplified so we can pass it around as a value type.
public struct SimpleContact: Hashable, Codable {
let firstName: String
let lastName: String
let emails: [String]
let phoneNumbers: [PhoneNumber]
var fullName: String { "\(self.firstName) \(self.lastName)" }
var asUser: User {
User(
id: Constants.unsavedID,
username: self.fullName,
picURL: "al",
email: self.emails.first ?? "",
phone: self.phoneNumbers.first ?? "",
created: Date().timeIntervalSince1970
)
}
}
extension CNContact {
/// Returns the `SimpleContact` representation of `self`
var asSimpleContact: SimpleContact {
SimpleContact(
firstName: self.givenName,
lastName: self.familyName,
emails: self.emailAddresses.map({ String($0.value) }),
phoneNumbers: self.phoneNumbers.map({ Authentication.sanitize(phoneNo: $0.value.stringValue) })
)
}
}
extension Array where Element == CNContact {
/// Returns the `SimpleContact` mapping of `self`
var asSimpleContacts: [SimpleContact] { self.map({ $0.asSimpleContact }) }
}
public struct User: Hashable, Codable, Identifiable {
public let id: String
let username: String
let picURL: String
let email: String
let phone: String
let created: Double
var asInvitee: Invitee { Invitee(user: self, isGoing: false) }
}
The contacts are populated into self.contactsProvider.invitees as expected following self.contactsProvider.fetch(). However, SwiftUI is displaying self.contactsProvider.invitees.count instances of self.contactsProvider.invitees.first, rather than each contact. I have compared my approach below to other examples online and can't seem to find where I went wrong. I have determined that the issue lies somewhere with the contacts manipulation - when I supply a mocked array of invitees, everything works as expected, despite things compiling and running as expected without the mocks, and printing and debugging not revealing anything.
Any help would be appreciated.
I just ran into this issue, to answer a bit more clearly:
Each row in a SwiftUI list should be generated with a unique ID.
If you are using the List() function to create the view, make sure you are specifying an id
struct MenuView: View {
var body: some View {
VStack {
Section(header: MenuSectionHeader()) {
//item.sku is the unique identifier for the row
List(Menu.allItems, id:\.sku) { item in
MenuItemRow(item)
}
}
}
}
For anyone who runs across something similar, the problem was that the ID I was instantiating the objects with was not unique. Curious that this would be the expected behavior if that error is made, but thats what it was.
I used to write Models in swift projects in separate files and that what I usually see in others projects, but wouldn't be easier to put them in one file? I want to know which is the best practice and why we supposed to follow? here an example of separating them:
User.swift
import Foundation
class User: NSObject {
var name: String?
var email: String?
var id: String?
}
Post.swift
class Post: NSObject {
var id: String?
var type: String?
var imageUrl: String?
var caption: String?
}