I use the following function to retrieve my calendar:
func retrieveCalendar() -> EKCalendar? {
appDelegate = UIApplication.sharedApplication().delegate
as? AppDelegate
var myCalendar: EKCalendar?
let calendars = appDelegate!.eventStore!.calendarsForEntityType(EKEntityTypeReminder) as! [EKCalendar]
let filteredCalendars = calendars.filter {$0.title == "MedicalCalendar"}
if filteredCalendars.isEmpty {
println("could not find reminder calendar 'MedicalCalendar'")
return nil
} else {
myCalendar = filteredCalendars[0]
return myCalendar!
}
}
However, anytime I add new events to the calendar I'd like to check if they already exist there. I figured out that the easiest approach would be to delete all reminders and load new ones again. I tried:
self.retrieveCalendar()?.reset()
But it does not work. How can I remove reminders from calendar?(either one at a time or all of them at once)
To check reminders you have to call the method fetchRemindersMatchingPredicate() in conjunction with predicateForRemindersInCalendars or predicateForIncompleteRemindersWithDueDateStarting:ending:calendars: or predicateForCompletedRemindersWithCompletionDateStarting:ending:calendars:
For example if you want to delete all expired reminders in the past until now, use something like this
assumed properties:
var calendar : EKCalendar // current calendar
let eventStore : EKEventStore // current event store
code
func removeExpiredReminders() {
let pastPredicate = eventStore.predicateForIncompleteRemindersWithDueDateStarting(nil, ending:NSDate(), calendars:[calendar])
eventStore.fetchRemindersMatchingPredicate(pastPredicate) { foundReminders in
let remindersToDelete = !foundReminders.isEmpty
for reminder in foundReminders as! [EKReminder] {
self.eventStore.removeReminder(reminder, commit: false, error: nil)
}
if remindersToDelete {
self.eventStore.commit(nil)
}
}
}
in the loop you can check for further conditions
Related
Our app requires that the user carries out a daily task. If he/she completes the task, then at midnight a new task is released. If they don't, then come midnight they will continue seeing the previous task until they do.
I'm saving the current date in User Defaults and I'm using isDateInToday to heck if the date has changed:
func checkIfDayChanged() -> Bool {
if let date = UserDefaults.standard.object(forKey: "currentDate") as? Date {
var calendar = Calendar.current
calendar.timeZone = TimeZone.current
let isSameDay = calendar.isDateInToday(date)
if (isSameDay) {
return false
}
}
return true
}
In the majority of cases this works fine. However, there is a small group of users that are stuck with the same task even though they completed it the previous day. For some reason the app is not recognizing that the day is a new one.
Could this be a setting on the phone? Or do I need to change this code? Why wouldn't this be working in only a few devices?
UPDATE:
This is what happens when the daily task (a lesson) is completed:
func onComplete() {
let latestLesson = UserDefaults.standard.integer(forKey: "currentLesson")
if String(latestLesson) == lesson?.lessonNumber {
if (UserDefaults.standard.bool(forKey: "dayLessonCompleted") == false) {
UserDefaults.standard.set(true, forKey: "dayLessonCompleted")
lesson?.completed = true
}
}
}
Every day, the following function runs:
private func checkForMoreLessons() {
// Check the date
let newDay = checkIfDayChanged()
// If the day changed, check if user has watched the previous lesson
if newDay {
if (UserDefaults.standard.bool(forKey: "dayLessonCompleted") == true) {
// Save the new day
UserDefaults.standard.set(Date(), forKey:"currentDate")
// Add 1 to the current date
let currentLesson = UserDefaults.standard.integer(forKey: "currentLesson")
UserDefaults.standard.set(currentLesson + 1, forKey: "currentLesson")
UserDefaults.standard.set(false, forKey: "dayLessonCompleted")
}
}
}
A new stored property created for exisiting class using extension. While getting value its always returning nil value. The below code is what i tried to add new stored property.
var IdentifiableIdKey = "kIdentifiableIdKey"
extension EKEvent {
public var customId: Int {
get {
return (objc_getAssociatedObject(self, &IdentifiableIdKey) as? Int) ?? 0
}
set {
print("\(newValue)")
objc_setAssociatedObject(self, &IdentifiableIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Utilisation
let eventStore = EKEventStore()
let calendars = eventStore.calendars(for: .event)
for calendar in calendars {
if calendar.title == "Events" {
let oneMonthAgo = NSDate(timeIntervalSinceNow: -30*24*3600)
let oneMonthAfter = NSDate(timeIntervalSinceNow: +30*24*3600)
let predicate = eventStore.predicateForEvents(withStart: oneMonthAgo as Date, end: oneMonthAfter as Date, calendars: [calendar])
let events = eventStore.events(matching: predicate)
for event in events {
print("evnet id \(event.customId)")
}
}
}
Somebody help me to find the mistake I did. Thanks in advance.
The fact is that objc_getAssociatedObject/objc_setAssociatedObject assign a value to an object instance so it can be alive with this instance only. Since your custom property is not serialised by EventKit it's naturally that new obtained instances of events after the request have no associated objects.
Extensions in Swift can add computed instance properties and computed type properties only https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
It's called objc_setAssociatedObject. See the "Object" at the end? An Int is not an object. Turn your newValue into an NSNumber so you have an object. So that is definitely not going to work.
If your class is not an #objc class that might be a problem.
I've created a function in swift that checks through all the Calendars currently saved on an iOS device, then creates a new one if a calendar with the specific title "TESTCAL" (in this case) doesn't exist.
func checkCal(){
print("checkCal")
let calendars = eventStore.calendars(for: EKEntityType.event) as [EKCalendar]
var exists = false
for calendar in calendars {
if calendar.title == "TESTCAL"{
exists = true
print("FOUND CAL: TESTCAL")
print(calendar.title)
print(calendar.calendarIdentifier)
self.calIdent = calendar.calendarIdentifier
} else{
print("NO CAL: ", calendar.title)
}
}
if exists == false {
let newCal = EKCalendar(for: EKEntityType.event, eventStore: eventStore)
newCal.title = "TESTCAL"
newCal.source = eventStore.defaultCalendarForNewEvents?.source
_ = try? eventStore.saveCalendar(newCal, commit: true)
print("CAL CREATED: ", newCal.title)
print("With Ident: ", newCal.calendarIdentifier)
self.calIdent = newCal.calendarIdentifier
}
addToCal()
}
Then when I come to add the event later, in the following way
func addToCal()
{
let eventVC = EKEventEditViewController()
eventVC.editViewDelegate = self
eventVC.eventStore = EKEventStore()
let date = self.passedDate
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yy"
let dateForCal = dateFormatter.date(from: date)
let event = EKEvent(eventStore: eventVC.eventStore)
event.title = self.passedTitle
event.notes = self.passedDetail
event.startDate = dateForCal
event.endDate = dateForCal
event.isAllDay = true
event.url = URL(string: passedWebLink) }
The event gets added to the iOS default calendar.
I've tried adding the following code to the addCal() function:
event.calendar = eventStore.calendar(withIdentifier: self.calIdent)
and
let calendars = eventStore.calendars(for: EKEntityType.event) as [EKCalendar]
for calendar in calendars {
if calendar.title == "TESTCAL"{
event.calendar = calendar
print("YES")
} else{
print("NO")
}
}
Both of these give me error message in the AppDelegate and cause a crash:
[EventKit] Error getting shared calendar invitaions for entity types 3 from daemon: Error Domain=EKCADErrorDomain Code 1014 "(null)
Any ideas?
I see more possible causes for the issue you are experiencing:
The TESTCAL calendar is not created successfully. You can validate the calendar creation by catching the possible error thrown on saving the calendar:
do {
try eventStore.saveCalendar(newCal, commit: true)
} catch {
print(error)
}
How the event saving is happening is not clear from the code you posted.
If you are presenting the EKEventEditViewController instance for the user to
complete the event creation, you are missing setting the event on the view
controller in the addToCal method:
eventVC.event = event
If you want to save the event directly, then you don't need to use
EKEventEditViewController, but instead call save on the eventStore:
do {
try eventStore.save(event, span: .thisEvent)
} catch {
print(error)
}
For shared calendars, an additional privacy key for contacts accessing is needed in the .plist file of your target, namely NSContactsUsageDescription. Without this key, accessing shared calendars causes a crash.
With everything from the above checked/taken care of, your solution of setting the calendar on the event should work:
event.calendar = eventStore.calendar(withIdentifier: self.calIdent)
Also, make sure to have in place the code for requesting event kit store access from the user before any calendar/event manipulation. For more info on requesting access, check https://developer.apple.com/documentation/eventkit/ekeventstore/1507547-requestaccess
I have added events to calendar using EventKit and I can fetch all events from calendar, but I need only my app's event from calendar. So now my question is, how can I fetch events which are related to my app.
func getEvents() -> [EKEvent] {
var allEvents: [EKEvent] = []
// calendars
let calendars = self.eventStore.calendars(for: .event)
// iterate over all selected calendars
for (_, calendar) in calendars.enumerated() where isCalendarSelected(calendar.calendarIdentifier) {
// predicate for today (start to end)
let predicate = self.eventStore.predicateForEvents(withStart: self.initialDates.first!, end: self.initialDates.last!, calendars: [calendar])
let matchingEvents = self.eventStore.events(matching: predicate)
// iterate through events
for event in matchingEvents {
allEvents.append(event)
}
}
return allEvents
}
this code will gvie you all events and then you have to filter it by title or something which can be identify that this event is from your app :)
I am trying to implement updating Firebase database once a user taps on a button. When the user logs in (with Facebook in my case), a data structure is created successfully with the initial values in the data tree created.
What I want to accomplish is once the user is in the app and they create a new item it saves it to the database, hence updates the values under one of the already created child values. See my code and screenshot for reference - thanks for any help!
// user taps button to send item to be updated in Firebase data tree
func confirmAddPlace() {
// add place to tableview array
let accessToken = FBSDKAccessToken.current()
guard let accessTokenString = accessToken?.tokenString else { return }
let credentials = FIRFacebookAuthProvider.credential(withAccessToken: accessTokenString)
FIRAuth.auth()?.signIn(with: credentials, completion: { (user, error) in
if error != nil {
print("Something went wrong with our FB user: ", error ?? "")
return
}
guard let uid = user?.uid else {
return
}
// here is where i am having issues
let ref = FIRDatabase.database().reference().root.child("Users").child(uid).child("Places")
let values = ["place": self.placeNameLabel.text]
ref.updateChildValues(values)
})
animateOut()
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
let placeID = place.placeID
placesClient.lookUpPlaceID(placeID, callback: { (place, error) -> Void in
if let error = error {
print("lookup place id query error: \(error.localizedDescription)")
return
}
guard let place = place else {
return
}
})
let selectedPlace = place.formattedAddress
if let name = selectedPlace as String!
{
self.placeNameLabel.text = "Are you sure you would like to add \(name) to your places?"
}
}
You want to change the value of Places, which is the value in the child of child(uid).
let values = ["place": self.placeNameLabel.text]
let ref = FIRDatabase.database().reference().root.child("users").child(uid).updateChildValues(["Places": values])
user3708224 - You could try this:
let values = ["place": self.placeNameLabel.text]
// create a child reference that uses a date as the key
let date = Date()
let ref = FIRDatabase.database().reference().root.child("users").child(uid).child(date).updateChildValues(["Places": values])
If you want more control of what components are in the date object try this:
let calendar = Calendar.current
let year = calendar.component(.year, from: date)
// you can do the same with [.month, .day, .hour, .minute, and more]
// This will allow you to have control of how frequently they can update the DB
// And it will allow you to sort by date
If you want to upload it to Firebase as a String try this:
/**
This function returns the Date as a String
- "Year-Month-Day"
If the character ' / ' is used in place of ' - ' Firebase will make each component child of the previous component.
*/
func getDate() -> String {
let date = Date()
let calendar = Calendar.current
// hours + min: -\(calendar.component(.hour, from: date))-\(calendar.component(.minute, from: date))
return "\(calendar.component(.year, from: date))-\(calendar.component(.month, from: date))-\(calendar.component(.day, from: date))"
}
let values = ["place": self.placeNameLabel.text]
let ref = FIRDatabase.database().reference().root.child("users").child(uid).child(getDate()).updateChildValues(["Places": values])