Event Kit - edit repeating event - swift

func editFutureEventToCalendar(id:String,title: String, description: String?,alarams :[EKAlarm],location:String,contacts:[GenParticipants],repeatString :String,timeZone:NSTimeZone, startDate: NSDate, endDate: NSDate , isAllDay :Bool,calendar:EKCalendar,StruLocation:EKStructuredLocation?)->Bool {
let eventStore = EventsManager.getEventStore()
let event = EventsManager.geteventID(id: id)
let ocdate = event.occurrenceDate
event.title = title
event.startDate = startDate as Date
event.endDate = endDate as Date
event.notes = description
event.calendar = calendar
// event.occurrenceDate = ocdate
event.timeZone = timeZone as TimeZone //timezoneNSTimeZone() as TimeZone //timeZone as TimeZone
event.isAllDay = isAllDay
if StruLocation != nil {
event.structuredLocation = StruLocation
}else {
event.structuredLocation = StruLocation
}
if repeatString != "None" {
if event.hasRecurrenceRules {
let rrrl = event.recurrenceRules
for i in rrrl! {
event.removeRecurrenceRule(i)
}
}
let rule = EventsManager.getRepeatValue(repeatString)//self.getRepeatValue(option: repeat)
event.addRecurrenceRule(rule!)
}else if repeatString == "None"{
if event.hasRecurrenceRules {
let rrrl = event.recurrenceRules
for i in rrrl! {
event.removeRecurrenceRule(i)
}
}
}
for item in alarams {
event.addAlarm(item)
}
// event.location = location
// let formatter = CNContactFormatter()
// formatter.style =
var attendees = [EKParticipant]()
// for item in contacts {
//
// let con = event.(contact: item)
// attendees.append(con!)
//
// }
do {
try eventStore.save(event, span: .futureEvents, commit: true)
//try eventStore.save(event, span: .thisEvent)
return true
} catch let e as NSError {
EZAlertController.alert(e.debugDescription)
}
return false
}
I have this function for editing all the future events in repeat series - if i edit the event in between the series of events its deleting all the previous events and editing future events .
i want it to edit all the events in series .
I am using eventKit not eventKitUI . i dont want eventkitUI.
How to edit this function to do the same or do i have to fetch the event differently as the identifier are same for all the events in repeating series?

Related

how to update TIME ZONE for using it in my calendar correctly?

