Chronologically organize Cells in TableView - ios

I have two UITableViewCell's that each correspond to a Twitter Tweet or a Instagram Pic. As of now I have set it so that It will alternate between the two cells and have It go from an Twitter Tweet to an Instagram Pic. However I want to organize the UITableView so that it organizes both the Twitter Tweets and Instagram Pics chronologically in the TableView. Both the date keys for the Instagram API and the Twitter API are different. The Twitter API has a time key of created_at and Instagram's key is created_time. How would I organize both these Arrays? P.S. Both the arrays which the Tweets and Pics are in are NSMutableArray's.
Here is the Code that I have tried thus far:
// Setup for Cells
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *twitterCell = [self createTwitterCell:indexPath];
UITableViewCell *instagramCell = [self createInstagramCell:indexPath];
if (indexPath.row % 2 == 0) {
return twitterCell;
}else{
return instagramCell;
}
}
// Array Updater
- (void)updateArrays {
instaPics = self.timelineResponse[#"data"];
totalFeed = [tweets arrayByAddingObjectsFromArray:instaPics];
[self sortArrayBasedOndate:totalFeed];
}
// Current flawed method to organize by date
- (void)sortArrayBasedOndate {
NSDateFormatter *fmtDate = [[NSDateFormatter alloc] init];
[fmtDate setDateFormat:#"yyyy-MM-dd"];
NSDateFormatter *fmtTime = [[NSDateFormatter alloc] init];
[fmtTime setDateFormat:#"HH:mm"];
NSComparator compareDates = ^(id string1, id string2)
{
// Instagram Date Retrieval
NSDictionary *instagram = self.instaPics;
NSString *createdAt = instagram[#"created_time"];
int createdAtN = [createdAt intValue];
NSTimeInterval timestamp = (NSTimeInterval)createdAtN;
NSDate *date1 = [NSDate dateWithTimeIntervalSince1970:timestamp];
// Twitter Date Retrieval
NSDictionary *twitter = self.tweets;
NSDate *date2 = twitter[#"created_at"];
return [date1 compare:date2];
};
NSSortDescriptor * sortDesc1 = [[NSSortDescriptor alloc] initWithKey:#"start_date" ascending:YES comparator:compareDates];
[totalFeed sortUsingDescriptors:#[sortDesc1]];
}
If you guys need any more code excerpts feel free to ask :)

It seems like you're on the right track. Why don't you also format Twitter's time to match your formatting for Instagram's time?
NSComparator compareDates = ^(id string1, id string2)
{
// Instagram Date Retrieval
NSDictionary *instagram = self.instaPics;
NSString *createdAt = instagram[#"created_time"];
int createdAtN = [createdAt intValue];
NSTimeInterval timestamp = (NSTimeInterval)createdAtN;
NSDate *date1 = [NSDate dateWithTimeIntervalSince1970:timestamp];
// Twitter Date Retrieval
NSDictionary *twitter = self.tweets;
NSString *twitterCreated = twitter[#"created_at"];
int createdAtTwitter = [twitterCreated intValue];
NSTimeInterval timestampTwitter = (NSTimeInterval)createdAtTwitter;
NSDate *date2 = [NSDate dateWithTimeIntervalSince1970:timestampTwitter];
return [date1 compare:date2];
};

Related

Reset value by UITableView sections

I am building a personal budget app that tracks my purchases. I have an NSDictionary of NSArray that looks like this:
{
46 = (
{
DATE = "11/14/2019";
ID = 21;
PLACE = Kroger;
PRICE = "23.32";
},
{
DATE = "11/14/2019";
ID = 22;
PLACE = Walmart;
PRICE = "12.54";
}
);
47 = (
{
DATE = "11/18/2019";
ID = 23;
PLACE = Cinco;
PRICE = "65.32";
}
);
}
The keys in the NSDictionary are determined by strftime using %W. Basically I have a set limit I can spend per week (i.e. 190) and I want in each titleForHeaderInSection: to have a total value that is determined by each field's PRICE in each key.
I currently have the following on the app:
The following calculate the NSDictionary listed above along with the total value:
Note: arrFoodInfo is a NSDictionary of just the entries without the week numbers
for (NSDictionary *dict in self.arrFoodInfo) {
NSString *date = [dict objectForKey:#"DATE"];
[totalPrices addObject:[dict objectForKey:#"PRICE"]];
for (NSString *price in totalPrices) {
if ([totalPrices containsObject:price]) {
_sumPrices += [price floatValue]; // Calculates total value
[totalPrices removeObject:price];
}
}
NSDateFormatter *SDF = [[NSDateFormatter alloc]
init];
[SDF setDateStyle:NSDateFormatterMediumStyle];
[SDF setDateFormat:#"MM/dd/yyyy"];
NSDate *convertedDate = [SDF dateFromString:date];
NSDateComponents *dateComponents = [calendar components:NSCalendarUnitYear | NSCalendarUnitWeekOfYear fromDate:convertedDate];
NSInteger week = dateComponents.weekOfYear;
NSInteger index = week;
NSNumber *key = #(index);
NSMutableArray *weekArray = weeksDictionary[key];
if (!weekArray) {
weekArray = [NSMutableArray array];
weeksDictionary[key] = weekArray;
}
[weekArray addObject:dict];
}
At the moment, I am getting the total of each PRICE but the total value should be different per section. What is the best option to determine this?

Find nearest date in string array

So, I've got an sorted NSArray that contains NSString object (downloaded from a server), with the format: yyyy-MM-dd.
It's pretty much like this:
NSArray <NSString *> *dates = #[#"2017-06-25",
#"2017-06-26",
#"2017-06-27",
#"2017-06-28",
#"2017-06-30",
#"2017-07-01",
#"2017-07-02",
#"2017-07-03"];
So, today is 2017-06-29, and it's not in the array. How do I get the next nearest one? In this sample is 06-30, but it might be 07-01 if 06-30 doesn't exist...
Update
So people are asking me about what I've attempted to do. So it's like this (not very effective, but work)
Find if today is in the array (if yes, return)
Loop dates:
2.1 Convert dateString to date
2.2 Compare if date is greater than today => return if YES
If not found in step#2, return last object in dates array.
Actual code:
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = #"yyyy-MM-dd";
NSDate *today = [NSDate date];
NSUInteger index = [dates indexOfObject:[formatter stringFromDate:today]];
// Step 1
if (index == NSNotFound) {
// Step 2: Loop converted
NSInteger i = 0;
for (NSString *date in dates) {
// Step2.1: find the next nearest date's index
NSDate *convertedDate = [formmater dateFromString:date];
// Step2.2: Compare
if ([convertedDate intervalSinceDate:today] > 0) {
index = i;
break;
}
i++;
}
// Step 3: Still not found, index = last index
if (index == NSNotFound) index = i-1;
}
return dates[index];
This doesn't look so good because I might reload the dates array pretty much. Can I have a better solution?
Your algorithm is not bad, though your code doesn't appear to implement it (no sort?). If you'd like to improve it consider this:
First there is probably little point in doing a first scan to check for an exact match - that is potentially a linear search (implemented by indexOfObject:) through an unordered array, and if it fails you have to scan again for a close match, just do them at the same time.
Second there is no advantage in sorting, which is at best O(NlogN), as a linear search, O(N), will find you the answer you need.
Here is a sketch:
Convert the date you are searching for from NSString to NSDate, call it, say, target
Set bestMatch, an NSString to nil. Set bestDelta, an NSTimeInterval, to the maximum possible value DBL_MAX.
Iterate over your dates array:
3.1. Convert the string date to an NSDate, say date
3.2. Set delta to the difference between date and target
3.3. If delta is zero you have an exact match, return it
3.4. If delta is better than bestDelta, update bestDelta and bestMatch
After iteration bestMatch is the best match or nil if there wasn't one.
That is a single iteration, O(N), early return on exact match.
HTH
Please find the simplest solution for your problem. Updated solution based on sorting order!
We can use NSPredicate Block to solve.
static NSDateFormatter* formatter = nil;
static NSDate* today = nil;
// return an NSDate for a string given in yyyy-MM-dd
- (NSDate *)dateFromString:(NSString *)string {
if (formatter == nil) {
formatter = [NSDateFormatter new];
formatter.dateFormat = #"yyyy-MM-dd";
}
return [formatter dateFromString:string];
}
// Helps to return today date.
-(NSDate*) getTodayDate {
if (today == nil) {
today = [NSDate date];
}
return today;
}
// Helps to find nearest date from Array using Predicate
-(NSString*)findNearestDate:(NSArray*)dateArray {
today = nil;
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSString *dateString, NSDictionary *bind){
// this is the important part, lets get things in NSDate form so we can use them.
NSDate *dob = [self dateFromString:dateString];
NSComparisonResult result = [[self getTodayDate] compare:dob];
if (result == NSOrderedSame || result == NSOrderedAscending) {
return true;
}
return false;
}];
// Apply the predicate block.
NSArray *futureDates = [dateArray filteredArrayUsingPredicate:predicate];
if ([futureDates count] > 0) {
// Sort the Array.
futureDates = [futureDates sortedArrayUsingSelector: #selector(compare:)];
return [futureDates objectAtIndex:0];
}
return nil;
}
NSArray <NSString *> *dates = #[#"2017-06-25",
#"2017-06-26",
#"2017-06-27",
#"2017-06-28",
#"2017-06-30",
#"2017-07-01",
#"2017-07-02",
#"2017-07-03"];
NSLog(#"Nearest Date: %#", [self findNearestDate:dates]);
Answer: Nearest Date: 2017-06-30
1. Input
So you have an array of NSString like this
// input
NSArray<NSString *> * words = #[#"2017-06-25",
#"2017-06-26",
#"2017-06-27",
#"2017-06-28",
#"2017-06-30",
#"2017-07-01",
#"2017-07-02",
#"2017-07-03"];
2. Converting the array of NSString into an array of NSDate
First of all you need to convert the each input string into an NSDate
NSMutableArray<NSDate *> * dates = [NSMutableArray new];
NSDateFormatter * dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = #"yyyy-MM-dd";
for (NSString * word in words) {
[dates addObject:[dateFormatter dateFromString:word]];
}
3. Finding the nearestDate
Now you can find the nearest date
NSDate * nearestDate = nil;
NSTimeInterval deltaForNearesttDate = 0;
NSDate * now = [NSDate new];
for (NSDate * date in dates) {
NSTimeInterval delta = fabs([date timeIntervalSinceDate:now]);
if (nearestDate == nil || (delta < deltaForNearesttDate)) {
deltaForNearesttDate = delta;
nearestDate = date;
}
}
4. Conclusion
The result is into the nearestDate variable so
NSLog(#"%#", nearestDate);
Wed Jun 28 00:00:00 2017

How to show Single date in multiple events reload in MBCalendarKit

I am using a Calendar view in my project. I am using a MBCalendarKit. That time single date in single event show. But I want single date on multiple events show. But how it possible please help.
- (void) viewWillAppear: (BOOL)animated{
NSArray *title = [_caldevice valueForKey:#"pill"];
// NSLog(#"event name fetch %#",title);
NSArray *date =[_caldevice valueForKey:#"datetaken"];
// NSLog(#"event fetch %#",date);
NSArray*dose= [_caldevice valueForKey:#"dose"];
NSString *title1;
NSString*title2;
NSDate *date1;
NSData *imgdata;
CKCalendarEvent *releaseUpdatedCalendarKit;
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"dd-MM-yyyy";
for (int i = 0; i < [date count]; i++){
title1 = NSLocalizedString(title[i], #"");
title2 = NSLocalizedString(dose[i], #"");
NSString *combined = [NSString stringWithFormat:#"%# - %#", title1, title2];
date1 = [dateFormatter dateFromString:date[i]];
releaseUpdatedCalendarKit = [CKCalendarEvent eventWithTitle:combined andDate:date1 andInfo:Nil];
// NSLog(#"Event: %# , %#",combined,date1);
// releaseUpdatedCalendarKit = [CKCalendarEvent eventWithTitle:combined andDate:date1 andInfo:Nil andColor:[UIColor blueColor]];
self.data[date1] = #[releaseUpdatedCalendarKit];
}
}
You're looping over a bunch of events and for each event, you're *replacing the previously assigned array with a new one, containing a single element.
Replace this:
self.data[date1] = #[releaseUpdatedCalendarKit];
with something more like this:
// 1. First, get the previous events for that day.
NSMutableArray <CKCalendarEvent *> *events = self.data[date1].mutableCopy;
// 2. If events exist, append the event, otherwise create an empty array with the new event.
if (events) {
[events addObject: newEvent];
}
else {
events = #[newEvent];
}
// 3. Set the events for the date key.
self.data[date1] = events;
This way you're doing an "add or create" operation, instead of overwriting each time.
Disclosure: I wrote and maintain MBCalendarKit.

Method not working on second time through: SVPullToRefresh

So, I've made some progress on my issue described here — https://stackoverflow.com/questions/27348458/svpulltorefresh-infinite-scroll-with-an-rss-feed
Nonetheless, I have a problem. The table loads 10 YouTube videos initially. Then, the second ten will load using
__weak MasterViewController *weakSelf = self;
[self.tableView addInfiniteScrollingWithActionHandler:^{
NSLog(#"Infinite Scroll");
[weakSelf updateVideoList];
}];
-(void) updateVideoList essentially adds 10 items to the array that loads into the table view (reloadData is called on the tableview at the end after addObjectsFromArray adds items to the initial array). This works well enough. The problem is that trying to load a third 10 items does not work. I added the NSLog to see if it even goes into the method a second time, and it doesn't.
Is there any reason for the method to not work a second time?
Edit Here's updateVideoList, but I used the log to determine that the method isn't even called the second time through:
- (void) updateVideoList {
NSString *baseDomain = #"https://gdata.youtube.com/feeds/api/videos";
NSString *maxresults = #"10";
NSString *startIndex = [NSString stringWithFormat:#"%lu", (unsigned long)videoList.count+1];
NSString *orderBy = #"published";
NSString *author = #"theverge";
NSString *extension = #"v=2&alt=jsonc";
NSString *urlYoutube = [NSString stringWithFormat:#"%#?max-results=%#&start-index=%#&orderby=%#&author=%#&%#",
baseDomain,maxresults, startIndex,orderBy,author,extension];
NSDictionary *listOfVideos = [JSONParser listOfVideos:urlYoutube];
int videoListSize = [[[listOfVideos valueForKey:#"data"] valueForKey:#"totalItems"] intValue];
if (videoListSize>0) {
NSMutableArray *secondYoutubeList = [[NSMutableArray alloc] init];
NSArray *arrayVideoList = [[listOfVideos valueForKey:#"data"] valueForKey:#"items"];
for (NSDictionary *videoDictionary in arrayVideoList) {
NSString *idVideo = [videoDictionary valueForKey:#"id"];
NSString *description = [videoDictionary valueForKey:#"description"];
//NSString *updated = [videoDictionary valueForKey:#"updated"];
// NSString *departTimeDate = [videoDictionary valueForKey:#"updated"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
//NSDate *date = [dateFormatter dateFromString:departTimeDate];
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];
NSString *updated = [NSString stringWithFormat:#"%ld, %ld, %ld", (long)month,(long)day,(long)year];
NSString *duration = [videoDictionary valueForKey:#"duration"];
NSString *title = [videoDictionary valueForKey:#"title"];
// If we are using wifi, take hqDefault
NSString *thumbnail = [[videoDictionary valueForKey:#"thumbnail"] valueForKey:#"hqDefault"];
YoutubeVideo *video = [[YoutubeVideo alloc] initWithId:idVideo withDescription:description withUpdated:updated withDuration:duration withTitle:title withThumbnail:thumbnail];
[secondYoutubeList addObject:video];
}
[videoList addObjectsFromArray:secondYoutubeList];
[self.tableView reloadData];
}
}
You should call
[self.tableView.infiniteScrollingView stopAnimating];
Once your work of reloading is got completed.

EKEvent is not added according to given EKRecurrenceRule

I'm trying to add an event into calendar with recurrence rule RRULE:FREQ=YEARLY;BYMONTH=6,7;BYDAY=1TH
So according to this rule the event should be added yearly, each 1st thursday of june and july until expire date, which I've set in my project.
In my project, events are created but not according to the recurrence rule. With the following code the events added only on each 1st thursday of june. Why the events are not added on 1st thursday of each july also?
Here is .m file code
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self createEvent];
}
- (void)createEvent
{
EKEventStore *eventStore = [[EKEventStore alloc] init];
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
event.title = #"testRecurrenceRule";
event.location = #"Dhaka";
[event setCalendar:[eventStore defaultCalendarForNewEvents]];
event.startDate = [self dateFromString:#"2013-06-18T21:00:00+06:00"];
event.endDate = [self dateFromString:#"2013-06-18T22:00:00+06:00"];
id recurrenceRule = [self recurrenceRuleForEvent];
if(recurrenceRule != nil)
[event addRecurrenceRule:recurrenceRule];
if ([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
// iOS 6 and later
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self saveTheEvent:event eventStore:eventStore];
//[eventStore saveEvent:event span:EKSpanThisEvent error:error];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
//do nothing
});
}
}];
}
else
{
[self saveTheEvent:event eventStore:eventStore];
}
textView.text = [NSString stringWithFormat:#"Event has been added with recurrence rule %#",recurrenceRule];
}
- (void)saveTheEvent:(EKEvent *)aEvent eventStore:(EKEventStore *)aStore
{
[aStore saveEvent:aEvent span:EKSpanThisEvent error:NULL];
}
- (EKRecurrenceRule *)recurrenceRuleForEvent
{
//just creating a recurrence rule for RRULE:FREQ=YEARLY;BYMONTH=6,7;BYDAY=1TH
// setting the values directly for testing purpose.
//FREQ=YEARLY
EKRecurrenceFrequency recurrenceFrequency = EKRecurrenceFrequencyYearly;
NSInteger recurrenceInterval = 1;
EKRecurrenceEnd *endRecurrence = nil;
NSMutableArray *monthsOfTheYearArray = [NSMutableArray array];
NSMutableArray *daysOfTheWeekArray = [NSMutableArray array];
NSMutableArray *daysOfTheMonthArray = [NSMutableArray array];
NSMutableArray *weeksOfTheYearArray = [NSMutableArray array];
NSMutableArray *daysOfTheYearArray = [NSMutableArray array];
NSMutableArray *setPositionsArray = [NSMutableArray array];
//BYMONTH=6,7
[monthsOfTheYearArray addObject:[NSNumber numberWithInt:6]];
[monthsOfTheYearArray addObject:[NSNumber numberWithInt:7]];
//BYDAY=1TH
[daysOfTheWeekArray addObject:[EKRecurrenceDayOfWeek dayOfWeek:5 weekNumber:1]];
endRecurrence = [EKRecurrenceEnd recurrenceEndWithEndDate:[self dateFromString:#"2018-12-15T22:30+06:00"]];
EKRecurrenceRule *recurrence = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:recurrenceFrequency
interval:recurrenceInterval
daysOfTheWeek:daysOfTheWeekArray
daysOfTheMonth:daysOfTheMonthArray
monthsOfTheYear:monthsOfTheYearArray
weeksOfTheYear:weeksOfTheYearArray
daysOfTheYear:daysOfTheYearArray
setPositions:setPositionsArray
end:endRecurrence];
return recurrence;
}
- (NSDate *)dateFromString:(NSString *)string
{
//check if the date string in null
if ([string length] == 0)
return nil;
NSString *dateString = nil;
NSString *modifiedString = nil;
BOOL secSpotMissing = false;
NSRange range = [string rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:#"T"]];
if (range.location != NSNotFound)
{
dateString = [string substringFromIndex:range.location];
range = [dateString rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:#"+-Z"]];
if (range.location != NSNotFound)
{
//seperate the time portion of date string and checking second field is missing or not. like is it HH:mm or HH:mm:ss?
if ([[[dateString substringToIndex:range.location] componentsSeparatedByString:#":"] count] < 3)
secSpotMissing = true;
//seperate the time zone portion and checking is there any extra ':' on it. It should like -0600 not -06:00. If it has that extra ':', just replacing it here.
dateString = [dateString substringFromIndex:range.location];
if([dateString hasSuffix:#"Z"])
modifiedString = [dateString stringByReplacingOccurrencesOfString:#"Z" withString:#"+0000"];
else
modifiedString = [dateString stringByReplacingOccurrencesOfString:#":" withString:#""];
string = [string stringByReplacingOccurrencesOfString:dateString withString:modifiedString];
}
}
else
return nil;
// converting the date string according to it's format.
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
if (secSpotMissing)
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mmZZZ"];
else
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZZZ"];
return [dateFormatter dateFromString:string];
}
Can somebody please help me regarding this?
This seems a repeat of another question. Basically, according to "BYDAY" rule, the 1st for YEARLY frequency means the first week in the year - instead of 1st week each month.
#Shuvo, I did not read rfc. But here is Apple document EKRecurrenceDayOfWeek.
The EKRecurrenceDayOfWeek class represents a day of the week for use with an EKRecurrenceRule object. A day of the week can optionally have a week number, indicating a specific day in the recurrence rule’s frequency. For example, a day of the week with a day value of Tuesday and a week number of 2 would represent the second Tuesday of every month in a monthly recurrence rule, and the second Tuesday of every year in a yearly recurrence rule.
When you say "1st Thursday", that is correct - except in the context of yearly, it is 1st thursday of the year.
The bug was confirmed by Apple, at least until iOS 7.1.3 (which is the latest available version at this moment).

Resources