How to match 2 users if they both pressed 'Heart' button - swift

My app is very similar to Tinder, and only got the "matching" part left for it to be finished. When the user touches the 'Heart' button, a card with their profile is displayed on the Notifications View to the other user, for them to accept it or not.
In case of yes, the will both 'match' as in Tinder. My problem is in how to make that happen.
Person is presented to the User, and the home view displays the people, that UserManager holds.
struct Person: Identifiable, Hashable {
var username: String
var age: Int
}
struct User {
let username: String // These are let because they're being saved on UserManager
let age: Int
}
class UserManager: ObservableObject {
#Published var userInfo: UserInfo?
#Published var people: [Person] = [] // All People
#Published var matches: [Person] = [] // Matched people
Now, here in UserManager, I fetchAllUsers from Firebase and basically init the people's data by the documentSnapshot.
ref.getDocuments { documentsSnapshot, error in
if let error = error { }
documentsSnapshot?.documents.forEach({ snapshot in
let data = snapshot.data()
self.people.append(.init(data: data))
})
}
And finally, in HomeView, the user taps the button and appends the other person in the matches array, which doesn't make sense cause the other person hasn't even accepted them yet.
struct HomeView: View {
var body: some View {
CircleButtonView(type: .heart) {
if let person = userMng.people.last {
userMng.swipe(person, _direction: .like)
userMng.matches.append(person)
// Should change this to another array?
}
}
}
}
Tried saving it to Firebase and then retrieving the data from the users once matched but I can't especify what person the user has liked, for me to make that network call.
How can I append the liked person to another array and then append it to 'matches' once confirmed that they both like each other?

You need to make a new table in firebase with the name of firendRequest, where table child id will be userID and below it, we will have ids of all users who has sent him the friends request. Please have a look the the schema in below image which will make your concept more clear.
The way, you are getting all the user will add up more computation when number of users start increasing and its not recommend to do such large computation on mobile devices. One more thing, Firesbase is NO-SQL database so you need to duplicate your data to avoid computations. Making duplicate date does not mean you are not implementing the thing right :)

Related

Realm - Update List Order

