How to get a set of EKCalendars properly - ios

I now develop an iOS App that shows a list of iCloud EKCalendars.
My swift codes can get a set of iCloud EKCalendars, but the order is always different.
Could anyone give me advice to get the set in order?
let eventStore = EKEventStore()
let sources = eventStore.sources
for source in sources {
if (source.title == "iCloud") {
let calendars = source.calendars(for: .event)
for calendar in calendars {
print("calendar title = " + calendar.title)
}
}
}
Example of the result of the codes:
calendar title = title1
calendar title = title6
calendar title = title5
calendar title = title3
calendar title = title4
calendar title = title2

So let calendars = source.calendars(for: .event) is of type Set<EKCalendar>, by definition a Set is data type that is not ordered so whenever you iterate through a set it could always be in different order, the only thing that Set enforces is that there is only one instance of the object it the set.
If you want to have calendars ordered you will have to order it by yourself, one is to order them by title or calendarIdentifier.

Just sort the Set, the result is an ordered array:
let eventStore = EKEventStore()
let sources = eventStore.sources.filter{ $0.title == "iCloud" }
for source in sources {
let calendars = source.calendars(for: .event).sorted{ $0.title < $1.title }
calendars.forEach { print("calendar title = ", $0.title) }
}

Related

Downcast from Any to Specific type

I have firestore DB "sales", which has one column called saleapproveddate. There are 2 levels of people, one who logs sale and other approves the sale logs. While logging sale, I save the saleapproveddate as NSNull() (which saves as nil in firestore DB field). Approver can update the saleapproveddate as TimeStamp, but if the approver never approves the sale log, it remains as nil in firestore DB field. So it can have either nil or TimeStamp type.
I have model Sales
class Sale {
var saleapprovedate : Any?
}
When I load the data from firestore, I tried to downcast the saleapprovedate as Any
let approvedDate = document[SaleProperties.paidDate.rawValue] as Any
But the real challenge is, saleapprovedate can have either nil or Timestamp. How do I check for type condition, convert to specific type and display in label?
Below is what I tried:
While loading data:
sale.saleapprovedate = document[SaleProperties.saleapprovedate.rawValue] as Any
while displaying data:
let saleItem = sales[indexPath.row]
let paidDate = saleItem.saleapprovedate
if paidDate == nil {
cell.paidDateLabelContainer.text = "Yet to pay"
cell.paidStatusImageView.isHidden = true
}
else {
let paidDateTimeStamp = saleItem.saleapprovedate as! Timestamp
let convertedPaidDate = self.convertTimestampToDate(timeStamp: paidDateTimeStamp)
cell.paidDateLabelContainer.text = convertDateToString(date: convertedPaidDate)
cell.paidStatusImageView.isHidden = false
}
But the above code is not updating the cell label properly. I have two data, one has saleapprovedate as Timestamp and other as nil. Both the cell label is displaying as "Yet to pay". What is wrong?
Modal :
var incentivepaiddate : Any?
Array :
var sales : [Sale] = [Sale]()
Loading data from firestore :
for document in querySnapshot!.documents {
let sale = Sale()
sale.incentivepaiddate = document[SaleProperties.incentivepaiddate.rawValue]
self.sales.append(sale)
}
Checking for nil, downcasting to a specific type and display data in cell
let saleItem = sales[indexPath.row]
let paidDate = saleItem.incentivepaiddate
if let paid = paidDate {
let paidDateTimeStamp = paid as? Timestamp
let convertedPaidDate = self.convertTimestampToDate(timeStamp: paidDateTimeStamp!)
cell.paidDateLabelContainer.text = convertDateToString(date: convertedPaidDate)
}
else {
cell.paidDateLabelContainer.text = "Yet to pay"
cell.paidStatusImageView.isHidden = true
}
Hope this helps someone!

How to change year date in CSSearchableItem (content type — kUTTypeMovie)?

