Scroll to certain point in tableview - objective-c

I have a tableview that is filled up with dates. My section header is the month name. You can see my tableview over here.
What I want is that it scrolls to the section of the month of that moment. For setting my section headers I use this method.
id <NSFetchedResultsSectionInfo> theSection = [[self.fetchedResultsController sections] objectAtIndex:section];
static NSArray *monthSymbols = nil;
NSArray *dutchMonths = [[NSArray alloc]initWithObjects:#"Januari",#"Februari",#"Maart",#"April",#"Mei",#"Juni",#"Juli",#"Augustus",#"September",#"Oktober",#"November",#"December", nil];
if (!monthSymbols) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setCalendar:[NSCalendar currentCalendar]];
[formatter setMonthSymbols:dutchMonths];
monthSymbols = [formatter monthSymbols];
}
NSLog(#"%#",monthSymbols);
NSInteger numericSection = [[theSection name] integerValue];
NSInteger year = numericSection / 1000;
NSInteger month = numericSection - (year * 1000);
NSString *titleString = [NSString stringWithFormat:#"%#", [monthSymbols objectAtIndex:month-1]];
label.text = titleString;
I know already that I have to use this method.
[sampleListTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
But how do I get the indexpath of the correct row?
Any help? If you need more details. Please help.
Kind regards.

Looks like you can get an index by looping through your sections array (from self.fetchedResultsController) and comparing the month obtained from the object in question to the month from the current date. If you have a match, then your indexPath would be:
NSIndexPath *path = [NSIndexath indexPathForRow:0 inSection:foundIndex];
You should also make the dutchMonths array static so it isn't created every time that method is called. Also, if you aren't using ARC, it's leaking (unless the release code is not posted). A general rule of thumb is to make the date formatter static too, or manage one instance of it in some way, because it is an expensive operation. I know with this code it is only created once since you only use it to populate the monthSymbols array, but if you need to use the same formatter in other code, then you'll want to rewrite that.
To do all of this, you should extract the logic in this method and put it in smaller, reusable methods like
- (NSInteger)monthFromSection:(id<NSFetchedResultsSectionInfo>)section;
Then you can write:
NSIndexPath *path = nil;
NSInteger currentMonth = // Calculate month from date returned by [NSDate date]
NSArray *sections = [self.fetchedResultsController sections];
for (int i = 0; i < sections.count; i++)
{
if (currentMonth = [self monthFromSection:[sections objectAtIndex:i]])
{
path = [NSIndexPath indexPathForRow:0 inSection:i];
break;
}
}

Related

XLForm - How to hide a row depending on the value of another row?

I could need some help.
I'm using XLForm in my Objective-C project. I have a first row with a picker where you have four different options. Then there is a second picker. But the second picker should only be available if a certain value is selected in the first picker.
I know there is a description on the github-site but I don't really understand what to do. So please help me doing this.
Here are the relevant parts of my code:
row = [XLFormRowDescriptor formRowDescriptorWithTag:#"repeat" rowType:XLFormRowDescriptorTypeSelectorPickerViewInline title:#"Wiederholen:" ];
row.cellClass = [LehrerXLFormInlineSelectorCell class];
NSMutableArray *selectionArray = [NSMutableArray array];
for (NSNumber *item in [APIDataReplacement appointmentRepeatingList]) {
NSString *title = [APIDataReplacement appointmentRepeatingToString:item.intValue];
[selectionArray addObject:[XLFormOptionsObject formOptionsObjectWithValue:item displayText:title]];
}
row.selectorOptions = selectionArray;
if ([selectionArray count] == 0) {
[row setDisabled:#YES];
}
if (aItem) {
int repeatingID = [[aItem appointmentRepeatingId] intValue];
NSString *title = [APIDataReplacement appointmentRepeatingToString:repeatingID];
row.value = [XLFormOptionsObject formOptionsObjectWithValue:[NSNumber numberWithInt:repeatingID] displayText:title];
}else{
NSNumber *first = [[APIDataReplacement appointmentRepeatingList] firstObject];
NSString *title = [APIDataReplacement appointmentRepeatingToString:first.intValue];
row.value = [XLFormOptionsObject formOptionsObjectWithValue:first displayText:title];
}
[section addFormRow:row];
section = [XLFormSectionDescriptor formSectionWithTitle:#""];
[form addFormSection:section];
row = [XLFormRowDescriptor formRowDescriptorWithTag:#"weekday" rowType:XLFormRowDescriptorTypeSelectorPickerViewInline title: #"Wochentag:"];
row.cellClass = [LehrerXLFormInlineSelectorCell class];
NSMutableArray *selectionArray1 = [NSMutableArray array];
for (NSNumber *item in [APIDataReplacement dayList]) {
NSString *title = [APIDataReplacement dayToString:item.intValue];
[selectionArray1 addObject:[XLFormOptionsObject formOptionsObjectWithValue:item displayText:title]];
}
row.selectorOptions = selectionArray1;
if (aItem) {
// AppointmentRepeating *currentRepeat = [aItem appointmentRepeating];
int dayID = [[aItem startday] intValue];
NSString *title = [APIDataReplacement dayToString:dayID];
row.value = [XLFormOptionsObject formOptionsObjectWithValue:[NSNumber numberWithInt:dayID] displayText:title];
}else{
NSNumber *first = [[APIDataReplacement dayList] firstObject];
NSString *title = [APIDataReplacement dayToString:first.intValue];
row.value = [XLFormOptionsObject formOptionsObjectWithValue:first displayText:title];
}
[section addFormRow:row];
section = [XLFormSectionDescriptor formSectionWithTitle:#""];
[form addFormSection:section];
These are the two pickers. I think I should do something in the formRowDescriptorValueHasChanged-method, but I don't know what. If the first picker has the value "20" then the second picker should be hidden.
Really appreciate your help.
For your second XLFormRowDescriptor you should:
secondRow.hidden = [NSString stringWithFormat:#"$%# == 20", firstRow];
firstRow is the first XLFormRowDescriptor.
This actually creates an NSPredicate that is automatically evaluated whenever values change and sets the visibility appropriately.
Taken from XLForm's example:
For example, you could set the following string to a row (second) to
make it disappear when a previous row (first) contains the value
"hide".
second.hidden = [NSString stringWithFormat:#"$%d contains[c] 'hide'", [first.value intValue]];
Edit: I changed the predicate to work with NSNumber values.

Put last 4 objects of an ascending array into UITableView

My array (trips) grows depending on how many entries a user has made. Currently the array is in ascending order from the database.
I need to have it start at the last object and populate my tableview with the last 4 'trips' objects from the array.
My tableview only shows 4 rows, I want those rows to be the last 4 from the array.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if ([trips count] < 4){
return [trips count]
} else {
return 4;
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyReuseIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:MyIdentifier];
}
UserMiles *cellInfo = [self milesAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%# to %#", cellInfo.beg_school, cellInfo.end_school];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]init];
dateFormat.dateFormat = #"MM/dd/yyyy";
NSString *date = [dateFormat stringFromDate:cellInfo.driven_date];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", date];
return cell;
}
-(UserMiles *) milesAtIndex: (NSInteger) index
{
return [trips objectAtIndex:index];
}
You can use a NSSortDescriptor to get the array into the order you want. The quickest fix would be to just take your milesAtIndex: and change it to:
-(UserMiles *) milesAtIndex: (NSInteger) index
{
NSSortDescriptor *sort = [[NSSortDescriptor sortDescriptorWithKey:#"propertyHere" ascending:NO];
return [[trips sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]] objectAtIndex:index];
}
Note that #"propertyHere" can be any property of your UserMiles object that you want to sort by. So if you are using a date for example, you might have a myUserMiles.tripDate property, so you'd replace #"propertyHere" with #"tripDate".
I utilized the reverseObjectEnumerator class (which I had no idea existed!) to get it done.
It now works great!
trips = [[trips reverseObjectEnumerator] allObjects];

Avoiding fragile unit tests with NSDateFormatter

What is the best practice for testing NSDateFormatter methods? For example, lets say I have a method:
- (NSString *)formatStringFromDate:(NSDate *)date {
NSDateFormatter *f = [[NSDateFormatter alloc] init];
[f setTimeStyle:NSDateFormatterShortStyle];
[f setDateStyle:NSDateFormatterNoStyle];
return [f stringFromDate:date];
}
There are two ways I can think of testing this method using Kiwi:
1) Create the same formatter in the unit test:
it(#"should format a date", ^{
NSDate *date = [NSDate date];
NSDateFormatter *f = [[NSDateFormatter alloc] init];
[f setTimeStyle:NSDateFormatterShortStyle];
[f setDateStyle:NSDateFormatterNoStyle];
[[[testObject formatStringFromDate:date] should] equal:[f stringFromDate:date]];
});
2) Explicitly write the intended output:
it(#"should format a date", ^{
NSDate *date = [NSDate dateWithTimeIntervalSince1970:1385546122];
NSDateFormatter *f = [[NSDateFormatter alloc] init];
[f setTimeStyle:NSDateFormatterShortStyle];
[f setDateStyle:NSDateFormatterNoStyle];
[[[testObject formatStringFromDate:date] should] equal:#"9:55 am"];
});
Now, to me, #1 seems a bit redundant. I know the test will pass as I'm essentially duplicating the method in my unit test.
Method #2 is a non-starter, as it is incredibly fragile. It completely relies on the test devices current locale being what you'd expect.
So my question is: is there a more appropriate method to test this method, or should I just go ahead with test method #1.
As you say in your comment, what you want to test is that the cell's detailTextLabel's text is set to date with the expected format when a date is present.
This can be tested in several different ways, I expose here the one I would go for.
First of all, creating a date formatter each time we want to format a date is not efficient and it makes it more difficult to test.
So what I suggest is to create a date formatter property in your table view controller:
// .h
#property (strong, nonatomic) NSDateFormatter *dateFormatter;
// .m
- (NSDateFormatter *) dateFormatter
{
if (_dateFormatter == nil)
{
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[_dateFormatter setDateStyle:NSDateFormatterNoStyle];
}
return _dateFormatter;
}
For the date formatter I would create a simple test that verifies the timeStyle and dateStyle. I am not familiar with Kiwi so I use OCUnit assertions:
TableViewController *sut;// Instantiate the table view controller
STAssertTrue(sut.dateFormatter.timeStyle == NSDateFormatterShortStyle, nil);
STAssertTrue(sut.dateFormatter.dateStyle == setDateStyle:NSDateFormatterNoStyle, nil);
After having this, the idea is to create a test that verifies that the cell returned by tableView:cellForRowAtIndexPath: has its detailTextLabel's text set with the string returned by the formatter. As you said testing the string returned by the date formatter is fragile so we can mock it, stub stringFromDate: returning a constant and verify that the detailTextLabel's text is set to that constant.
So we write the test. I'd try to write it with Kiwi mocks, so sorry if I do something wrong - the idea is the important thing:
TableViewController *sut;// Instantiate the table view controller
id mockDateFormatter = [NSDateFormatter mock];
NSString * const kFormattedDate = #"formattedDate";
NSDate * const date = [NSDate date];
[mockDateFormatter stub:#selector(stringFromDate:) andReturn:kFormattedDate withArguments:date,nil];
sut.dateFormatter = mockDateFormatter;
sut.dates = #[date];// As an example we have an array of dates to show. In the real case we would have an array of the objects you want to show in the table view.
[sut view];// In case we have the registered cells...
UITableViewCell *cell = [sut tableView:sut.tableView
cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
STAssertEqualObjects(cell.detailTextLabel.text, kFormattedDate, nil);
And the method to satisfy that test would be something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// I assume you have a standard cell registered in your table view
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
NSDate *date = [self.dates objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [self.dateFormatter stringFromDate:date];
return cell;
}
Hope it helps.

Accessing Dictionaries inside a Dictionary by Index in Obj-C

I'm currently trying to learn Objective-C and I've stumbled across a little problem.
I want to build an iPad Application for collecting simple Numbers,which the User enters, by Date and Time.
To do that I thought of this Structure:
Dictionary("main")
Dictionary("27012013") //this holds all data for the 27th of January 2013
Index = 0
3:33pm = 123 //this would mean at 3:33pm there was a value of 123
..other values to follow
Dictionary("28012013") //and so on
So basically there is one big Dictionary called "main" which holds the dictionaries for all days which then hold their index and all recorded values.
I get the Value's by a UIAlertView Input which then calls the
(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
delegate method in which then the current time and date is saved in NSString's like this:
NSDateFormatter *format = [[NSDateFormatter alloc] init];
[format setDateStyle:NSDateFormatterNoStyle];
[format setTimeStyle:NSDateFormatterShortStyle];
NSString *time = [format stringFromDate:[NSDate date]];
NSLog(#"Time: %# and entered Text:%#",time,returnvalue.text);
//Which Date do we have
[format setDateStyle:NSDateFormatterShortStyle];
[format setTimeStyle:NSDateFormatterNoStyle];
NSString *date = [format stringFromDate:NSDate.date];
NSLog(#"Found Date:%#",date);
NSString *identifier =
[[date componentsSeparatedByCharactersInSet:
[NSCharacterSet punctuationCharacterSet]] componentsJoinedByString:#""];
NSLog(#"Identifier:%#",identifier);
where returnvalue.text holds the entered text. I then check if the dictionary called like the NSString identifier already exist and if not add it to main:
if([main objectForKey:identifier] == nil){
//No Dict Available for current Date so create one:
//there should be no more than 30 Entries per Day
NSMutableDictionary *d = [[NSMutableDictionary alloc] initWithCapacity:30];
[d setObject:identifier forKey:#"Name"];
NSNumber* tmpi = [NSNumber numberWithInteger:main.count];
[d setObject:tmpi forKey:#"Index"];
//and store the recieved value in it
[d setObject:returnvalue.text forKey:time];
[main setObject:d forKey:identifier];
}
else{
NSMutableDictionary *d = [main objectForKey:identifier];
[d setObject:returnvalue.text forKey:time];
}
The First question here is: Do I have to use main.count or main.count+1?
Furthermore I want to display they information in tableView in which each day should have his own section. For the method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
I therefore need to address they dictionaries inside the main dictionary by Index or e.g. filter them by their Index key to return the number of values inside of them.
I do now I could use a giant NSArray for main but this would make the method for determining if the dictionary for the day already exist more complicated and would rather not want to do this.
So could anyone please help me?
Thanks in Advance
First Answer:
it depends on you but "main.count" will return the number of objects in dictionary may be it's zero. So if you want to start from zero then it's good otherwise +1.
Second Answer:
here is function of NSDictionary "allKeys" which return all the keys as "NSArray" then you can get each key by index if you don't know about the "key" just get it using index from array.

Adding NSMutableArray to another from a loop seems to create duplicates

I'm parsing through an NSDictionary of json-encoded events and placing them into a two-dimensional NSMutableArray based on their month -- for display in a sectioned table view.
Since I am adding items to an array and then placing that array in an array (event_container) in a loop, event_container shows the correct number of arrays, however, they all appear to be duplicates of the last iteration, so all of the contents of event_container are the same array.
I believe this is because it's a pointer and/or not being released. I'm unsure of an appropriate way around this or possibly even a better solution. I'm using ARC.
int month = 0;
int current_month = 0;
int counter = 0;
event_container = [[NSMutableArray alloc] init];
temp_array = [[NSMutableArray alloc] init];
for (NSDictionary *result in results)
{
NCEvent *anEvent = [[NCEvent alloc] init];
anEvent.title = [result objectForKey:#"title"];
anEvent.startdate = [result objectForKey:#"startdate"];
anEvent.enddate = [result objectForKey:#"enddate"];
NSDateFormatter *importDate = [[NSDateFormatter alloc] init];
[importDate setDateFormat:#"yyyy-M-d H:m:ss"];
anEvent.dateStart = [importDate dateFromString:anEvent.startdate];
anEvent.dateEnd = [importDate dateFromString: anEvent.enddate];
NSDateFormatter *exportDate = [[NSDateFormatter alloc] init];
[exportDate setDateFormat:#"d"];
anEvent.text_date = [exportDate stringFromDate: anEvent.dateStart];
NSDateFormatter *exportMon = [[NSDateFormatter alloc] init];
[exportMon setDateFormat:#"MMM"];
anEvent.text_mon = [exportMon stringFromDate: anEvent.dateStart];
NSDateFormatter *monthInt = [[NSDateFormatter alloc] init];
[monthInt setDateFormat:#"M"];
month = [[monthInt stringFromDate: anEvent.dateStart] intValue];
if(counter == 1){ //first month
current_month = month;
NSLog(#"I'm the first month: %i", month);
[temp_array addObject:anEvent];
}
else if(month > current_month){ //new month
NSLog(#"This is a new month");
current_month = month;
//add the events array to events container and reset the events array
[self.event_container addObject: temp_array];
[temp_array removeAllObjects];
[temp_array addObject:anEvent];
}
else{
NSLog(#"Same Month"); //same month
[temp_array addObject:anEvent];
}
NSLog(#"Event month integer: %i", month);
anEvent = nil;
counter++;
}
Those arrays are declared as properties:
#property (nonatomic, retain) NSMutableArray *event_container;
#property (nonatomic, retain) NSMutableArray *temp_array;
In the line:
[self.event_container addObject: temp_array];
You are always adding the same instance temp_array to self.event_container. This is why you see the same array duplicated many times.
You can solve this by doing the following for example:
-Add the following before your for loop
for (int i = 0; i < 12; i++) {
[event_container addObject:[NSMutableArray array]];
}
for (NSDictionary *result in results)
...
-Remove
if(counter == 1){ //first month
current_month = month;
NSLog(#"I'm the first month: %i", month);
[temp_array addObject:anEvent];
}
-and change the code that comes after that into :
tmp_array = [event_container objectAtIndex:month];
[temp_array addObject:anEvent];
Your suspicions about the array being a pointer is basically correct. The problem is that your temp_array isn't so temporary -- it's in fact the same array object every time through your loop.
You're creating it outside the loop, and whenever you send it addObject: or removeAllObjects, it's affecting the stuff that you've already put in there.
The key part, though, is that when you add the temp_array to event_container, it's the exact same object. It's not copied; the event_container array just gets a pointer to temp_array. When you add it again, it's the same thing. Since event_container just holds a whole bunch of pointers, you end up looking at the same object when you inspect it.
That's what's happening. To solve this, you need to create a separate array for each month; I think that sch's answer will work for you.
A quick demonstration:
NSMutableArray * container = [NSMutableArray array];
NSMutableArray * temp = [NSMutableArray array];
int i;
for( i = 0; i < 5; i++ ){
[temp addObject:[NSNumber numberWithInt:i]];
[container addObject:temp]; // Doesn't copy; just adds pointer to temp
[temp removeAllObjects];
}
// Inspecting container now, we find that it has five arrays, all empty.
NSLog(#"%#", container);
temp_array is a pointer type (like all objects in objective c). Therefore, with this call:
[self.event_container addObject: temp_array];
...you are adding a pointer to that object to event_container. You are not creating a new array, merely adding multiple pointers to the same object. What you most likely want to do is add a (pointer to a) copy of the object, like this:
[self.event_container addObject: [temp_array mutableCopy]];