I have this method return User observable
internal func getUser() -> Observable<User> {
let result = withRealm("getting user") { realm -> Observable<User> in
let realm = try Realm()
let user = realm.objects(User.self).first
return Observable.from(optional: user)
}
return result ?? .empty()
}
Now i want to have another method that returns me if user is loggedin, how it will be done?
private var _isLoggedIn: Observable<User> {
return getUser().count > 0 //error
}
I would suggest moving to enum to get rid of optionals on make it easier to manage:
enum LoginState {
case loggedIn(user: User)
case loggedOut
}
internal func getLoginState() -> Observable<LoginState> {
let user = withRealm("getting user") { realm -> User? in
let realm = try Realm()
return realm.objects(User.self).first
}
if let user = user {
return Observable.just(.loggedIn(user))
} else {
return Observable.just(.loggedOut)
}
}
private var _isLoggedIn: Observable<Bool> {
return getLoginState().map {
switch $0 {
case .loggedIn: return true
case .loggedOut: return false
}
}
}
Related
I need to check if the following query return a true or false I have try with escaping but I receive an error:
here below the code of the function:
func checkBookingTime(user: UserModel, completion:#escaping(Bool) -> ()) {
if let userId = user.id {
db.collection("bookings").whereField("publisherUser", isEqualTo: userId).addSnapshotListener { querySnapshot, error in
if let querySnapshot = querySnapshot {
let count = querySnapshot.documents.count
if(count == 0) {
completion(true)
} else {
completion(false)
}
print("number of doc: \(querySnapshot.documents.count)")
}
}
}
}
and here is when I'm good to use it:
func loadBookingCheckTime(user: UserModel) -> Bool {
self.bookingRepository.checkBookingTime(user: user) { (isSuccess) in
if isSuccess {
print("si")
} else {
print("no")
}
}
}
but I receive the following error:
Cannot convert return expression of type '()' to return type 'Bool'
Can someone give me some hint?
Why dont u use just:
bookingRepository.checkBookingTime(user: user) { (isSuccess) in
if isSuccess {
print("si")
} else {
print("no")
}
}
You have your bool in isSucess.
func loadBookingCheckTime(user: UserModel) -> Bool can be skipped.
You need to use a completion handler in your loadBookingCheckTime if you want to do it that way. However, as #saro mentioned, you could just call it directly.
I am creating an onboarding portion of an app which gets the user's contacts to check which already have the app to add as friends. I'm using the CNContact framework. I have created several methods I'm using to get a full list of the users' contacts, check if they have the app, and enumerate them in a UITableView. However, when the view loads, the app crashes with the error "A property was not requested when contact was fetched." I already make a fetch request with the keys CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, ad CNContactImageDataKey. I've included all my code here:
import Foundation
import Contacts
import PhoneNumberKit
struct ContactService {
static func createContactArray() -> [CNContact] {
var tempContacts = [CNContact]()
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let _ = error {
print("failed to request access to contacts")
return
}
if granted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
request.sortOrder = CNContactSortOrder.familyName
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stop) in
tempContacts.append(contact)
})
} catch {
print("unable to fetch contacts")
}
print("created contact list")
}
}
return tempContacts
}
static func createFetchedContactArray(contactArray: [CNContact], completion: #escaping ([FetchedContact]?) -> Void) -> Void {
var temp = [FetchedContact]()
getNumsInFirestore { (nums) in
if let nums = nums {
for c in contactArray {
let f = FetchedContact(cnContact: c, numsInFirebase: nums)
temp.append(f)
}
return completion(temp)
} else {
print("Error retrieving numbers")
}
}
return completion(nil)
}
static func getNumsInFirestore(_ completion: #escaping (_ nums : [String]?) -> Void ) -> Void {
var numsInFirebase = [String]()
let db = FirestoreService.db
db.collection(Constants.Firestore.Collections.users).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting user documents: \(err)")
completion(nil)
} else {
for doc in querySnapshot!.documents {
let dict = doc.data()
numsInFirebase.append(dict[Constants.Firestore.Keys.phone] as! String)
}
completion(numsInFirebase)
}
}
}
static func getPhoneStrings(contact: CNContact) -> [String] {
var temp = [String]()
let cnPhoneNums = contact.phoneNumbers
for n in cnPhoneNums {
temp.append(n.value.stringValue)
}
return temp
}
static func hasBump(contact: CNContact, completion: #escaping (_ h: Bool) -> Void ) -> Void {
let contactNums = ContactService.getPhoneStrings(contact: contact)
ContactService.getNumsInFirestore { (nums) in
if let nums = nums {
return completion(contactNums.contains(where: nums.contains))
} else {
print("Error retrieving numbers from firestore")
return completion(false)
}
}
}
static func anyContactsWithBump(completion: #escaping (_ yes: Bool) -> Void) {
let contacts = createContactArray()
var tempBool = false
let workgroup = DispatchGroup()
for c in contacts {
workgroup.enter()
hasBump(contact: c) { (has) in
if has {
tempBool = true
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
completion(tempBool)
}
}
}
Then I call the methods in the view controller class:
import UIKit
import Contacts
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class AddContactsVC: UIViewController {
var fetchedContactsWithBump: [FetchedContact] = []
override func viewDidLoad() {
let cnContacts = ContactService.createContactArray()
var contactsWithBump = [CNContact]()
let workgroup = DispatchGroup()
for contact in cnContacts {
workgroup.enter()
ContactService.hasBump(contact: contact) { (has) in
if has {
contactsWithBump.append(contact)
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
print("Number of contacts with Bump: \(contactsWithBump.count)")
ContactService.createFetchedContactArray(contactArray: contactsWithBump) { (fetchedContacts) in
if let fetchedContacts = fetchedContacts {
self.fetchedContactsWithBump = fetchedContacts
} else {
print("Error creating fetchedContacts array.")
}
}
}
I also get the message "Error creating fetchedContacts array" in the console, so I know something is going wrong with that method, I'm just not sure what. Any help is appreciated!
Edit: The exception is thrown at 3 points: 1 at the first line of my FetchedContact init method, which looks like this:
init(cnContact: CNContact, numsInFirebase: [String]) {
if cnContact.imageDataAvailable { self.image = UIImage(data: cnContact.imageData!) }
It also points to the line let f = FetchedContact(cnContact: c, numsInFirebase: nums) in createFetched contact array, and finally at my completion(numsInFirebase) call in getNumsInFirestore.
To start with
let contacts = createContactArray()
will always return an empty array.
This function has a return statement outside the closure so will immediately return an empty array.
Change createContactArray to use a completion handler like the other functions you have to populate contacts from inside the closure.
Xcode 11.3, Swift 5.1.3
I am trying currently to create a custom property wrapper that allows me to link variables to a Firebase database. When doing this, to make it update the view, I at first tried to use the #ObservedObject #Bar var foo = []. But I get an error that multiple property wrappers are not supported. Next thing I tried to do, which would honestly be ideal, was try to make my custom property wrapper update the view itself upon being changed, just like #State and #ObservedObject. This both avoids needing to go down two layers to access the underlying values and avoid the use of nesting property wrappers. To do this, I checked the SwiftUI documentation and found out that they both implement the DynamicProperty protocol. I tried to use this too but failed because I need to be able to update the view (call update()) from within my Firebase database observers, which I cannot do since .update() is mutating.
Here is my current attempt at this:
import SwiftUI
import Firebase
import CodableFirebase
import Combine
#propertyWrapper
final class DatabaseBackedArray<Element>: ObservableObject where Element: Codable & Identifiable {
typealias ObserverHandle = UInt
typealias Action = RealtimeDatabase.Action
typealias Event = RealtimeDatabase.Event
private(set) var reference: DatabaseReference
private var currentValue: [Element]
private var childAddedObserverHandle: ObserverHandle?
private var childChangedObserverHandle: ObserverHandle?
private var childRemovedObserverHandle: ObserverHandle?
private var childAddedActions: [Action<[Element]>] = []
private var childChangedActions: [Action<[Element]>] = []
private var childRemovedActions: [Action<[Element]>] = []
init(wrappedValue: [Element], _ path: KeyPath<RealtimeDatabase, RealtimeDatabase>, events: Event = .all,
actions: [Action<[Element]>] = []) {
currentValue = wrappedValue
reference = RealtimeDatabase()[keyPath: path].reference
for action in actions {
if action.event.contains(.childAdded) {
childAddedActions.append(action)
}
if action.event.contains(.childChanged) {
childChangedActions.append(action)
}
if action.event.contains(.childRemoved) {
childRemovedActions.append(action)
}
}
if events.contains(.childAdded) {
childAddedObserverHandle = reference.observe(.childAdded) { snapshot in
guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
fatalError("Could not decode value from Firebase.")
}
self.objectWillChange.send()
self.currentValue.append(decodedValue)
self.childAddedActions.forEach { $0.action(&self.currentValue) }
}
}
if events.contains(.childChanged) {
childChangedObserverHandle = reference.observe(.childChanged) { snapshot in
guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
fatalError("Could not decode value from Firebase.")
}
guard let changeIndex = self.currentValue.firstIndex(where: { $0.id == decodedValue.id }) else {
return
}
self.objectWillChange.send()
self.currentValue[changeIndex] = decodedValue
self.childChangedActions.forEach { $0.action(&self.currentValue) }
}
}
if events.contains(.childRemoved) {
childRemovedObserverHandle = reference.observe(.childRemoved) { snapshot in
guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
fatalError("Could not decode value from Firebase.")
}
self.objectWillChange.send()
self.currentValue.removeAll { $0.id == decodedValue.id }
self.childRemovedActions.forEach { $0.action(&self.currentValue) }
}
}
}
private func setValue(to value: [Element]) {
guard let encodedValue = try? FirebaseEncoder().encode(currentValue) else {
fatalError("Could not encode value to Firebase.")
}
reference.setValue(encodedValue)
}
var wrappedValue: [Element] {
get {
return currentValue
}
set {
self.objectWillChange.send()
setValue(to: newValue)
}
}
var projectedValue: Binding<[Element]> {
return Binding(get: {
return self.wrappedValue
}) { newValue in
self.wrappedValue = newValue
}
}
var hasActiveObserver: Bool {
return childAddedObserverHandle != nil || childChangedObserverHandle != nil || childRemovedObserverHandle != nil
}
var hasChildAddedObserver: Bool {
return childAddedObserverHandle != nil
}
var hasChildChangedObserver: Bool {
return childChangedObserverHandle != nil
}
var hasChildRemovedObserver: Bool {
return childRemovedObserverHandle != nil
}
func connectObservers(for event: Event) {
if event.contains(.childAdded) && childAddedObserverHandle == nil {
childAddedObserverHandle = reference.observe(.childAdded) { snapshot in
guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
fatalError("Could not decode value from Firebase.")
}
self.objectWillChange.send()
self.currentValue.append(decodedValue)
self.childAddedActions.forEach { $0.action(&self.currentValue) }
}
}
if event.contains(.childChanged) && childChangedObserverHandle == nil {
childChangedObserverHandle = reference.observe(.childChanged) { snapshot in
guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
fatalError("Could not decode value from Firebase.")
}
guard let changeIndex = self.currentValue.firstIndex(where: { $0.id == decodedValue.id }) else {
return
}
self.objectWillChange.send()
self.currentValue[changeIndex] = decodedValue
self.childChangedActions.forEach { $0.action(&self.currentValue) }
}
}
if event.contains(.childRemoved) && childRemovedObserverHandle == nil {
childRemovedObserverHandle = reference.observe(.childRemoved) { snapshot in
guard let value = snapshot.value, let decodedValue = try? FirebaseDecoder().decode(Element.self, from: value) else {
fatalError("Could not decode value from Firebase.")
}
self.objectWillChange.send()
self.currentValue.removeAll { $0.id == decodedValue.id }
self.childRemovedActions.forEach { $0.action(&self.currentValue) }
}
}
}
func removeObserver(for event: Event) {
if event.contains(.childAdded), let handle = childAddedObserverHandle {
reference.removeObserver(withHandle: handle)
self.childAddedObserverHandle = nil
}
if event.contains(.childChanged), let handle = childChangedObserverHandle {
reference.removeObserver(withHandle: handle)
self.childChangedObserverHandle = nil
}
if event.contains(.childRemoved), let handle = childRemovedObserverHandle {
reference.removeObserver(withHandle: handle)
self.childRemovedObserverHandle = nil
}
}
func removeAction(_ action: Action<[Element]>) {
if action.event.contains(.childAdded) {
childAddedActions.removeAll { $0.id == action.id }
}
if action.event.contains(.childChanged) {
childChangedActions.removeAll { $0.id == action.id }
}
if action.event.contains(.childRemoved) {
childRemovedActions.removeAll { $0.id == action.id }
}
}
func removeAllActions(for event: Event) {
if event.contains(.childAdded) {
childAddedActions = []
}
if event.contains(.childChanged) {
childChangedActions = []
}
if event.contains(.childRemoved) {
childRemovedActions = []
}
}
}
struct School: Codable, Identifiable {
/// The unique id of the school.
var id: String
/// The name of the school.
var name: String
/// The city of the school.
var city: String
/// The province of the school.
var province: String
/// Email domains for student emails from the school.
var domains: [String]
}
#dynamicMemberLookup
struct RealtimeDatabase {
private var path: [String]
var reference: DatabaseReference {
var ref = Database.database().reference()
for component in path {
ref = ref.child(component)
}
return ref
}
init(previous: Self? = nil, child: String? = nil) {
if let previous = previous {
path = previous.path
} else {
path = []
}
if let child = child {
path.append(child)
}
}
static subscript(dynamicMember member: String) -> Self {
return Self(child: member)
}
subscript(dynamicMember member: String) -> Self {
return Self(child: member)
}
static subscript(dynamicMember keyPath: KeyPath<Self, Self>) -> Self {
return Self()[keyPath: keyPath]
}
static let reference = Database.database().reference()
struct Event: OptionSet, Hashable {
let rawValue: UInt
static let childAdded = Event(rawValue: 1 << 0)
static let childChanged = Event(rawValue: 1 << 1)
static let childRemoved = Event(rawValue: 1 << 2)
static let all: Event = [.childAdded, .childChanged, .childRemoved]
static let constructive: Event = [.childAdded, .childChanged]
static let destructive: Event = .childRemoved
}
struct Action<Value>: Identifiable {
let id = UUID()
let event: Event
let action: (inout Value) -> Void
private init(on event: Event, perform action: #escaping (inout Value) -> Void) {
self.event = event
self.action = action
}
static func on<Value>(_ event: RealtimeDatabase.Event, perform action: #escaping (inout Value) -> Void) -> Action<Value> {
return Action<Value>(on: event, perform: action)
}
}
}
Usage example:
struct ContentView: View {
#DatabaseBackedArray(\.schools, events: .all, actions: [.on(.constructive) { $0.sort { $0.name < $1.name } }])
var schools: [School] = []
var body: some View {
Text("School: ").bold() +
Text(schools.isEmpty ? "Loading..." : schools.first!.name)
}
}
When I try to use this though, the view never updates with the value from Firebase even though I am positive that the .childAdded observer is being called.
One of my attempts at fixing this was to store all of these variables in a singleton that itself conforms to ObservableObject. This solution is also ideal as it allows the variables being observed to be shared throughout my application, preventing multiples instances of the same date and allowing for a single source of truth. Unfortunately, this too did not update the view with the fetched value of currentValue.
class Session: ObservableObject {
#DatabaseBackedArray(\.schools, events: .all, actions: [.on(.constructive) { $0.sort { $0.name < $1.name } }])
var schools: [School] = []
private init() {
//Send `objectWillChange` when `schools` property changes
_schools.objectWillChange.sink {
self.objectWillChange.send()
}
}
static let current = Session()
}
struct ContentView: View {
#ObservedObject
var session = Session.current
var body: some View {
Text("School: ").bold() +
Text(session.schools.isEmpty ? "Loading..." : session.schools.first!.name)
}
}
Is there any way to make a custom property wrapper that also updates a view in SwiftUI?
Making use of the DynamicProperty protocol we can easily trigger view updates by making use of SwiftUI's existing property wrappers. (DynamicProperty tells SwiftUI to look for these within our type)
#propertyWrapper
struct OurPropertyWrapper: DynamicProperty {
// A state object that we notify of updates
#StateObject private var updater = Updater()
var wrappedValue: T {
get {
// Your getter code here
}
nonmutating set {
// Tell SwiftUI we're going to change something
updater.notifyUpdate()
// Your setter code here
}
}
class Updater: ObservableObject {
func notifyUpdate() {
objectWillChange.send()
}
}
}
The solution to this is to make a minor tweak to the solution of the singleton. Credits to #user1046037 for pointing this out to me. The problem with the singleton fix mentioned in the original post, is that it does not retain the canceller for the sink in the initializer. Here is the correct code:
class Session: ObservableObject {
#DatabaseBackedArray(\.schools, events: .all, actions: [.on(.constructive) { $0.sort { $0.name < $1.name } }])
var schools: [School] = []
private var cancellers = [AnyCancellable]()
private init() {
_schools.objectWillChange.sink {
self.objectWillChange.send()
}.assign(to: &cancellers)
}
static let current = Session()
}
I have a Driver of type Bool and a BehaviorRelay of type Page (which is a custom enum).
enum Page {
case option1(CustomClass1, CustomClass2)
case option2(CustomClass3)
case option3(CustomClass4)
var property1: CustomClass2? {
switch self {
case .option1(_, let custom):
return custom
case .option2, .option3:
return nil
}
}
}
I have the Driver<Bool> in another ViewModel.
class ViewModel1 {
struct Output {
let hasItems: Driver<Bool>
}
let output: Output
init() {
let hasItemsRelay: BehaviorRelay<Bool> = BehaviorRelay<Bool>(value: false)
self.output = Output(
hasItems: hasItemsRelay.asDriver()
)
}
}
And I have a BehaviorRelay<Page?> in my base class.
class ViewModel2 {
let currentPageRelay: BehaviorRelay<Page?> = BehaviorRelay<Page?>(value: nil)
init() {
self.currentPageRelay = BehaviorRelay<Page?>(value: nil)
}
}
In ViewModel2 class I'm trying to catch an event on the hasItems driver of ViewModel1.Input and when I get an event, I need the current value of currentPageRelay and later on do stuff with it. So basically withLatestFrom is the thing I need to use.
class ViewModel2 {
private func test() {
let customViewModel: ViewModel1 = ViewModel1()
customViewModel
.output
.hasItems
.withLatestFrom(currentPageRelay) { ($0, $1) }
.map { (hasItems, page) -> (CustomClass2, Bool)? in
guard let property1 = page?.property1 else { return nil }
return (property1, hasItems)
}
.unwrap()
.drive(onNext: { (property1, hasItems) in
// do stuff
}
.disposed(by: disposeBag)
}
}
Xcode completely loses it on the withLatestFrom. No code completion and it gives the following compile error:
Expression type '(Bool, _)' is ambiguous without more context
I'm completely in the dark about this one. I've already tried everything, providing the correct classes in the parameter list below it, so that it knows what to expect etc, but no luck so far.
I think because .withLatestFrom requires both types it operates on to be of the same observable trait. So both should be either Observable, Driver, Signal, etc.
If you want to keep your Driver in your viewModel a Driver you could add an .asObservable() after the .hasItems:
class ViewModel2 {
let currentPageRelay: BehaviorRelay<Page?> = BehaviorRelay<Page?>(value: nil)
let disposeBag = DisposeBag()
init() {
// self.currentPageRelay = BehaviorRelay<Page?>(value: nil)
}
private func test() {
let customViewModel: ViewModel1 = ViewModel1()
customViewModel
.output
.hasItems
.asObservable()
.withLatestFrom(currentPageRelay) { ($0, $1) }
.map { (hasItems, page) -> (CustomClass2, Bool)? in
guard let property1 = page?.property1 else { return nil }
return (property1, hasItems)
}
.asDriver(onErrorJustReturn: nil)
.drive(onNext: {
guard let (property1, hasItems) = $0 else {
return
}
// do stuff
})
.disposed(by: disposeBag)
}
}
Or add a .asDriver() to currentPageRelay in the withLatestFrom(..):
customViewModel
.output
.hasItems
.withLatestFrom(currentPageRelay.asDriver()) { ($0, $1) }
.map { (hasItems, page) -> (CustomClass2, Bool)? in
guard let property1 = page?.property1 else { return nil }
return (property1, hasItems)
}
.drive(onNext: {
guard let (property1, hasItems) = $0 else {
return
}
// do stuff
})
.disposed(by: disposeBag)
I am developing an application in which I need to fetch all the contacts from device and then set it to favorite contact on button press. I am able to fetch all contacts using [CNContact] in iOS 9 and 10. But don't know how to set it as a favorite contact.
Can we set CNContact as a favorite contact?
Can we make changes in CNContact?
You can store Favourites to Realm DB. Like This,
class FavouriteList: Object {
let favouriteList : List<FavouriteContact> = List<FavouriteContact>()
}
class FavouriteContact: Object {
dynamic var identifier : String? = ""
override class func primaryKey() -> String? {
return "identifier"
}
}
// Add Favourite Contact in Realm
class func add(identifier: String) -> Bool {
var realm: Realm!
do {
realm = try Realm()
realm.beginWrite()
} catch {
print(error.localizedDescription)
}
let realmTask: FavouriteList= FavouriteList()
let favContact: FavouriteContact = FavouriteContact()
// Check ID Exist or Not
let idExists: FavouriteContact? = realm.object(ofType: FavouriteContact.self, forPrimaryKey: identifier)
if idExists?.identifier != nil {
realm.cancelWrite()
return false
} else {
favContact.identifier = identifier
realmTask.favouriteList.append(favContact)
realm.add(realmTask)
}
// Realm Commit
do {
try realm.commitWrite()
} catch {
print("Realm Task Write Error : ", error.localizedDescription)
}
return true
}
// Remove Favourite Contact
class func remove(identifier: String) -> Bool {
var realm: Realm!
do {
realm = try Realm()
realm.beginWrite()
} catch {
print(error.localizedDescription)
}
// Check ID Exist or Not
let idExists: FavouriteContact? = realm.object(ofType: FavouriteContact.self, forPrimaryKey: identifier)
if idExists?.identifier != nil {
realm.delete(idExists!)
} else {
realm.cancelWrite()
return false
}
// Realm Commit
do {
try realm.commitWrite()
} catch {
print("Realm Task Write Error : ", error.localizedDescription)
}
return true
}
// Get Favourite List
class func get(completionHandler: #escaping (_ result: [CNContact]) -> ()) {
var favourites: [CNContact] = [CNContact]()
do {
let realm = try Realm()
let dataRealmContacts: Results<FavouriteList> = realm.objects(FavouriteList.self)
for item in dataRealmContacts {
for contactID in item.favouriteList {
if contactID.identifier != nil {
favourites.append(getContactFromID(identifier: contactID.identifier!))
}
}
}
completionHandler(favourites)
} catch {
print(error.localizedDescription)
completionHandler(favourites)
}
}