I have an app with video content and I track all viewed items with NSUserActivity to add it to Spotlight. The result looks perfect, except year date, it is always a year when NSUserActivity was added to the index. But I need to change this to release year of a movie.
let activity = NSUserActivity(activityType: "com.bundleid.movie")
activity.isEligibleForSearch = true
activity.title = "Mess"
let movie = kUTTypeMovie as String
let attributes = CSSearchableItemAttributeSet(itemContentType: movie)
attributes.title = "Mess"
attributes.contentDescription = "Even if you get behind bars..."
attributes.thumbnailData = try? Data(contentsOf: thumbURL) // cover
attributes.rating = NSNumber(value: 3.7)
attributes.ratingDescription = "(Kinopoisk: 7.9, IMDb: 7.4)"
attributes.duration = NSNumber(value: 5785) // 1:36:25 in sec
attributes.contentCreationDate = dateFormatter.date(from: "1989") // ¯\_(ツ)_/¯
activity.contentAttributeSet = attributes
I'm trying setting not only attributes.contentCreationDate but also addedDate and all other Dates available in CSSearchableItemAttributeSet_Media.h. But it is always 2018(
I was able to reproduce this.
However, when I put this CSSearchableItemAttributeSet not inside of a NSUserActivity but instead inside a CSSearchableItem and index that with CSSearchableIndex.default().indexSearchableItems(), the search result says 1989 as desired.
Would that work for you?

NSpredicate one child item per parent

I'm using Realm to store all the data in my app.
I have a shop item that contains a list of promotion ids and a promotion that have a parent property that is an id to the shop.
I now filter every promotion that is available now, and I like to add one more filter, so that the results list only contains one promotion per shop.
How can I complete the filter, so I only get one available promotion per shop?
Thanks!
// Kim
//EDIT
let startDate: Double = currentDate.timeIntervalSince1970
let endDate: Double = currentDate.timeIntervalSince1970
let parentPredicate = NSPredicate(format: "")
let datePredicate = NSPredicate(format: "SUBQUERY(dates, $d, $d.start <= \(startDate) && $d.end >= \(endDate)).#count > 0")
self.realm?.objects(CSPromotions.self).filter(datePredicate).filter(parentPredicate)
Realm Swift currently doesn't natively support distinct, so there's no great way to do this. What you can do is manually build an array of the distinct values:
let shops = NSMutableSet()
let promotions = self.realm?.objects(CSPromotions.self).filter(datePredicate).filter(parentPredicate).filter {
let id = $0.shop.id
if shops.contains(id) {
return false
}
shops.add(id)
return true
}

Using CoreData how to configure a fetch request to find upcoming birthdays?