Issue here is that while from API I get date in this form
Foundation.Date 2021-07-20 00:00:00 UTC
I successfully add hours, with I get start hour from API in form of "08:30:00" with gives me the value for start (but same issue about the end date) this way:
Foundation.Date 2021-07-20 08:30:00 UTC
but, when I get to calendar, the events are set to 10:30 AM, how could I correct the TIME ZONE?
I tested things like
let calendar = Calendar()
calendar.timezone = .current
but did not work
example from response
{
"data": {
"appointments": [
{
"date": "2021-07-20T00:00:00.000Z",
"time": {
"from": "08:30:00",
"to": "09:30:00"
}
}
]
}
}
my code
func onSynchronize(detailInfo: AppointmentDetail) {
let appointment = detailInfo
let fakeAddress = "fake address"
let fakeLocality = "Alderaan"
let store = EKEventStore()
store.requestAccess(to: .event) {(granted, error) in
if !granted { return }
let event = EKEvent(eventStore: store)
event.title = "my title"
guard let date0 = appointment.date else { return }
guard let hours = appointment.time else { return }
guard let host = appointment.host else { return }
if let componentsFrom = self.timeComponentsFromHourInString(from: hours.from) {
if let start = Calendar.current.date(byAdding: componentsFrom, to: date0, wrappingComponents: false) {
event.startDate = start
event.addAlarm(EKAlarm(absoluteDate: start.dayBefore))
event.addAlarm(EKAlarm(absoluteDate: start.halfHourBefore))
event.endDate = start.hourAfter
}
}
if let componentsTo = self.timeComponentsFromHourInString(from: hours.to) {
if let end = Calendar.current.date(byAdding: componentsTo, to: date0, wrappingComponents: false) {
event.endDate = end
}
}
event.location = fakeAddress.concats(fakeLocality, withSeparator: "-")
event.url = URL(string: BanksUtils.appLocalizeString(forKey: "url"))
if let managerDescription = host.managerDescription,
let manager = BanksUtils.localizeString(forKey: "handledBy").concats(managerDescription.capitalized),
let description = appointment.requestorDescription,
let reason = BanksUtils.localizeString(forKey: "reason").concats(description.capitalized) {
event.notes = manager + "\n" + reason
} else {
if let description = appointment.requestorDescription {
event.notes = BanksUtils.localizeString(forKey: "reason").concats(description.capitalized)
}
}
let structuredLocation = EKStructuredLocation(title: fakeAddress)
structuredLocation.geoLocation = self.location
event.structuredLocation = structuredLocation
event.calendar = store.defaultCalendarForNewEvents
do {
try store.save(event, span: .thisEvent, commit: true)
} catch {
Log.logger.error("Error: \(String(describing: error))")
}
guard let url = URL(string: "calshow:\(event.startDate.timeIntervalSinceReferenceDate)") else { return }
DispatchQueue.main.async {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}
///Allows to get HH:MM:SS from an hour in form of a string
func timeComponentsFromHourInString(from string: String) -> DateComponents? {
let comps = string.components(separatedBy: ":")
guard comps.count == 3,
let hr = Int(comps[0]),
let mn = Int(comps[1]),
let sc = Int(comps[2])
else { return nil }
return DateComponents(hour: hr, minute: mn, second: sc)
}

How I can fix error "No start date has been set"?

When saving an event in the regular calendar, the events are sometimes not saved. An error is displayed: "failed to save event with error: Error Domain = EKErrorDomain Code = 2" No start date has been set. "UserInfo = {NSLocalizedDescription = No start date has been set.}", Although the date is determined correctly
func saveTaskInCalendar() {
let eventStore : EKEventStore = EKEventStore()
eventStore.requestAccess(to: .event) { (granted, error) in
if (granted) && (error == nil) {
let event: EKEvent = EKEvent(eventStore: eventStore)
DispatchQueue.main.async {
event.title = "Test"
event.startDate = Date()
event.endDate = Date()
}
event.calendar = eventStore.defaultCalendarForNewEvents
do {
try eventStore.save(event, span: .thisEvent)
} catch let error as NSError {
print("failed to save event with error : \(error)")
}
}
else {
print("failed to save event with error : \(String(describing: error)) or access not granted")
}
}
}
How do I fix this error?
The problem is that you wrap setting the properties of the EKEvent in a dispatch async block, while you save the even outside the async block. This can cause the property setting to be executed after you've already tried saving the event.
Either remove the DispatchQueue.main.async call, since it shouldn't be necessary anyways or put the saving code inside the dispatch async as well.
let event: EKEvent = EKEvent(eventStore: eventStore)
event.title = "Test"
event.startDate = Date()
event.endDate = Date()
event.calendar = eventStore.defaultCalendarForNewEvents
do {
try eventStore.save(event, span: .thisEvent)
}
or
DispatchQueue.main.async {
let event: EKEvent = EKEvent(eventStore: eventStore)
event.title = "Test"
event.startDate = Date()
event.endDate = Date()
event.calendar = eventStore.defaultCalendarForNewEvents
do {
try eventStore.save(event, span: .thisEvent)
}
...

How to add an event at eventStore.save?

I'm developing an OS X app with Swift.
If pressing the Add button and call the function "saveEvent()" on the screen for adding an event to the calendar, the error "unrecognized selector sent to instance" will appear.
It stops at eventStore.save and the event is not added.
How can I add an event?
import Cocoa
import EventKit
class ViewController: NSViewController {
let eventStore:EKEventStore = EKEventStore()
override func viewDidLoad() {
super.viewDidLoad()
let status = EKEventStore.authorizationStatus(for: EKEntityType.event)
if status == .authorized {
print("success")
}else if status == .notDetermined {
eventStore.requestAccess(to: EKEntityType.event) { (granted, error) in
if granted {
print("true")
}else {
print("false")
}
}
}
saveEvent()
}
func saveEvent() {
if EKEventStore.authorizationStatus(for: .event) == .authorized{
let event = EKEvent(eventStore: eventStore)
event.title = "sample"
event.startDate = Date()
event.endDate = Date().addingTimeInterval(2 * 60 * 60)
event.isAllDay = false
event.calendar = self.eventStore.defaultCalendarForNewEvents
do {
try eventStore.save(event, span: .thisEvent)
}
catch {
print("Save is failed.")
}
}
}
}
I think what you can do is:
eventStore.requestAccess(to: .event, completion: { (granted, error) in
if (granted) && (error == nil) {
let event = EKEvent(eventStore: eventStore)
event.title = title
event.startDate = startDate
event.endDate = endDate
event.notes = note
event.calendar = eventStore.defaultCalendarForNewEvents
do {
try eventStore.save(event, span: .thisEvent)
} catch let e as NSError {
NSLog(e as! String)
return
}
}
})

Swift - completion handler not waiting

My problem is straighforward. I am fetching some data from site with TRON and I need to wait until data is successfully fetched than continue with execution. But my problem is that main thread is not waiting until completion block is completed.
I am doing this as Unit Test, so in setUp() function
Meteorites.Service.sharedInstance.fetchData(completion: { (meteorites, err) in
if err == nil {
self.meteorites = meteorites
}
})
than in test function I am using meteorites array, but I get EXC error (object is nil)
What I am doing wrong? Can someone explain me? Thanks
func fetchData(completion: #escaping ([Meteor]?, APIError<Service.JSONError>?) -> ()) {
print("fetching")
let request: APIRequest<Meteorites, JSONError> = tron.request("")
request.perform(withSuccess: { (meteorites) in
completion(meteorites.meteorites, nil)
}) { (err) in
completion(nil, err)
}
}
In case Meteorites class
class Meteorites: JSONDecodable {
var meteorites: [Meteor]
required init(json: JSON) throws {
print("fetched")
var meteorites = [Meteor]()
for meteorJson in json.array! {
let dateString = meteorJson["year"]
if dateString != JSON.null {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" //Your date format
dateFormatter.timeZone = TimeZone(abbreviation: "GMT+0:00")!
let date = dateFormatter.date(from: dateString.stringValue)
if let compareDate = dateFormatter.date(from: "2011-01-01T00:00:00.000") {
if date! >= compareDate {
let meteor = Meteor()
meteor.date = date!
meteor.name = meteorJson["name"].stringValue
meteor.fall = meteorJson["fall"].stringValue
meteor.id = meteorJson["id"].intValue
meteor.reclong = meteorJson["reclong"].doubleValue
meteor.reclat = meteorJson["reclat"].doubleValue
meteor.mass = meteorJson["mass"].intValue
meteor.nametype = meteorJson["nametype"].stringValue
meteor.recclass = meteorJson["recclass"].stringValue
meteor.lastUpdate = NSDate()
meteorites.append(meteor)
}
}
}
}
self.meteorites = meteorites
}
}

is #distinctUnionOfObjects usable in Swift?

I am trying to make a calendar in swift starting from this great example. So far I managed to show the grid and all the supplementary views customizing UICollectionViewLayout. The problem is when I am trying to add the events in the calendar. Both the day row and the calendar are in the same NSMutableDictionary which has a key that represent the date (String) using this format 2016-10-12. The objective c code is
-(void)groupEventsBySection{
//toDeviceTimezoneDateString simply take a Date and make a string
_eventsBySection = [mEvents groupBy:#"StartDate.toDeviceTimezoneDateString"].mutableCopy;
// after groupBy is performed _eventsBySection contains only those keys found in mEvents with inside an array of objects {
// "2016-10-12" = (
// "<MSEvent: 0x60000004a680>",
// "<MSEvent: 0x60000004a380>");
// "2016-10-13" = (
// "<MSEvent: 0x600000049b10>");
// "2016-10-17" = (
// "<MSEvent: 0x600000049fc0>");
// }
NSDate* date = [NSDate today:#"device"]; // today's date
if(self.daysToShow == 1 && _eventsBySection.count == 1){
date = [NSDate parse:_eventsBySection.allKeys.firstObject];
}
//here it adds the remaining "daysToShow" if the key doesn't already exist in the MutableDictionary.
for(int i = 0; i< self.daysToShow; i++){
if(![_eventsBySection.allKeys containsObject:date.toDeviceTimezoneDateString]){
[_eventsBySection setObject:#[] forKey:date.toDeviceTimezoneDateString];
}
date = [date addDay]; // this just add one day to date
}
}
it's quite clear so far. The problem is when I try to change the groupBy function which in objective c is:
- (NSDictionary*)groupBy:(NSString*)keypath{
return [self groupBy:keypath block:^NSString *(id object, NSString *key) {
return key;
}];
}
- (NSDictionary*)groupBy:(NSString*)keypath block:(NSString*(^)(id object, NSString* key))block{
NSMutableDictionary *result = [NSMutableDictionary new];
NSString* finalKeypath = [NSString stringWithFormat:#"%#.#distinctUnionOfObjects.self",keypath];
NSArray *distinct = [self valueForKeyPath:finalKeypath];
[distinct each:^(NSString* value) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K = %#", keypath,value];
NSArray *objects = [self filteredArrayUsingPredicate:predicate];
[result setObject:objects forKey:block(objects[0],value)];
}];
return result;
}
- (NSDictionary*)expand:(NSString*)keypath{
return [self expand:keypath unique:NO];
}
So far this is what I have done in swift:
I have this class which in the original code is MSEvent
class Event : NSObject {
var title:String?
var location:String?
var startTime:Date?
var endTime:Date?
var duration:Int?
var subtitle:String?
init(startTime: Date, duration: Int, title: String, subtitle: String) {
super.init()
self.startTime = startTime
self.duration = duration
self.title = title
self.subtitle = subtitle
}
I create some fake events:
override func viewDidLoad() {
let today = Date()
let event1 = Event(startTime: today, duration: 60, title: "prova", subtitle: "HEllo")
let event2 = Event(startTime: today, duration: 60, title: "prova", subtitle: "HEllo")
let event3 = Event(startTime: today.dateByAddingDays(days: 1), duration: 60, title: "prova", subtitle: "HEllo")
var array:[Event] = []
array.append(event1)
array.append(event2)
array.append(event3)
calendarView.setEvents(events: array)
}
func setEvents(events:[Event]){
mEvents = events as NSArray
print(mEvents)
//this right now returns the 3 event objects I added:
// (
// "<myapp.Event: 0x60800032ba40>",
// "<myapp.Event: 0x60800032bae0>",
// "<myapp.Event: 0x60800032bcc0>"
// )
self.forceReload(reloadEvent: true) //force reload eventually call the groupEventsBy days
}
func groupEventsByDays(){
// of course everything works without the next line of code
eventsBySection = mEvents.groupBy(keypath: "startTime").mutableCopy() as! NSMutableDictionary
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
var date = dateFormatter.date(from: "2016-10-01")
// right now daysToShow is equal to 30
for _ in 0..<daysToShow{
eventsBySection.setObject([], forKey: self.setFormatDate(date: date!) as NSCopying)
date = self.addDay(date: date!)
}
}
here is the problem. This is my extension.
extension NSArray{
func groupBy(keypath:NSString)->NSDictionary{
return self.groupBy(keypath: keypath, block: { (object, key) -> NSString in
return key
})
}
func groupBy(keypath:NSString,block:#escaping ((_ object: Any, _ key:NSString )-> NSString))-> NSDictionary{
let result:NSMutableDictionary = NSMutableDictionary()
let finalKeypath = String.localizedStringWithFormat("%#.distinctUnionOfObjects.self", keypath)
let distinct:NSArray = self.value(forKey: finalKeypath) as! NSArray
(distinct as AnyObject).each(operation: { (value) in
let predicate = NSPredicate(format: "%K = %#", keypath,value as! CVarArg)
let objects = self.filtered(using: predicate)
result.setObject(objects, forKey: (block(objects[0], value as! NSString)))
})
return result;
}
func each(operation:#escaping ((_ object: AnyObject)-> Void)){
self.enumerateObjects({ (object, idx, stop) in
operation(object as AnyObject)
})
}
}
it crashes with the error this class is not key value coding-compliant for the key startTitle.distinctUnionOfObjects.self.'
I have tried to use map like this
let startDate = array.map ({ $0.startTime })
I managed to get all the date (no distinct though) but I have no idea how to achieve the same result shown in the above code using swift.
I finally managed to do what the above objective c code does with #distinctUnionOfObjects.sel. I am not really sure this is an elegant way of doing it though. All comments or better answers are welcome.
extension Collection {
func find( predicate: (Self.Iterator.Element) throws -> Bool) rethrows -> Self.Iterator.Element? {
return try index(where: predicate).map({self[$0]})
}
}
override func viewDidLoad() {
super.viewDidLoad()
let event1 = Event(startTime: today!, duration: 60, title: "prova", subtitle: "HEllo")
let event2 = Event(startTime: (today?.dateByAddingDays(days: 5))!, duration: 60, title: "prova", subtitle: "HEllo")
let event3 = Event(startTime: (today?.dateByAddingDays(days: 1))!, duration: 60, title: "prova", subtitle: "HEllo")
let event4 = Event(startTime: todayLater!, duration: 60, title: "prova", subtitle: "HEllo")
var array:[Event] = []
array.append(event1)
array.append(event2)
array.append(event3)
array.append(event4)
// I create an array with all the date/startTime
let startTime = array.map ({ $0.startTime })
let dict:NSMutableDictionary = [:]
for date in startTime {
//find the objects for that date
let object = array.find(predicate: {$0.startTime?.toDeviceDateString() == date?.toDeviceDateString()})
//check if that key/date exist in the dictionary
if var val:[Event] = dict[(date?.setFormatDate())!] as! [Event]? {
//if it does I simply add the object to the array
val.append(object!)
dict.setObject(val, forKey: date?.setFormatDate() as! NSCopying)
} else {
print("key is not present in dict")
//otherwise I add a new array with that object inside for the date/key
dict.setObject([object], forKey: date?.setFormatDate() as! NSCopying)
}
}
calendarView.setEvents(events: dict)
}
func setEvents(events:NSMutableDictionary){
//slots events hold the events objects
slotEvents = events
self.forceReload(reloadEvent: true)
}
func groupEventsByDays(){
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
var date = dateFormatter.date(from: "2016-10-01")
for _ in 0..<daysToShow{
if let val:[Event] = slotEvents[(date?.setFormatDate())!] as! [Event]? {
eventsBySection.setObject(val, forKey: date?.setFormatDate() as! NSCopying)
}else{
eventsBySection.setObject([], forKey: date?.setFormatDate() as! NSCopying)
}
date = self.addDay(date: date!)
}
print("after\(eventsBySection)")
//Which print
//{
//"2016-10-01" = (
// );
//"2016-10-02" = (
// );
// "2016-10-03" = (
// "Optional(<myapp.Event: 0x6000001242e0>)",
// "<myapp.Event: 0x6000001242e0>"
// );
// "2016-10-04" = (
// "Optional(<myapp.Event: 0x600000124600>)"
// );
// "2016-10-05" = (
// );
// "2016-10-06" = (
// );
// "2016-10-07" = (
// );
// "2016-10-08" = (
// "Optional(<myapp.Event: 0x600000124240>)"
// );
// "2016-10-09" = (
// );
//.... and so on up to 30 days
}