Push Notification in Swift Firebase - swift

I want to send push notifications to the user if he gets new chat messages or by some other actions I already implemented in a activity feed. All activity messages I receive, I want to duplicate to push notifications if the user is not inside the app.
I already implemented some general stuff in app delegate.swift:
import UserNotifications
import Firebase
import FirebaseInstanceID
import FirebaseMessaging
// The callback to handle data message received via FCM for devices running iOS 10 or above.
func applicationReceivedRemoteMessage(_ remoteMessage: MessagingRemoteMessage) {
print(remoteMessage.appData)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
// For iOS 10 data message (sent via FCM
Messaging.messaging().delegate = self
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
FirebaseApp.configure()
UITabBar.appearance().tintColor = UIColor.black
UIBarButtonItem.appearance().tintColor = .black
return true
}
This Is my activity feed where I collect all activitys in my network. All This messages I want to send the user as push notification if the user is not inside the app:
func updateView(report: ReportingModel) {
if report.type == "post" {
statusLabel.text = "hat einen neuen Post erstellt"
createTime(report: report)
guard let postId = report.objectId else { return }
PostApi.shared.observePost(withPostId: postId, completion: { (post) in
guard let postImageUrlSting = post.imageURL else { return }
guard let imageUrl = URL(string: postImageUrlSting) else { return }
self.postImageView.sd_setImage(with: imageUrl, completed: { (_, _, _, _) in
})
})
} else if report.type == "comment" {
statusLabel.text = "hat einen neuen Kommentar erstellt"
createTime(report: report)
guard let postId = report.objectId else { return }
PostApi.shared.observePost(withPostId: postId, completion: { (post) in
guard let postImageUrlSting = post.imageURL else { return }
guard let imageUrl = URL(string: postImageUrlSting) else { return }
self.postImageView.sd_setImage(with: imageUrl, completed: { (_, _, _, _) in
})
})
} else if report.type == "like" {
statusLabel.text = "hat deinen Beitrag geliked"
createTime(report: report)
guard let postId = report.objectId else { return }
PostApi.shared.observePost(withPostId: postId, completion: { (post) in
guard let postImageUrlSting = post.imageURL else { return }
guard let imageUrl = URL(string: postImageUrlSting) else { return }
self.postImageView.sd_setImage(with: imageUrl, completed: { (_, _, _, _) in
})
})
} else if report.type == "dislike" {
statusLabel.text = "hat deinen Beitrag gedisliked"
createTime(report: report)
guard let postId = report.objectId else { return }
PostApi.shared.observePost(withPostId: postId, completion: { (post) in
guard let postImageUrlSting = post.imageURL else { return }
guard let imageUrl = URL(string: postImageUrlSting) else { return }
self.postImageView.sd_setImage(with: imageUrl, completed: { (_, _, _, _) in
})
})
}
}
But how to connect the activity feed to my push notifications?
I now receive some manually send push notifications through cloud messaging.
Thanks in advance for your help!

Related

Completion not working with Firebase Database

The user can upload a profile picture and some information about himself in my app.
I want to write the url of the uploaded picture in firebase realtime database but it takes the placeholder text "testentry" and not the real url. Why does my completion not work here?
var imagePicker: UIImagePickerController!
var urltoPicture = "testentry"
#IBAction func updateProfile(_ sender: UIButton) {
uploadPic(arg: true, completion: { (success) -> Void in
if success {
linkUbertragen()
} else {
}
})
func uploadPic(arg: Bool, completion: #escaping (Bool) -> ()) {
guard let imageSelected = self.image else {
//print("ok")
return
}
guard let imageData = imageSelected.jpegData(compressionQuality: 0.1) else {
return
}
let storageRef = Storage.storage().reference(forURL: "gs://h......com")
let storageProfileRef = storageRef.child("profilePictures").child(Auth.auth().currentUser!.uid)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
storageProfileRef.putData(imageData, metadata: metadata, completion: {
(storageMetadata, error) in
if error != nil {
//print(error?.localizedDescription)
return
}
storageProfileRef.downloadURL(completion: { (url, error) in
if let metaImageURL = url?.absoluteString {
print(metaImageURL)
self.urltoPicture = metaImageURL
}
})
})
completion(arg)
}
func linkUbertragen(){
ref = Database.database().reference()
let userID = Auth.auth().currentUser!.uid
ref.child("user/\(userID)").updateChildValues(["profileText": profileText.text!])
print(urltoPicture)
ref.child("user/\(userID)").updateChildValues(["picture": urltoPicture])
}
self.navigationController?.popViewController(animated: true)
}
This is a very common mistake. You have to call completion inside the (final) closure.
And it is good practice to call completion(false) always in case of an error – even better to return and handle all errors
func uploadPic(arg: Bool, completion: #escaping (Bool) -> ()) {
guard let imageSelected = self.image else {
//print("ok")
completion(false); return
}
guard let imageData = imageSelected.jpegData(compressionQuality: 0.1) else {
completion(false); return
}
let storageRef = Storage.storage().reference(forURL: "gs://h......com")
let storageProfileRef = storageRef.child("profilePictures").child(Auth.auth().currentUser!.uid)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
storageProfileRef.putData(imageData, metadata: metadata, completion: {
(storageMetadata, error) in
if error != nil {
//print(error?.localizedDescription)
completion(false); return
}
storageProfileRef.downloadURL(completion: { (url, error) in
if let metaImageURL = url?.absoluteString {
print(metaImageURL)
self.urltoPicture = metaImageURL
completion(true)
} else {
completion(false)
}
})
})
}
The arg parameter is actually not needed.

Cannot share with UICloudSharingController; vanishes with "uploading" message

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