I've trying to reorder objects from at TableView in a Realm utility class. Many other Stack Overflow questions have said to uses List, however I can't seem to make it work. (Example, example). I'm able to successfully add objects to my list with:
public func addUserSong(song: Song) {
let songList = List<Song>()
songList.append(objectsIn: realm.objects(Song.self))
try! realm.write {
songList.append(song)
realm.add(songList)
}
}
However, I'm not able to preserve the updated order when trying:
public func reorder(from: Int, to: Int) {
let songs = List<Song>()
songs.append(objectsIn: realm.objects(Song.self))
songs.move(from: from, to: to)
try! realm.write {
realm.add(songs)
}
My models are:
class Song: Object {
#Persisted var name: String
#Persisted var key: String
}
class SongList: Object {
let songs = List<Song>()
}
Thanks!
Realm object order is not guaranteed. (unless you specify a sort order)
e.g. if you load 10 songs from Realm, they could come into your app an any order and the order could change between loads. The caveat to that is a Realm List object. Lists always maintain their order.
The problem in the question is you have Song objects stored in Realm but as mentioned above there is no ordering.
So the approach needs to be modified by leveraging a List object for each user to keep track of their songs:
class UserClass: Object {
#Persisted var name: String
#Persisted var songList = List<SongClass>()
}
When adding a song to a user, call it within a write transaction
try! realm.write {
someUser.songList.append(someSong)
}
suppose the user wants to switch the place of song 2 and song 3. Again, within a write transaction:
try! realm.write {
someUser.songList.move(from: 2, to: 3)
}
So then the UI bit - tableViews are backed by a tableView dataSource - this case it would be the songList property. When that dataSource is updated, the tableView should reflect that change.
In this case you would add an observer to the someUser.songList and as the underlying data changes, the observer will react to that change and can then update the UI.
You can do something simple like tableView.reloadData() to reflect the change or if you want fine-grained changes (like row animations for example) you can do that as well. In that same guide, see the code where tableView.deleteRows, .insertRows and .reload is handled. You know what rows were changed in the underlying data there so you can then animate the rows in the tableView.

Model objects that are not developer-defined?

Every SwiftUI tutorial/example uses model objects that are defined by me, the guy writing the app. However, what is the best practice when the model objects are not under my direct control? For example, the HomeKit framework has an API to get all the rooms in a given home. It returns an array of HMRoom objects:
open class HMRoom: NSObject
{
open var name: String { get }
open var accessories: [HMAccessory] { get }
open var uniqueIdentifier: UUID { get }
// Update the room's name in a HomeKit-compliant way that notifies all other HomeKit apps
open func updateName(_ name: String) async throws
}
When I receive an array of HMRoom objects from the HomeKit API, what should I do with them to power SwiftUI? Should I create my own class that looks like this:
final class Room: ObservableObject
{
#Published var name: String
#Published var accessories: [Accessory]
#Published var uniqueIdentifier: UUID
private var representedRoom: HMRoom
init(homekitRoom: HMRoom)
{
// Copy properties from 'homekitRoom' to self, then set `representedRoom` to `homekitRoom` so we can use it to call the updateName(:) function
}
}
Is there, instead, a way for me to extend the HMRoom class directly to inform SwiftUI that name, accessories, and uniqueIdentifier are the properties we must watch for changes in order to reload views appropriately?
It's unclear to me what the best approach is to integrate #Published/#ObservableObject when I don't write the model classes/structs myself.
If you read Apple's document Choosing Between Structures and Classes the Use Classes When You Need to Control Identity section makes you think use a class, however if you read the Use Structures When You Don't Control Identity section and notice that every HomeKit object contains a uniqueIdentifier it turns out we can use structs yay!
struct Room: Identifiable {
let id: UUID
var name: String
}
class HomeDelegate: NSObject, HMHomeDelegate {
weak var homeManagerDelegate: HomeManagerDelegate?
// HomeManagerDelegate sets the primaryHome.
var home: HMHome? {
didSet {
oldValue?.delegate = nil
home?.delegate = self
reload()
}
}
func reload() {
let rooms: [Room]
if let home = home {
rooms = home.rooms.map { r in
Room(id: r.uniqueIdentifier, name: r.name)
}
}else {
rooms = []
}
homeManagerDelegate?.rooms = rooms
}
func home(_ home: HMHome, didUpdateNameFor room: HMRoom) {
if let index = homeManagerDelegate?.rooms.firstIndex(where: { $0.id == room.uniqueIdentifier } ) {
homeManagerDelegate?.rooms[index].name = room.name
}
}
Note: I'd recommend not using structs that mirror the home classes and instead create a struct that contains what you want to show in your UI. And in your model ObservableObject you could have several #Published arrays of different custom structs for different HomeKit related things. Apple's Fruta and Scrumdinger sample projects can help with that.
Why not a class to hold all of your rooms with a published variable like this:
class Rooms: ObservableObject {
#Published rooms: [HMRoom]
}
Isn't that your view model? You don't need the room parameters to be published; you need to know when anything changes and evaluate it from there.

SwiftUI bind array of objects and show changes

I'm trying to build a little app in SwiftUI. It is supposed to show a list of items an maybe change those. However, I am not able to figure out, how the data flow works correctly, so that changes will be reflected in my list.
Let's say I have a class of Item like this:
class Item: Identifiable {
let id = UUID()
var name: String
var dateCreated: Date
}
And this class has an initializer, that assigns each member a useful random value.
Now let's say I want to store a list of items in another class like this:
class ItemStore {
var items = [Item]()
}
This item store is part of my SceneDelegate and is handed to the ContextView.
Now what I want to do is hand one element to another view (from the stack of a NavigationView), where it will be changed, but I don't know how to save the changes made so that they will be reflected in the list, that is shown in the ContextView.
My idea is to make the item store an environment object. But what do I have to do within the item class and how do I have to pass the item to the other view, so that this works?
I already tried something with the videos from Apple's WWDC, but the wrappers there are deprecated, so that didn't work.
Any help is appreciated. Thanks a lot!
The possible approach is to use ObservableObject (from Combine) for storage
class ItemStore: ObservableObject {
#Published var items = [Item]()
// ... other code
}
class Item: ObservableObject, Identifiable {
let id = UUID()
#Published var name: String
#Published var dateCreated: Date
// ... other code
}
and in dependent views
struct ItemStoreView: View {
#ObservedObject var store: ItemStore
// ... other code
}
struct ItemView: View {
#ObservedObject var item: Item
// ... other code
}

Where's the best place to call methods that interact with my database?

I'm creating an app that interacts with a Firestore database. As of now I have a singleton class, DatabaseManager that has all the methods relating to the Firestore database (i.e. get/post methods).
I have a User model called User that has properties such as name, email, photoURL, and some app-specific properties. Any user can edit their profile to update information from a view controller called EditProfileViewController.
Now my question is: is it best to call the DatabaseManager.shared.updateInfo(forUser: user) (where user is a User instance) from EditProfileViewController, User, or some other place?
Sorry if this is an obvious question, but there's going to be a lot of points in the app where I'll need similar logic so I wanted to know what's the best design. Also I'm sure this question has more to with MVC than it does Firebase/Swift.
A couple of thoughts:
Rather than accessing the singleton directly with, DatabaseManager.shared.update(for:), I might instead have a property for the database manager, initialize/inject it with the DatabaseManager.shared, and have whatever needs to interact with the database use that reference, e.g., dataManager.update(for:). The goal would be to allow your unit tests to mock a database manager if and when necessary.
I would not be inclined to have a view controller interact directly with the DatabaseManager. Many of us consider the view controller, which interacts directly with UIKit/AppKit objects, as part of the broader “V” of MVC/MVP/MVVM/whatever. We’d often extricate business logic (including interaction with the database manager) out of the view controller.
I personally wouldn’t bury it under the User object, either. I’d put it in an extension of the database manager, and called from the view model, the presenter, or whatever you personally want to call that object with the business logic.
Is there a reason you're using a singleton to contain all the Firestore logic? User model should contain the method updateInfo.
Here's an example i've used with Firestore:
class Group {
// can read the var anywhere, but an only set value in this class
private(set) var groupName: String!
private(set) var guestsInGroup: Int!
private(set) var joinedGroup: Bool!
private(set) var timeStampGroupCreated: Date!
private(set) var documentId: String!
init(groupName: String, guestsInGroup: Int, joinedGroup: Bool, timeStampGroupCreated: Date, documentId: String) {
self.groupName = groupName
self.guestsInGroup = guestsInGroup
self.joinedGroup = joinedGroup
self.timeStampGroupCreated = timeStampGroupCreated
self.documentId = documentId
}
// method to parse Firestore data to array, that table view will display
class func parseData(snapshot: QuerySnapshot?) -> [Group]{
var groups = [Group]()
guard let snap = snapshot else { return groups }
for document in snap.documents {
let data = document.data()
let groupName = data[GROUP_NAME] as? String ?? "No Group Name"
let guestsInGroup = data[GUESTS_IN_GROUP] as? Int ?? 0
let joinedGroup = data[JOINED_GROUP] as? Bool ?? false
let timeStampGroupCreated = data[TIMESTAMP_GROUP_CREATED] as? Date ?? Date()
let documentId = document.documentID
// add objects with fetched data into thoughts array
let newGroup = Group(groupName: groupName, guestsInGroup: guestsInGroup, joinedGroup: joinedGroup, timeStampGroupCreated: timeStampGroupCreated, documentId: documentId)
groups.append(newGroup)
}
return groups
}
}

User Class Design

I am very new to design patterns, so I am in need of help determining a good way of designing a specific part of an iOS app I am working on.
I have a User object, that represents the user that is logged in. It is very simple, and looks like this:
class User {
var firstName: String
var lastName: String
var photo: String //must be stored as a string for the filename.
var listOfJoinedOpportunities = [Opportunity]()
var listOfJoinedOpportunitiesKeys = [String]()
var listOfFriendsOnTheApp: NSArray
var bio: String
var email: String
var userID: String //A unique ID that is used to persist data about the user to the database (Firebase).
init(firstName: String, lastName: String, photo: String, email:String, userID: String, joinedEvents: [Opportunity],joinedStrings: [String]){
self.firstName = firstName
self.lastName = lastName
self.photo = photo
self.listOfJoinedOpportunities = joinedEvents
self.listOfFriendsOnTheApp = []
self.listOfJoinedOpportunitiesKeys = []
self.bio = ""
self.email = email
self.userID = userID
}
}
Currently I am only in need of an instance that represents the logged in user, but I could foresee needing this class if I add features to interact with other users of the app.
A total of 5 views include interactions that need to read and write to the user object that represents the logged in user.
Currently, I am passing around the object from controller to controller by creating new references. OR, I am creating a new object that represents the same logged-in user from data that was saved to the database (Firebase).
Please help!
Passing the object to the destination view is actually the preferred way to handle this (prepareForSegue). That being said, you can also simply store the User object instance in your AppDelegate and reference it from any view easily if that fits your model.
The drawback to storing references in the App Delegate is clutter and it may be unnecessary in more complex cases since they'll always be allocated (lookup lazy loading as well). Generally you want to pass objects along so the previous views can dealloc which in turn lowers your memory footprint. This way if you receive a memory warning you can handle it appropriately to recover properly.
// Set a var in your appDelegate
var user:User?
// Accessing it from any view
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let loggedInUser = appDelegate.user