Answer is below, image is here:
I was searching how to do this for a couple of days and was only able to find people who stored UILocalNotificaations in NSUserDefaults. Saving these in NSUserDefaults seemed wrong to me because it is supposed to be used for small flags. I just now finally figured out how to store notifications in CoreData. This is Using Xcode 7.3.1 and Swift 2.2
First off you need to create a new entity in your CoreDataModel
and then add a single attribute to it. the attribute should be of type Binary Data I named my table/entity "ManagedFiredNotifications" and my attribute "notification". it should look like this:
Image linked in Question above.
Next you need to add an extension to UILocalNotification it should go like this:
extension UILocalNotification {
func save() -> Bool {
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let firedNotificationEntity = NSEntityDescription.insertNewObjectForEntityForName("ManagedFiredNotifications", inManagedObjectContext: appDelegate!.managedObjectContext)
guard appDelegate != nil else {
return false
}
let data = NSKeyedArchiver.archivedDataWithRootObject(self)
firedNotificationEntity.setValue(data, forKey: "notification")
do {
try appDelegate!.managedObjectContext.save()
return true
} catch {
return false
}
}
}
Now for saving a notification all you need to do is call
UILocalNotification.save()
On the notification you would like to save. my notifications were named 'notification' so I would call notification.save()
To retrieve a notification you need a method like this
func getLocalFiredNotifications() -> [UILocalNotification]? {
let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)!.managedObjectContext
let firedNotificationFetchRequest = NSFetchRequest(entityName: "ManagedFiredNotifications")
firedNotificationFetchRequest.includesPendingChanges = false
do {
let fetchedFiredNotifications = try managedObjectContext.executeFetchRequest(firedNotificationFetchRequest)
guard fetchedFiredNotifications.count > 0 else {
return nil
}
var firedNotificationsToReturn = [UILocalNotification]()
for managedFiredNotification in fetchedFiredNotifications {
let notificationData = managedFiredNotification.valueForKey("notification") as! NSData
let notificationToAdd = NSKeyedUnarchiver.unarchiveObjectWithData(notificationData) as! UILocalNotification
firedNotificationsToReturn.append(notificationToAdd)
}
return firedNotificationsToReturn
} catch {
return nil
}
}
Note that this returns an array of UILocalNotifications.
When retrieving these if you plan on removing a few of them and then storing the list again you should remove them when you get them something like this works:
func loadFiredNotifications() {
let notifications = StudyHelper().getLocalFiredNotifications()
if notifications != nil {
firedNotifications = notifications!
} else {
// throw an error or log it
}
classThatRemoveMethodIsIn().removeFiredLocalNotifications()
}
I hope this helps someone who had the same problems that I did trying to implement this.
Related
I'm calling a Firestore query that does come back, but I need to ensure completion before moving on with the rest of the code. So I need a completion handler...but for the life of me I can't seem to code it.
// get user info from db
func getUser() async {
self.db.collection("userSetting").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let userTrust = document.data()["userTrust"] as! String
let userGrade = document.data()["userGrade"] as! String
let userDisclaimer = document.data()["userDisclaimer"] as! String
var row = [String]()
row.append(userTrust)
row.append(userGrade)
row.append(userDisclaimer)
self.userArray.append(row)
// set google firebase analytics user info
self.userTrustInfo = userTrust
self.userGradeInfo = userGrade
}
}
}
}
Called by:
internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
db = Firestore.firestore()
Database.database().isPersistenceEnabled = true
Task {
do {
let userInfo = await getUser()
}
} return true }
I used a Task as didFinishLauncingWithOptions is synchronous and not asynchronous
However, the getUser() still isn't completed before didFinishLauncingWithOptions moves on.
I need the data from getUser as the very next step uses the data in the array, and without it I get an 'out of bounds exception' as the array is still empty.
Also tried using dispatch group within the func getUser(). Again with no joy.
Finally tried a completion handler:
func getUser(completion: #escaping (Bool) -> Void) {
self.db.collection("userSetting").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let userTrust = document.data()["userTrust"] as! String
let userGrade = document.data()["userGrade"] as! String
let userDisclaimer = document.data()["userDisclaimer"] as! String
var row = [String]()
row.append(userTrust)
row.append(userGrade)
row.append(userDisclaimer)
self.userArray.append(row)
// set google firebase analytics user info
self.userTrustInfo = userTrust
self.userGradeInfo = userGrade
completion(true)
}
}
}
}
Nothing works. The getUser call isn't completed before the code moves on. Can someone please help. I've searched multiple times, looked at all linked answers but I can not make this work.I'm clearly missing something easy, please help
read this post: Waiting for data to be loaded on app startup.
It explains why you should never wait for data before returning from
function application(_:didFinishLaunchingWithOptions).
To achieve what you need, you could use your first ViewController as a sort of splashscreen (that only shows an image or an activity indicator) and call the function getUser(completion:) in the viewDidLoad() method the ViewController.
Example:
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
MyFirestoreDatabaseManager.shared.getUser() { success in
if success {
//TODO: Navigate to another ViewController
} else {
//TODO: Show an error
}
}
}
}
Where obviously MyFirestoreDatabaseManager.shared is the object on which you defined the getUser(completion:) method.
(In your example, I think that you defined that function in the AppDelegate. In that case, you should mark your getUser(completion:) method and all related variables as static. Then replace MyFirestoreDatabaseManager.shared with AppDelegate).
Not 100% sure what you would like to accomplish as I can't see all your code, but try something similar to this, replacing Objects for what you are trying to return from the documents.
You don't want your user's data spread across multiple documents. With Firebase you pay for every document you have to get. Ideally you want all your user's settings within one firebase document. Then create a UserInfo struct that you can decode to using the library CodeableFirebase or the decoder of your choice.
// Create user struct
struct UserInfo: Codable {
var userId: String
var userTrust: String
var userGrade: String
var userDisclaimer: String
}
// get user info from db and decode using CodableFirebase
func getUser() async throws -> UserInfo {
let doc = try await self.db.collection("users").document("userIdHere")
let userInfo = try FirestoreDecoder().decode(UserInfo.self, from: doc.data())
return UserInfo
}
Then you can do this...
Task {
do {
let userInfo = try await getUser()
let userTrust = userInfo.userTrust
let userGrade = userInfo.userGrade
let userDisclaimer = userInfo.userDisclaimer
}
}
I am trying to make a document based app in swiftUI with a custom UI. I want iCloud capabilities in my app. I am trying to use iCloud Document (No cloudKit) way for storing data on iCloud container. I am using UIDocument and it's working. It's storing data to iCloud and I am able to retrieve it back.
Now the thing is when I run the app on two devices (iphone and iPad) and make changes to a file on one device, the changes are not reflecting on the other device while the file or say app is open. I have to close the app and relaunch it to see the changes.
I know I have to implement NSMetadataQuery to achieve this but I am struggling with it. I don't know any objective-C. I have been searching on the internet for a good article but could not find any. Can you please tell how do I implement this feature in my app. I have attach the working code of UIDocument and my Model class.
Thank you in advance !
UIDocument
class NoteDocument: UIDocument {
var notes = [Note]()
override func load(fromContents contents: Any, ofType typeName: String?) throws {
if let contents = contents as? Data {
if let arr = try? PropertyListDecoder().decode([Note].self, from: contents) {
self.notes = arr
return
}
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -1, userInfo: nil)
}
override func contents(forType typeName: String) throws -> Any {
if let data = try? PropertyListEncoder().encode(self.notes) {
return data
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -2, userInfo: nil)
}
}
Model
class Model: ObservableObject {
var document: NoteDocument?
var documentURL: URL?
init() {
let fm = FileManager.default
let driveURL = fm.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
documentURL = driveURL?.appendingPathComponent("savefile.txt")
document = NoteDocument(fileURL: documentURL!)
}
func loadData(viewModel: ViewModel) {
let fm = FileManager.default
if fm.fileExists(atPath: (documentURL?.path)!) {
document?.open(completionHandler: { (success: Bool) -> Void in
if success {
viewModel.notes = self.document?.notes ?? [Note]()
print("File load successfull")
} else {
print("File load failed")
}
})
} else {
document?.save(to: documentURL!, for: .forCreating, completionHandler: { (success: Bool) -> Void in
if success {
print("File create successfull")
} else {
print("File create failed")
}
})
}
}
func saveData(_ notes: [Note]) {
document!.notes = notes
document?.save(to: documentURL!, for: .forOverwriting, completionHandler: { (success: Bool) -> Void in
if success {
print("File save successfull")
} else {
print("File save failed")
}
})
}
func autoSave(_ notes: [Note]) {
document!.notes = notes
document?.updateChangeCount(.done)
}
}
Note
class Note: Identifiable, Codable {
var id = UUID()
var title = ""
var text = ""
}
This is a complex topic. Apple do provide some sample swift code, the Document-Based App Programming Guide for iOS and iCloud Design Guide.
There is also some good third party guidance: Mastering the iCloud Document Store.
I would recommend reading the above, and then return to the NSMetaDataQuery API. NSMetaDataQuery has an initial gathering phase and a live-update phase. The later phase can remain in operation for the lifetime of your app, allowing you to be notified of new documents in your app's iCloud container.
I am coding since January 2019 and this is my first post here.
I am using Swift and Firestore. In my App is a tableView where I display events loaded out of a single Document with an array of events inside as [String: [String:Any]]. If the user wants to get more infos about an event he taps on it. In the background the TableViewController will open a new "DetailEventViewController" with a segue and give it the value of the eventID in the tapped cell.
When the user is on the DetailViewController Screen the app will download a new Document with the EventID as key for the document.
I wanna save this Data out of Firestore in a Struct called Event. For this example just with Event(eventName: String).
When I get all the data I can print it directly out but I can't save it in a variable and print it out later. I really don't know why. If I print the struct INSIDE the brackets where I get the data its working but if I save it into a variable and try to use this variable it says its nil.
So how can I fetch data out of Firestore and save in just a Single ValueObject (var currentEvent = Event? -> currentEvent = Event.event(for: data as [String:Any]) )
I search in google, firebaseDoc and stackoverflow but didn't find anything about it so I tried to save all the singe infos of the data inside a singe value.
// Struct
struct Event {
var eventName: String!
static func event(for eventData: [String:Any]) -> Event? {
guard let _eventName = eventData["eventName"] as? String
else {
print("error")
return nil
}
return Event(eventName: _eventName)
}
// TableView VC this should work
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEventDetailSegue" {
if let ShowEvent = segue.destination as? DetailEventViewController, let event = eventForSegue{
ShowEvent.currentEventId = event.eventID
}
}
}
// DetailViewController
var currentEvent = Event()
var currentEventId: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let _eventID = currentEventId else {
print("error in EventID")
return}
setupEvent(eventID: _eventID) /* currentEvent should be set here */
setupView(event: currentEvent) /* currentEvent has after "setupEvent" the value of nil */
}
func setupEvent(eventID: String) {
let FirestoreRef = Firestore.firestore().collection("events").document(eventID)
FirestoreRef.getDocument { (document, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
SVProgressHUD.showError(withStatus: "Error in Download")
}else {
if let document = document, document.exists {
guard let data = document.data() else {return}
let eventData = Event.event(for: data as [String:Any])
print(eventData)
//here all infos are printed out - so I get them
self.currentEvent = eventData!
//Here is the error.. I can't save the fetched Data in my single current Event
} else {
SVProgressHUD.showError(withStatus: "Error")
}
}
}
}
func setupView(event: Event) {
self.titleLabel.text = event.eventName
}
I expect that the function setupEvents will give the currentEvent in the DetailViewController a SINGLEvalue cause its a SINGLE document not an array. So I can use this single Eventvalue for further actions. Like starting a new segue for a new ViewController and just push the Event there not
In my application, I have set up a Core Data entity called "Finances". For now it has 2 attributes: money and net-worth.
I wish to write a function that each time it is called, it deletes all results for a specific entity. An example might be:
func resetAttribute(attribute: String) {
}
PS: I have found on the internet a function which was engineered to only delete a specific element of an attribute, which matched to a string. I have modified the code in the following way:
func resetTest() {
if let dataAppDelegatde = UIApplication.shared.delegate as? AppDelegate {
let mngdCntxt = dataAppDelegatde.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ApplicationFinances")
let predicate = NSPredicate(format: "money != %#", "vdavduabsdpansuryiubj")
fetchRequest.predicate = predicate
do {
let result = try mngdCntxt.fetch(fetchRequest)
print(result.count)
if result.count > 0 {
for object in result {
print(object)
mngdCntxt.delete(object as! NSManagedObject)
}
}
} catch{
}
}
}
Meaning that if money wouldn't have been equals to vdavduabsdpansuryiubj (Meaning never of course) it would have deleted the other values. But this didn't seem to work.
What happens when you run the function? Does it throw an error? Or does it print just fine and runs fine but nothing actually happens when you reload the core data? If this is the case, try adding:
do {
try mngdCntxt.save()
} catch {}
Right after the for loop is over
I have two classes Place and BeenHere. BeenHere has pointers called "toPlace" and "fromUser"with target to Class Place and User accordingly. Place, in its turn, has title and image (PFFile) that I want to retrieve and show in ViewController. In the code below I have reached that pointer with objectId, but don't know how I can now retrieve title and image related to specific place this pointer leads to. Appreciate your help and suggestions.
class UserBeenHereViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let user = PFUser.currentUser()?.username
if user != nil {
let query = PFQuery(className: "BeenHere")
query.includeKey("toPlace")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
for object in objects! {
print(object["toPlace"].objectId)
}
}
else {
print("There is error")
}
}
}
}
First you need to get the "toPlace" into a PFObject and then access it. So in your case it should look something like:
var toPlace = comment["toPlace"] as? PFObject
print (toPlace["title"])