"Flag" / Report user functionality to work — Swift / Xcode / Firebase

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!

Perform segue after notification access has been granted

I would like to know how to perform a modal segue after the remote notification access has been granted from the dialog box. I have set up my remote notification in the app delegate.
func registerANSForApplication(_ application: UIApplication,withBlock block: #escaping (_ granted:Bool) -> (Void)){
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print("Error fetching remote instange ID: \(error)")
} else if let result = result {
print("Remote instance ID token: \(result.token)")
AppDelegate.isToken = result.token
}
}
let current = UNUserNotificationCenter.current()
let options : UNAuthorizationOptions = [.sound, .badge, .alert]
current.requestAuthorization(options: options) { (granted, error) in
guard granted else{
return
}
if error != nil{
print(error?.localizedDescription ?? "")
}else{
Messaging.messaging().delegate = self
current.delegate = self
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}
}
}
Then, in my view controller, I have this code:
let appDelegate = UIApplication.shared.delegate as!
appDelegate.registerANSForApplication(UIApplication.shared) { (granted) -> (Void) in
self.performSegue(withIdentifier: "MembershipVC", sender: nil)
}
The problem is whether the user allows or denies the access to notification, the segue is not executed.
Thank you for your help.
You have to call the block parameter
Replace
current.requestAuthorization(options: options) { (granted, error) in
guard granted else{
return
}
if error != nil{
print(error?.localizedDescription ?? "")
}else{
Messaging.messaging().delegate = self
current.delegate = self
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}
}
with
current.requestAuthorization(options: options) { (granted, error) in
if error != nil {
print(error?.localizedDescription ?? "")
block(false)
} else {
Messaging.messaging().delegate = self
current.delegate = self
DispatchQueue.main.async {
application.registerForRemoteNotifications()
block(granted)
}
}
}

Why does the function return the result ahead of time?

In appDelegate I will check whether the user is authorized through the userAuthorizedCheck() function. Depending on the result, redirect it to one or another stroyBoard. userAuthorizedCheck() should return the result only after the server has answered. The problem is that if i leave the last completion(false) in userAuthorizedCheck(), then it returns false first, and then it is checked. Even if the check was successful, then all the same, completion(false) is sent first and, as a result, the redirect is sent to the Authorization storyboard.
But if I remove the last completion(false), then I get Thread 1: signal SIGABRT, opposite func application (). print (tempToken) is triggered after checking userAuthorizedCheck (). If i put a breakpoint, i can see that in the userAuthorizedCheck () function, the last completion (false) works first.
AppDelegate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
var storyboardName: String = ""
userAuthorizedCheck(after: { (succesful) in
if (succesful == true){
storyboardName = "Main"
}else{
print(false)
storyboardName = "Authorization"
}
})
let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main)
window = UIWindow(frame: UIScreen.main.bounds)
window!.makeKeyAndVisible()
window!.rootViewController = storyboard.instantiateInitialViewController()
return true
}
func userAuthorizedCheck(after completion: #escaping (Bool) -> Void) {
let username : String = UserDefaults.standard.string(forKey: "username") ?? ""
let password : String = UserDefaults.standard.string(forKey: "password") ?? ""
let tokenSaved : String = UserDefaults.standard.string(forKey: "token") ?? ""
var tempToken:String = ""
//
if(!username.isEmpty && !password.isEmpty)
{
let json: [String: String] = ["username": username, "password": password]
login(json: json, after: {(status, token, code) in
if(code == 200 && !token.isEmpty){
UserDefaults.standard.setValue(token, forKey: "token");
UserDefaults.standard.synchronize();
tempToken = token
print(tempToken)
completion(true)
}
else{
tempToken = ""
completion(false)
}
})
}
else
{
completion(false)
}
completion(false)//The problem in this line, as I repent
}
login(in another swift file):
func login(json:Any, after completion: #escaping (Bool, _ token: String, _ code:Int) -> Void){
guard let url = URL(string: ngrok+"/api/auth/token/create")else{return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField:"Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject: json, options: [])else {return}
request.httpBody = httpBody
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 5.0
sessionConfig.timeoutIntervalForResource = 60.0
URLSession(configuration: sessionConfig).dataTask(with: request){(data, response, error) in
if error != nil{
print("server error")
completion(true, "", 0)
}
else if let response = response{
// print(response)
if let httpResponse = response as? HTTPURLResponse{
guard let data = data else{return}
do{
// print(data)
if(httpResponse.statusCode == 200){
if let json_response = try JSONSerialization.jsonObject(with: data, options: [])as? [String:Any]{
if let token = json_response["auth_token"]{
print(token as! String)
completion(true, "token",httpResponse.statusCode)
}
}
}
else if(httpResponse.statusCode == 400)
{
completion(true, "",httpResponse.statusCode)
print("The username or password you entered is incorrect")
}
else{
print("Unknown error")
completion(true, "", 0)
}
}
catch{
print("errasd")
print(error)
completion(true, "", 0)
}
}
}
}.resume()
}
I want the user Authorized Check () function to send the result only after the server has responded.
You cannot wait in didFinishLaunchingWithOptions for something asynchronous before returning true or false.
One option is to return true and load the storyboard after the response of the server
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
userAuthorizedCheck(after: { (succesful) in
let storyboardName = succesful ? "Main" : "Authorization"
let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main)
self.window = UIWindow(frame: UIScreen.main.bounds)
window!.makeKeyAndVisible()
window!.rootViewController = storyboard.instantiateInitialViewController()
})
return true
}
And delete this line in userAuthorizedCheck
completion(false)//The problem in this line, as I repent
because it completes immediately which is not intended.