I am trying to determine what the best location would be for putting a firebase Performance trace. I want to see how long it is taking my app to pull data.
In my VC I have the following
func pullAllUsersCards() {
// 1 Start Here?
FirebaseUtility.shared.getCards { (cards, errMessage) in
if let theCards = cards {
if theCards.count < 1 {
if let addVC = self.storyboard?.instantiateViewController(withIdentifier: StoryboardKeys.addCardViewControllerStoryboardID) as? AddCardViewController {
let addNavigation = UINavigationController(rootViewController: addVC)
if UIDevice.current.userInterfaceIdiom == .pad {
self.splitViewController?.present(addNavigation, animated: true, completion: nil)
} else {
self.present(addNavigation, animated: true, completion: nil)
}
}
} else {
// 2 Start Here?
MBProgressHUD.showAdded(to: self.view, animated: true)
self.cardArray = theCards
self.tableView.reloadData()
MBProgressHUD.hide(for: self.view, animated: true)
}
}
}
}
Originally I wanted to put the trace on my singleton class FirebaseUtility where the getCards method is.
func getCards(completion: #escaping (_ cards: [Card]?, _ errorMessage: String?) -> Void) {
// let testTrace = Performance.startTrace(name: "Test")
guard let userID = user?.uid else {
let error = "Unknown error occured! User is not logged in."
completion(nil, error)
return
}
let userCardRef = ref.child(FirebaseKeys.newCards).child(userID)
userCardRef.observe(.value, with: { (snapshot) in // changed from Single Event
let enumerator = snapshot.children
var cards = [Card]()
while let cardSnapshot = enumerator.nextObject() as? DataSnapshot {
if let cardDict = cardSnapshot.value as? [String : Any] {
let card = Card(id: cardSnapshot.key, cardDict: cardDict)
cards.append(card)
}
}
completion(cards, nil)
})
// testTrace?.stop()
}
however when I try to use it there I get an error saying Firebase Performance does not support Extensions at this time
are you using Firebase Performance in the context of an App Extension (e.g. Watch, keyboard, today, etc.)? That message is triggered by this line in the FirebasePerformance.h file:
NS_EXTENSION_UNAVAILABLE("FirebasePerformance does not support app extensions at this time.")
Firebase Performance currently only supports normal applications on iOS.
Related
while presenting the UICloudSharingController on top of a view, it presents the screen and when I select the messages option to send a message to a person whom I want to share with, it gives a spinning wheel with "uploading" message and vanishes - attached.
However when I go to cloudkit dashboard the root record has been shared. But I cannot share it with specific person. Is it because it has shared global? How can I fix it?
self.shareInfraRecord(zoneID: appDelegate.privateContactZoneID, completion: { (status) in
if ( status == false) {
return
}
})
func shareInfraRecord(zoneID: CKRecordZone.ID, completion: #escaping(Bool) -> Void) {
if let rootRecord = self.rootRecord {
if self.rootRecord?.share == nil {
let sharingController = UICloudSharingController { (controller, preparationHandler: #escaping (CKShare?, CKContainer?, Error?) -> Void) in
let shareID = CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID)
var share = CKShare(rootRecord: rootRecord, shareID: shareID)
share[CKShare.SystemFieldKey.title] = Cloud.ShareInfrastructure.ContactShareTitleKey as CKRecordValue?
share[CKShare.SystemFieldKey.shareType] = Cloud.ShareInfrastructure.ContactShareTypeKey as CKRecordValue?
let modifyRecZoneOp = CKModifyRecordsOperation(recordsToSave:[rootRecord, share], recordIDsToDelete: nil)
modifyRecZoneOp.modifyRecordsCompletionBlock = { (records, recordID, error) in
if error != nil {
if let ckerror = error as? CKError {
if let serverVersion = ckerror.serverRecord as? CKShare {
share = serverVersion
}
completion(false)
}
}
preparationHandler(share, self.defaultContainer, error)
}
self.privateDB?.add(modifyRecZoneOp)
}
sharingController.availablePermissions = [.allowReadOnly, .allowPrivate]
sharingController.delegate = self
sharingController.popoverPresentationController?.sourceView = self.view
self.present(sharingController, animated:true, completion:nil)
} else {
let shareRecordID = rootRecord.share!.recordID
let fetchRecordsOp = CKFetchRecordsOperation(recordIDs: [shareRecordID])
fetchRecordsOp.fetchRecordsCompletionBlock = { recordsByRecordID, error in
guard error == nil, let share = recordsByRecordID?[shareRecordID] as? CKShare else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
//self.saveToCloudKitStatus(recordName: myRecordName, success: false)
}
completion(false)
return
}
DispatchQueue.main.async {
let sharingController = UICloudSharingController(share: share, container: self.defaultContainer!)
completion(true)
//completionHandler(sharingController)
}
}
self.privateDB?.add(fetchRecordsOp)
}
}
}
This might be a bit late but I was running into this issue too, while using NSPersistentCloudKitContainer and it seems the issue was just making sure that my iCloud container name in the Capabilities section of the settings matched my app bundle name ie iCloud.com.goddamnyouryan.MyApp
I'm trying to allow a user to report a user (in a Tinder-like app). To report, this button takes the user to a new VC to elaborate the issue as an email.
What I'm missing:
How can I add the reporter's and reportee's Firebase unique ID to the email (or whatever form of communication)? (So then I can investigate and take action as needed)
Here's what I have:
The code to properly send an email...
func configureMailController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setToRecipients(["RadiusAppHelp#gmail.com"])
mailComposerVC.setSubject("Reporting user")
mailComposerVC.setMessageBody("Please include as much detail as possible:", isHTML: false)
return mailComposerVC
}
func showMailError() {
let sendMailErrorAlert = showAlert(withTitle: "Could not send message", message: "Please try again")
let dismiss = UIAlertAction(title: "Okay", style: .default, handler: nil)
// sendMailErrorAlert.addAction(dismiss)
// self.present(sendMailErrorAlert, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
The code to pull the other user's ID in the swipe view VC...
var otherUsersId = ""
var currentlyViewedUserId: String?
firebaseServer.fetchUsers {[weak self] (usersDict) in
self?.usersDict = usersDict
let fetchedUsers = Array(usersDict.values)
self?.filterBlockedUsers(from: fetchedUsers)
self?.loadFirstUser()
self?.cardView.reloadData()
}
func loadFirstUser() {
if users.count > 0 {
let imageView = UIImageView()
let storage = Storage.storage()
let storageRef = storage.reference(withPath:
"\(users[0].userId!)/photos/\(0)")
currentlyViewedUserId = users[0].userId
PhotoUploader.downloadImageUrl(from:
storageRef) { (url) in
guard let url = url else { return }
imageView.downloaded(from: url,
contentMode: .scaleAspectFill)
}
nameLbl.text = users[0].firstName
setupDetailsFor(user: users[0])
infoCollectionView.reloadData()
}
}
As well as the code to block a user (but blocking / reporting functions work independently).
Any help is greatly appreciated!
Okay, I found at least a temporary fix...
(Not using Firestore however, which I'll eventually need to implement — https://www.youtube.com/watch?v=Ofux_4c94FI)
In FirebaseFunctions.swift...
// Report Someone
func reportSomeone(with userId: String, completion:
#escaping (Error?) -> Void) {
let usersRef = db.child("users")
if let uid = Auth.auth().currentUser?.uid {
usersRef.child("\(uid)/report/\ .
(userId)").setValue(true) { (error, dbref) in
completion(error)
}
}
}
// Set Preferences for Reporting
func reportPreferences(with userId: String,
completion: #escaping (Error?) -> Void) {
let usersRef = db.child("users")
if let uid = Auth.auth().currentUser?.uid {
usersRef.child("\(uid)/preferences/\ .
(userId)").setValue(true) { (error, dbref) in
completion(error)
}
}
}
In User.swift...
var report: [String: Bool]? = [:]
func makeDictionary() -> [String: Any] {
print("")
return [
"report": report ?? [:]
]
static func makeObjectFrom(_ dictionary: [String:
Any]) -> LocalUser {
let report = dictionary["report"] as?
[String:Bool] ?? [:]
}
let localUser = LocalUser(report: report)
In the View Controller...
import Firebase
var currentUserId = Auth.auth().currentUser?.uid
var otherUsersId = ""
// Report Button
var isCurrentUserReported = false
var isOtherUserReported = false
var currentlyViewedUserId: String?
// FILTER REPORTED USERS
func filterReportUsers(from users: [LocalUser]) {
var notReportUsers = users
var notReportUsersDict = self.usersDict
var reportUsers = newUser.report ?? [:]
if let currentUserId =
Auth.auth().currentUser?.uid {
reportUsers[currentUserId] = true
}
for (userId, report) in reportUsers ?? [:] {
print("UserId...", userId)
print("UserHere...", usersDict[userId])
if let user = usersDict[userId] {
notReportUsersDict.removeValue(forKey:
userId)
}
}
let notReport = Array(notReportUsersDict.values)
self.users = notReport
}
This isn't perfect, but it works!
I have a function that connects to an API to retrieve data. The API takes two parameters accessCode (provided by user in a text box) and then UDID (UDID of their device). I can parse the data from within the function, but only locally. I need to store the values that are returned but am unsure on how to return them properly. Essentially I need this to return the json object as a dictionary (I think...) so it can be parsed outside of the async task. I've read through the swift documentation and that's where I found out how to do the requests, but I can't find a way to store the returned values in memory for access outside of the function.
func getResponse(accessCode:String, UDID:String, _ completion: #escaping (NSDictionary) -> ()) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult as? NSDictionary
print(results)
completion(results!)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
First of all don't use NSDictionary in Swift, use native [String:Any] and declare the type as optional to return nil if an error occurs.
And never use .mutableContainers in Swift, the option is useless.
func getResponse(accessCode:String, UDID:String, completion: #escaping ([String:Any]?) -> Void)) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error else {
print(error)
completion(nil)
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
print(jsonResult)
completion(jsonResult)
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
task.resume()
}
Your mistake is that you don't consider the closure, you have to execute the entire code inside the completion handler
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { [weak self] result in
if let accessCodeFound = result?["Found"] as? Bool {
print("Value of Found during function:")
//If access code is valid, go to License view
print(accessCodeFound)
if accessCodeFound {
//Load License View
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self?.show(licenseController, sender: self)
}
}
}
}
}
Your completion closure should handle the obtained data. You would call the function like this:
getResponse(accessCode: "code", UDID: "udid", completion: { result in
// Do whatever you need to do with the dictionary result
}
Also, I'd recommend you to change your NSDictionary with a swift Dictionary.
This is what the API returns as a response
{
AccessCode = 00000000;
Client = "0000 - My Company Name";
EmailAddress = "brandon#brandonthomas.me";
FirstName = Brandon;
Found = 1;
LastName = Thomas;
Status = A;
UDIDregistered = 1;
}
And this is what calls the function. I am calling at after clicking a button after an access code is being entered in a text field.
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
var accessCodeFound: Bool? = nil
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { result in
accessCodeFound = result["Found"] as! Bool
print("Value of Found during function:")
print(accessCodeFound)
//accessCodeFound = true
}
//If access code is valid, go to License view
print("Value of Found after function:")
print(accessCodeFound)
//accessCodeFound = nil ???
//it seems the value is getting reset after the function completes
if accessCodeFound == true{
//Load License View
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self.show(licenseController, sender: Any?.self)
}
}
I know this question has been asked before but none of the answers in the other questions worked for me. Here is my code:
```
var values = [String: AnyObject]()
func loginUserToFirebase(_ completion: () -> Void) {
let accessToken = FBSDKAccessToken.current()
guard let accessTokenString = accessToken?.tokenString else {fatalError()}
let credentials = FIRFacebookAuthProvider.credential(withAccessToken: accessTokenString)
FIRAuth.auth()?.signIn(with: credentials, completion: { (user, error) in
if error != nil {
print(error ?? "Something went wrong")
return
}
self.fbGraphRequest()
})
}
internal func fbGraphRequest(){
FBSDKGraphRequest(graphPath: "/me", parameters: ["fields": "id, name, email"]).start { (connection, result, error) in
if error != nil {
print(error ?? "error unknown")
return
} else {
print(result ?? "no result")
self.values = result as! [String: AnyObject]
print(self.values)
weak var rootViewModel = RootViewModel()
rootViewModel?.values = self.values
self.presentRootViewController()
}
}
}
internal func presentRootViewController() {
let loginController = LoginController()
let rootViewController = RootViewController()
loginController.present(rootViewController, animated: true,
completion: nil)
}
```
and here is my error:
Attempt to present <Art_Cache.RootViewController: 0x7fa6a1c2aab0> on <Art_Cache.LoginController: 0x7fa6a1c840b0> whose view is not in the window hierarchy!
This snippet worked when I had this in my LoginViewController and I used self.present(rootViewController, animation: true, completion: nil). Im trying to convert my project to MVVM and this is what's happening. The problem seems to be around the self.presentRootViewController(). These functions are fired upon pressing the facebook login button. Please help and cheers!
This happens when you current view controller's is not the part of window, could you change
func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(top)
} else if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
topViewController()?.present(rootViewController, animated: true,
completion: nil)
I've been struggling with this issue for the better part of five days now and am unable to come up with a solution. Im hoping you all will be able to help me out. :) I am currently in my 10th week of a coding bootcamp so please excuse any mistakes I make in my terminology. Thank you.
So... Here is my problem. A user creates an event and that event send a notification to all the user of the app. ( V.2. will use location to only send it to those near you ) Once you receive that push notification i want the app to open a specific view and populate the fields with the information from the User that created the event. let me show you my code.
// Youre CRUD Methods go here.
func createEvent(eventSummary: String, eventLongtitude: Float, eventLatitude: Float, eventCreationDate: NSDate, completion: (() -> Void)?) {
guard let currentUser = UserController.sharedInstance.currentUser else { return }
let event = Event(creator: currentUser, eventCreationDate: eventCreationDate, eventLatitude: eventLatitude, eventLongtitude: eventLongtitude, eventSummary: eventSummary)
saveContext()
if let completion = completion {
completion()
}
if let eventRecord = event.cloudKitRecord {
cloudKitManager.saveRecord(eventRecord, completion: { (record, error) in
if let record = record {
event.update(record)
}
})
}
}
func retrieveEventForRecordID(recordID: CKRecordID, completion: (Event) -> Void) {
self.cloudKitManager.fetchRecordWithID(recordID) { (record, error) in
if let error = error {
NSLog("Error fetching event for record ID \(recordID): \(error)")
return
}
guard let record = record else { return }
if let event = Event(record: record) {
completion(event)
}
}
}
This shows my createEvent method that should take in the user that created the event.
func userWithName(userRecordID: CKRecordID, completion: ((user: User?) -> Void)?) {
cloudKitManager.fetchRecordWithID(userRecordID) { (record, error) in
if let record = record {
if let user = User(record: record) {
if let completion = completion {
completion(user: user)
}
}
} else {
if let completion = completion {
completion(user: nil)
}
}
}
}
And this is whats in my App delegate.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
guard let remoteNotificationDictionary = userInfo as? [String: NSObject] else { return }
let cloudKitNotification = CKQueryNotification(fromRemoteNotificationDictionary: remoteNotificationDictionary)
guard let recordID = cloudKitNotification.recordID else { return }
let eventController = EventController.sharedInstance
eventController.retrieveEventForRecordID(recordID) { (event) in
// do whatever with this event
dispatch_async(dispatch_get_main_queue()) {
// show table view
let eventAlert = UILocalNotification()
eventAlert.alertTitle = "Place holder for title"
eventAlert.alertBody = "Place for body" // This is the local. This is what the user will see.
application.presentLocalNotificationNow(eventAlert)
print("Alert has been sent... maybe... prolly not." )
// TODO: alert to show users that there was an event.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("EventNotifVC") as? EventNotificationTableViewController else { return }
destinationViewController.modalPresentationStyle = .FullScreen
destinationViewController.event = event
if let rootVC = self.window?.rootViewController {
rootVC.presentViewController(destinationViewController, animated: true, completion: nil)
}
}
}
}
I believe that with those code snippets it should show my line of thinking. Create and event with a user. -> make it the creator -> and with that creator populate fields... Im so lost with this... Any help would be appreciated.
Also, this is how I'm setting the label..
func updateViews() {
guard let event = event,
creator = event.creator else {
// Update views to be blank?
return
}
userEventSummaryTextView.text = event.eventSummary
usernameLabel.text = creator.username
userPhoneNumber.text = creator.phoneNumber
}
override func viewDidLoad() {
super.viewDidLoad()
updateViews()
}