I’m using Xcode 7.3 and Swift 2.2 with an NSFetchedResultsController. Is it possible to create the required fetch request configured with a predicate and sort descriptors to solve this problem?
Given a Person entity that has a birthDate attribute how do I configure a fetch request to return upcoming birthdays? Here’s what I have tried:
I created a transient attribute called birthDateThisYear and configured it to return the person’s birthday this year. But I discovered that you can’t use a transient attribute in a fetch request with Core Data.
I tried the accepted answer here by denormalizing with birthDateDayNumber and birthDateMonthNumber attributes along with a custom setter for birthDate but I couldn’t figure out the predicate. What if today was Dec 31? Somehow it would need to wrap around to include Jan, Feb, and Mar.
I read that it could be done with expressions and comparison predicates. But I couldn’t figure out a solution. Anyone got this working?
I thought it work to create a denormalized attribute called birthDateInYear2000 but, again, that suffers from the same overlap problem.
Any ideas?
Suggestion:
Fetch all birthdays.
Map the birthdays to the next occurrence from now
let calendar = NSCalendar.currentCalendar()
let nextBirthDays = birthdays.map { (date) -> NSDate in
let components = calendar.components([.Day, .Month], fromDate: date)
return calendar.nextDateAfterDate(NSDate(), matchingComponents: components, options: .MatchNextTime)!
}
Sort the dates
let sortedNextBirthDays = nextBirthDays.sort { $0.compare($1) == .OrderedAscending }
Now sortedNextBirthDays contains all upcoming birthdays sorted ascending.
In Core Data you could fetch records as dictionary with birthday and objectID (or full name), create a temporary struct, map and sort the items and get the person for the objectID (or use the full name) – or you even could apply the logic to an NSManagedObject array
Edit
Using an NSFetchedResultsController you can sort the table view only if the information about the next birthday is stored in the persistent store (assuming it's the MySQL-store), because you can't apply sort descriptors including keys pointing to transient or computed properties.
The best place to update the nextBirthDate property is just before creating the NSFetchedResultsController instance of the view controller.
Create an (optional) attribute nextBirthDate in the entity Person
Create a extension of NSDate to calculate the next occurrence of a date from now
extension NSDate {
var nextOccurrence : NSDate {
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day, .Month], fromDate: self)
return calendar.nextDateAfterDate(NSDate(), matchingComponents: components, options: .MatchNextTime)!
}
}
In the closure to initialize the NSFetchResultsController instance add code to update the nextBirthDate property of each record
lazy var fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest(entityName: "Person")
do {
let people = try self.managedObjectContext.executeFetchRequest(fetchRequest) as! [Person]
people.forEach({ (person) in
let nextBirthDate = person.birthDate.nextOccurrence
if person.nextBirthDate == nil || person.nextBirthDate! != nextBirthDate {
person.nextBirthDate = nextBirthDate
}
})
if self.managedObjectContext.hasChanges {
try self.managedObjectContext.save()
}
} catch {
print(error)
}
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptors = [NSSortDescriptor(key:"nextBirthDate", ascending: true)]
fetchRequest.sortDescriptors = sortDescriptors
...
If the view controller can edit the birthDate property don't forget to update the nextBirthDate property as well to keep the view in sync.
my suggestion is similar:
fetch all customers/persons
filter all birthdates that range 3 days before and 3 days after today
Use closure like the following:
func fetchBirthdayList(){
// this example considers all birthdays that are like
// |---TODAY---|
// Each hyphen represents a day
let date = Date()
let timeSpan = 3
let cal = Calendar.current
nextBirthDays = self.list.filter { (customer) -> Bool in
if let birthDate = customer.birthDate {
let difference = cal.dateComponents([.day,.year], from: birthDate as! Date, to: date)
return (( (difference.day! <= timeSpan) || ((365 - difference.day!) <= timeSpan) ) && difference.day! >= 0)
}
return false
}
}
If you want, you can order the result afterwards.
Cheers
Oliver

How can you create an iCloud Calender for an iOS app to use with swift?

I've seen many people simply search the user's calendars and then grab the first one that's writable and start throwing the events on there. I don't like this practice; I prefer to respect my users a little more.
So how can you actually make your own calendar for the app to use for the events that it creates?
I came up with the following function, assuming your app is allowed to access the users calendars:
let eventStore = EKEventStore()
func checkCalendar() -> EKCalendar {
var retCal: EKCalendar?
let calendars = eventStore.calendarsForEntityType(EKEntityTypeEvent) as! [EKCalendar] // Grab every calendar the user has
var exists: Bool = false
for calendar in calendars { // Search all these calendars
if calendar.title == "Any String" {
exists = true
retCal = calendar
}
}
var err : NSError?
if !exists {
let newCalendar = EKCalendar(forEntityType:EKEntityTypeEvent, eventStore:eventStore)
newCalendar.title="Any String"
newCalendar.source = eventStore.defaultCalendarForNewEvents.source
eventStore.saveCalendar(newCalendar, commit:true, error:&err)
retCal = newCalendar
}
return retCal!
}
This function looks for a calendar by the name of "Any String". Of course, this can be anything you like. If it doesn't exist, it will create it. It then returns the calendar, so you can assign it to a calendar variable and then work with it however you please.

Resources