I'm adding a calendar view to my app using Kal Calendar but am having problems implementing a didSelectRowAtIndexPath method on the event list. I would like to push a view controller when the user selects an event for any given day. I've tried putting the method in "KalView.m", "KalViewController.m", and "KalDataSource.m", but none are recognized. Where is the appropriate place to call such a method?
I had similar issue once, Here is how I implemented it.
#import <MTDates/NSDate+MTDates.h>
#import <ObjectiveSugar/ObjectiveSugar.h>
#import <UIImageView+WebCache.h>
#import "EventsViewController.h"
#import "EventDetailsViewController.h"
#import "EventCell.h"
#import "Event.h"
#import "KalViewController.h"
#import "CalendarViewController.h"
#implementation EventsViewController
- (id)initWithEvents:(NSArray *)_events {
self = [super init];
events = _events;
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self listUpdated];
[self addCalendarView];
}
- (void)listUpdated
{
NSMutableArray *allEvents = [NSMutableArray array];
[allEvents addObjectsFromArray:events];
NSArray *sortedArray = [allEvents sortedArrayUsingComparator:^NSComparisonResult(Event *obj1, Event *obj2) {
return [obj1.date compare:obj2.date];
}];
_allEvents = sortedArray;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_allEvents count];
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//custom table cell (EventCell is a view I'm initialising my rows with)
Event *event = _allEvents[indexPath.row];
NSString *reuseIdentifier = [NSString stringWithFormat:#"Cell%#%#", event.venue.identifier, event.identifier];
EventCell *cell = (EventCell*) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (!cell) {
cell = [EventCell createDetailedCellWithReuseIdentifier:reuseIdentifier];
cell.nameLabel.text = event.name;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Event *event = _allEvents[indexPath.row];
EventDetailsViewController *controller = [[EventDetailsViewController alloc] initWithEvent:event];
[self.navigationController pushViewController:controller animated:YES];
}
- (void) addCalendarView{
_calenderView = [[KalViewController alloc] initWithSelectedDate:[NSDate date]];
[[self.view viewWithTag:2] addSubview:_calenderView.view]; /* depends on your requirements*/
_calenderView.view.tag = 200; /* not necessary */
[_calenderView.view setFrame:self.view.bounds];
_calenderView.dataSource = self;
_calenderView.delegate = self;
}
- (void)loadItemsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
//NSLog(#"%#, %#", fromDate, toDate);
// filter and pass the array to the events tableview
[self filterByDateSelected:toDate];
}
-(void)presentingDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate delegate:(id<KalDataSourceCallbacks>)delegate
{
/** when selecting a different month **/
}
- (void)removeAllItems
{
NSLog(#"Items Removed");
// remove all the previous items from the tableview
}
-(void) filterByDateSelected: (NSDate *)selectedDate
{
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd"];
NSString *theDate = [dateFormat stringFromDate:selectedDate];
NSDate *_date = [NSDate dateFromString:theDate usingFormat:#"yyyy-MM-dd"];
// filter table by selectedDate
NSArray *_dateFilteredEvents = _allEvents;
_dateFilteredEvents = [_allEvents filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Event *evaluatedEvent, NSDictionary *bindings) {
if ([evaluatedEvent.date isEqualToDate:_date]) {
return YES;
}
return NO;
}]];
NSLog(#"%#", _dateFilteredEvents);
// uncomment the following line if you want to display list in another controller
//[self showEventsByDate:_dateFilteredEvents];
// or else refresh table after updating the list
_allEvents = _dateFilteredEvents;
[_tableView reloadData];
}
- (void) showEventsByDate:(NSArray*)events
{
if (events.count > 0) {
CalendarViewController *_controller = [[CalendarViewController alloc] initWithEventArray:events];
[self.navigationController pushViewController:_controller animated:YES];
}
}
- (void) removeCalenderView{
[_calenderView.view removeFromSuperview];
}
#end
Related
I try using a custom kal datasource for my calendar. I succeeded in getting the data, and when I tried to run it I keep getting this error:
-[NSNull cc_componentsForMonthDayAndYear]: unrecognized selector sent to instance 0x2823fb8
My code
// KalParseDataSource.m
#import "KalParseDataSource.h"
#import <Parse/Parse.h>
#implementation KalParseDataSource
static BOOL IsDateBetweenInclusive(NSDate *date, NSDate *begin, NSDate *end)
{
return [date compare:begin] != NSOrderedAscending && [date compare:end] != NSOrderedDescending;
}
- (id)init
{
if ((self = [super init])) {
items = [[NSMutableArray alloc] init];
events= [[NSMutableArray alloc] init];
}
return self;
}
#pragma mark UITableViewDataSource protocol conformance
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"MyCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = #"Filler text";
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 0;
}
- (void)presentingDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate delegate:(id<KalDataSourceCallbacks>)delegate{
NSLog(#"getting data");
if ([events count] > 0) {
[delegate loadedDataSource:self];
return;
}
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:#"yyyy-MM-dd hh:mm:ss"];
PFUser *user = [PFUser currentUser];
PFQuery *query = [PFQuery queryWithClassName:#"CalendarEvents"];
[query whereKey:#"user" equalTo:user];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
[events removeAllObjects];
[events addObjectsFromArray:objects];
[delegate loadedDataSource:self];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
- (NSArray *)markedDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate {
return [[self tagsFrom:fromDate to:toDate] valueForKeyPath:#"date"];
}
- (void)loadItemsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
[items addObjectsFromArray:[self tagsFrom:fromDate to:toDate]];
}
- (NSArray *)tagsFrom:(NSDate *)fromDate to:(NSDate *)toDate
{
NSMutableArray *matches = [NSMutableArray array];
for (PFObject *event in events){
if (IsDateBetweenInclusive([event objectForKey:#"event_date"], fromDate, toDate)){
[matches addObject:event];
}
}
return matches;
}
- (void)removeAllItems{
[items removeAllObjects];
}
#end
My view controller holding the calendar.
#import "MainMenuViewController.h"
#import "Kal.h"
#import "KalParseDataSource.h"
#interface MainMenuViewController ()
#end
#implementation MainMenuViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
id<KalDataSource> source = [[KalParseDataSource alloc] init];
KalViewController *calendar = [[KalViewController alloc] init];
calendar.dataSource = source;
[self addChildViewController:calendar];
[calendar didMoveToParentViewController:self];
[self.view addSubview:calendar.view];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I'm stuck with this for 3 days, Can anyone give simple example in using this 3rd party framework https://github.com/klazuka/Kal ? The one on the repo seems a bit complicated, I just want to use a custom data source.
it seems that the function cc_componentsForMonthDayAndYear you are calling with null just put a debug there from wherever the function is being called and check if the function being called is on NSDate and it is not released.
And you can always Enable Zombies to find the released object when crash happens. You can track if any variable gets released via setting NSZombieEnabled to YES. With zombies enabled, messages to deallocated objects will no longer behave strangely or crash in difficult-to-understand ways, but will instead log a message and die in a predictable and debugger-breakpointable way.
You can set NSZombieEnabled by the following steps.
Select Product from the menu bar above. Keep alt/option pressed and select "Test..." or "Run...".
1.
Go to the Arguments tab, and add NSZombieEnabled YES in the "Environment Variables" section.
OR
2.
Go to the Diagnostics tab, and check Enable Zombie Objects in the "Memory Management" section.
I would like to know if I have a uitable, the title of each table items is a date and time. May I know how to code if I want the table auto scroll down to a cell with specific date e.g. Today's date? What should I code in the viewDidLoad method?
Here is my code for the table,
#interface PictureListMainTable : UITableViewController{
IBOutlet UIButton*scroll;
}
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) NSMutableArray *pictureListData;
#property (strong, nonatomic) IBOutlet UIButton*scroll;
- (void)readDataForTable;
-(IBAction)scrolldown:(id)sender;
#end
#synthesize managedObjectContext, pictureListData;
#synthesize scroll;
// When the view reappears, read new data for table
- (void)viewWillAppear:(BOOL)animated
{
// Repopulate the array with new table data
[self readDataForTable];
}
// Grab data for table - this will be used whenever the list appears or reappears after an add/edit
- (void)readDataForTable
{
// Grab the data
pictureListData = [CoreDataHelper getObjectsForEntity:#"Pictures" withSortKey:#"title" andSortAscending:YES andContext:managedObjectContext];
// Force table refresh
[self.tableView reloadData];
}
#pragma mark - Actions
// Button to log out of app (dismiss the modal view!)
- (IBAction)logoutButtonPressed:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark - Segue methods
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get a reference to our detail view
PictureListDetail *pld = (PictureListDetail *)[segue destinationViewController];
// Pass the managed object context to the destination view controller
pld.managedObjectContext = managedObjectContext;
// If we are editing a picture we need to pass some stuff, so check the segue title first
if ([[segue identifier] isEqualToString:#"EditPicture"])
{
// Get the row we selected to view
NSInteger selectedIndex = [[self.tableView indexPathForSelectedRow] row];
// Pass the picture object from the table that we want to view
pld.currentPicture = [pictureListData objectAtIndex:selectedIndex];
}
}
#pragma mark - Table view data source
// Return the number of sections in the table
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// Return the number of rows in the section (the amount of items in our array)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [pictureListData count];
}
// Create / reuse a table cell and configure it for display
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Get the core data object we need to use to populate this table cell
Pictures *currentCell = [pictureListData objectAtIndex:indexPath.row];
// Fill in the cell contents
cell.textLabel.text = [currentCell title];
cell.detailTextLabel.text = [currentCell desc];
// If a picture exists then use it
if ([currentCell smallPicture])
{
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.image = [UIImage imageWithData:[currentCell smallPicture]];
}
return cell;
}
// Swipe to delete has been used. Remove the table item
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Get a reference to the table item in our data array
Pictures *itemToDelete = [self.pictureListData objectAtIndex:indexPath.row];
// Delete the item in Core Data
[self.managedObjectContext deleteObject:itemToDelete];
// Remove the item from our array
[pictureListData removeObjectAtIndex:indexPath.row];
// Commit the deletion in core data
NSError *error;
if (![self.managedObjectContext save:&error])
NSLog(#"Failed to delete picture item with error: %#", [error domain]);
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
#end
Thanks
Since you are dealing with dates, it's better to create an array of dates and find current date using dateComponents.
In this snippet dates is an array of NSDate instances.
NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *today = [NSDate date];
[self.dates enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDate *date = (NSDate *)obj;
NSDateComponents *components = [cal components:NSDayCalendarUnit
fromDate:date
toDate:today
options:0];
if ([components day]==0) {
*stop = TRUE;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}
}];
EDIT :
- (void)viewWillAppear:(BOOL)animated
{
// Repopulate the array with new table data
[self readDataForTable];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"dd.MMMM - EEEE"];
NSDate *today = [NSDate date];
[pictureListData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Pictures *picture = (Pictures *)obj;
NSDate *date = [dateFormatter dateFromString:picture.title];
NSDateComponents *components = [cal components:NSDayCalendarUnit
fromDate:date
toDate:today
options:0];
if ([components day]==0) {
*stop = TRUE;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}
}];
}
Firstly find index of todays date or any other date in UITableView's dataSource like this:
I assume date format of search date and data source date are same
NSInteger index = NSNotFound;
if ([tableViewArray containsObject:dateHere]) {
index = [tableViewArray indexOfObject:dateHere];
}
Now scroll using scrollToRowAtIndexPath
[yourTableView scrollToRowAtIndexPath:NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
EDIT : Use these ScrollPosition to your requirement
UITableViewScrollPositionNone,
UITableViewScrollPositionTop,
UITableViewScrollPositionMiddle,
UITableViewScrollPositionBottom
You have to calculate indexpath for specific date. And use this delegate to scroll down or scroll Up the UITableViewCells in TableView
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:NO];
if you know positions of UITableViewCells to use
[tableView setContentOffset:CGPointMake(0,0) animated:YES];
I have integrated Kal Calendar in my app successfully, here is the method how I show the calendar
-(void)showDepartDatePicker{
NSLog(#"showDepartDatePicker");
if(_departDatePicker != nil){
[self.navigationController pushViewController:_departDatePicker animated:YES];
}else{
_departDatePicker = [[KalViewController alloc] init];
_departDatePicker.title = #"Departure Date";
_departDatePicker.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Today" style:UIBarButtonItemStyleBordered target:self action:#selector(showAndSelectTodayDeparturePicker)];
_departDatePicker.kvcDelegate = self;
[self.navigationController pushViewController:_departDatePicker animated:YES];
}
}
I have added following in KalViewController.h,
#protocol KalViewControllerDelegate <NSObject>
#required
- (void)didSelectDate:(KalDate *)date andLoaded:(BOOL)loaded;
#end
and
#property (nonatomic, assign) id <KalViewControllerDelegate> kvcDelegate;
and implemented this delegate method in my viewController as
- (void)didSelectDate:(KalDate *)date andLoaded:(BOOL)loaded
{
NSLog(#"Title : %#",[self.navigationController.visibleViewController title]);
[self.navigationController popViewControllerAnimated:YES];
}
now, as per my question, I want to implement KalDataSource so as it show the day marked with events and selecting it show the event details in the table view available below Month's View.
Refer this link if you are new for Kal Calendar https://github.com/klazuka/Kal
Second Question, here is how I call delegate method from KalViewController.m
- (void)didSelectDate:(KalDate *)date
{
self.selectedDate = [date NSDate];
NSDate *from = [[date NSDate] cc_dateByMovingToBeginningOfDay];
NSDate *to = [[date NSDate] cc_dateByMovingToEndOfDay];
[self clearTable];
[dataSource loadItemsFromDate:from toDate:to];
[tableView reloadData];
[tableView flashScrollIndicators];
//line below calls my delegate method
[self.kvcDelegate didSelectDate:date andLoaded:_loaded];
}
What happens is, when I call showDepartDatePicker to push KalViewController to my navigation stack, it calls my delegate method 2 times(which should be called on date selection), then for every date selection calls that delegate method again(1 time).
Even I want to limit this calendar not to show past dates!
Please help me out on this.
Define a class which implements the KalDataSource protocol. See below example for class implementing KalDataSource protocol.
//header file
#import Kal.h"
#interface MyClass : NSObject <KalDataSource>
#property (nonatomic, weak) id<KalDataSourceCallbacks> kalCallbackDelegate;
#property (nonatomic, strong) NSArray *events;
#end
----------------------
//implementation file
- (void)presentingDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate delegate:(id<KalDataSourceCallbacks>)callbackDelegate
{
//If you already have the events between fromDate and toDate then just call
[callbackDelegate loadedDataSource:self];
//Else store the callback variable in a property and do an asyncrhonous
//call to load the events.
self.kalCallbackDelegate = callbackDelegate;
//When the Asynchronous call is done, call
[self.kalCallbackDelgate loadedDataSource:self];
}
- (void)removeAllItems
{
self.eventsForDay = nil;
}
- (NSArray *)markedDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate
{
//self.events may have multiple events with the same date. This pulls only the unique dates.
//Also assumes that the object has an eventDate property for the beginning of the day
NSMutableSet *uniqueDatesSet = [NSMutableSet setWithArray:[self.events valueForKeyPath:#"#distinctUnionOfObjects.eventDate"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self >= %# && self <= %#", fromDate, toDate];
NSArray *uniqueDates = [[uniqueDatesSet allObjects] filteredArrayUsingPredicate:predicate];
return uniqueDates;
}
- (void)loadItemsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate
{
//filter for the events that occur between fromDate and toDate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"eventDate >= %# && eventDate <= %#", fromDate, toDate];
NSArray *filteredArray = [self.events filteredArrayUsingPredicate:predicate];
self.eventsForDay = [filteredArray sortedArrayUsingSelector:#selector(compareByEventTime:)];
}
To render the UITableViewCells, implement tableView:cellForRowAtIndexPath: in your KalDataSource class just like you would for a UITableView.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Event *event = [self.events objectAtIndex:indexPath.row];
static NSString *identifier = #"MyCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = event.title;
return cell;
}
If you want to know when a UITableViewCell is selected, define a class implementing the UITableViewDelegate protocol and set _departDatePicker.delegate equal to that class. Then you can implement the regular UITableViewDelegate methods in that class.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Event *event = [self.events objectAtIndex:indexPath.row];
MyViewController *viewController = [[UIStoryboard storyboardWithName:#"iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"eventInfo"];
viewController.event = event;
[self.navigationController pushViewController:viewController animated:YES];
}
So below is my code for my controller, which I have four of one for each tab on the tab bar. I have two issues.
I am getting an array out bounds but I can't chase it down. Am I missing something here?
Second when my app starts up it take 5 - 8 seconds before it loads the first view of the tab bar. Once it loads and I click another tab, the tab doesn't turn blue and the app sits there for 3 seconds then finally switches. I am a noob and am struggling here. I think this issue might be fixed by fetching the data in another thread or something? I am sure this controller is full of bugs.
import "AllMackTableViewController.h"
import "PostDetailViewController.h"
import "MackdabMobileAppDelegate.h"
import <QuartzCore/QuartzCore.h>
import "Post.h"
define FONT_SIZE 14.0f
define CELL_CONTENT_WIDTH 254.0f
define CELL_CONTENT_MARGIN 5.0f
#implementation AllMackTableViewController
#synthesize jsonArray;
#synthesize localJsonArray;
#synthesize postDetailViewController;
#synthesize allPostsTableView;
#synthesize fetchedResultsController, managedObjectContext;
#synthesize minuteTimer;
#synthesize regionsTimer;
pragma mark
pragma mark View lifecycle
(void)viewDidLoad {
[super viewDidLoad];
self.title = NSLocalizedString(#"Feed", #"You're Matching Posts");
UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self
action:#selector(refresh)];
self.navigationItem.rightBarButtonItem = refreshButton;
allPostsTableView.delegate = self;
[refreshButton release];
[self refresh];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
(void)scrollViewDidScroll:(UIScrollView *) scrollView;
{
CGFloat scrollHeight = scrollView.contentSize.height;
CGPoint p = scrollView.contentOffset;
//NSLog(#"x = %f, y = %f", p.x, p.y);
}
(void)viewWillAppear:(BOOL)animated {
/*
Set up two timers, one that fires every minute, the other every fifteen minutes.
1/ The time displayed for each time zone must be updated every minute on the minute.
2/ Time zone data is cached. Some time zones are based on 15 minute differences from GMT, so update the cache every 15 minutes, on the "quarter".
*/
NSTimer *timer;
NSDate *date = [NSDate date];
/*
Set up a timer to update the table view every minute on the minute so that it shows the current time.
*/
NSDate *oneMinuteFromNow = [date dateByAddingTimeInterval:60];
NSCalendarUnit unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *timerDateComponents = [calendar components:unitFlags fromDate:oneMinuteFromNow];
// Add 1 second to make sure the minute update has passed when the timer fires.
[timerDateComponents setSecond:1];
NSDate *minuteTimerDate = [calendar dateFromComponents:timerDateComponents];
timer = [[NSTimer alloc] initWithFireDate:minuteTimerDate interval:60 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
self.minuteTimer = timer;
[timer release];
}
pragma mark
pragma mark Table view data source
(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
// return [self.localJsonArray count];
NSLog(#"Setting numberOfRowsInSection to %i",[self.localJsonArray count]);
if ( [jsonArray count] < 9 ) {
return [self.localJsonArray count];
NSLog(#"Setting numberOfRowsInSection to %i inside of non add one.",[self.localJsonArray count]);
} else {
return [self.localJsonArray count] + 1;
}
}
(void)updateTime:(NSTimer *)timer {
/*
To display the current time, redisplay the time labels.
Don't reload the table view's data as this is unnecessarily expensive it recalculates the number of cells and the height of each item to determine the total height of the view etc. The external dimensions of the cells haven't changed, just their contents.
*/
NSArray *visibleCells = self.tableView.visibleCells;
for (PostTableCustomCellController *cell in visibleCells) {
[cell redisplay];
}
[self updateRegions];
}
(NSString *)dateDiff:(NSString *)origDate {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setFormatterBehavior:NSDateFormatterBehavior10_4];
[df setDateFormat:#"EEE, dd MMM yy HH:mm:ss VVVV"];
NSDate *convertedDate = [df dateFromString:origDate];
[df release];
NSDate *todayDate = [NSDate date];
double ti = [convertedDate timeIntervalSinceDate:todayDate];
ti = ti * 1;
if(ti < 1) {
return #"never";
} else if (ti < 60) {
return #"less than a minute ago";
} else if (ti < 3600) {
int diff = round(ti / 60);
return [NSString stringWithFormat:#"%d minutes ago", diff];
} else if (ti < 86400) {
int diff = round(ti / 60 / 60);
return[NSString stringWithFormat:#"%d hours ago", diff];
} else if (ti < 2629743) {
int diff = round(ti / 60 / 60 / 24);
return[NSString stringWithFormat:#"%d days ago", diff];
} else {
return #"never";
}
}
(void)updateRegions {
/*
The following sets the date for the regions, hence also for the time zone wrappers. This has the sideeffect of "faulting" the time zone wrappers (see TimeZoneWrapper's setDate: method), so can be used to relieve memory pressure.
*/
NSDate *date = [NSDate date];
for (Post *post in localJsonArray) {
post.timeRemaining = [self dateDiff:post.deadline];
}
}
// Customize the appearance of table view cells.
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UILabel *distance;
UIImage *image = [UIImage imageNamed:#"imageA.png"];
static NSString *CellIdentifier = #"customCell"; // This is identifier given in IB jason set this.
PostTableCustomCellController *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (indexPath.row != [localJsonArray count] ) {
if (cell == nil) {
NSLog(#"Cell created");
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"PostTableCustomCellController" owner:nil options:nil];
for(id currentObject in nibObjects)
{
if([currentObject isKindOfClass:[PostTableCustomCellController class]])
{
cell = (PostTableCustomCellController *)currentObject;
}
}
}
Post *post = [localJsonArray objectAtIndex:indexPath.row];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy"];
//Optionally for time zone converstions
//[formatter setTimeZone:[NSTimeZone timeZoneWithName:#"..."]];
NSString *stringFromDate = [formatter stringFromDate:post.deadline];
cell.authorName.text = post.author;
cell.deadline.text = stringFromDate;
cell.budget.text = post.budget;
cell.description.text = post.description;
[[cell avatar] setImage:[UIImage imageNamed:#"butterfly.jpeg"]];
[stringFromDate release];
NSLog([NSString stringWithFormat:#"%#", post.distance]);
if([#"" isEqualToString: post.distance] || nil == post.distance) {
cell.distance.text = #"1 mi.";
}else{
cell.distance.text = #"25 mi.";
}
Post *myPost = [localJsonArray objectAtIndex:[indexPath row]];
// Might can remove these
UILabel *locationLabel = (UILabel *) [cell distance];
UITextView *postTextView = (UITextView *) [cell description];
//CGSize maximumLabelSize = CGSizeMake(254,88.0f);
NSString *text;
CGSize constraint;
CGSize size;
CGFloat height;
CGFloat realHeight;
CGSize expectedLabelSize;
text = myPost.description;
constraint = CGSizeMake(CELL_CONTENT_WIDTH (CELL_CONTENT_MARGIN * 2), 88.0f);
size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
height = MAX(size.height, 35.0f);
realHeight = height + 36.0f (10);
expectedLabelSize = [text sizeWithFont:[UIFont boldSystemFontOfSize:18.0f] constrainedToSize:CGSizeMake(252.0f, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
CGRect newFrame = postTextView.frame;
newFrame.size.height = expectedLabelSize.height;
newFrame.size.width = 252;
postTextView.frame = newFrame;
[[cell description] sizeToFit];
[[cell viewForBackground] sizeToFit];
[cell setNeedsDisplay];
}// Ok, all done for filling the normal cells, next we probaply reach the +1 index, which doesn’t contain anything yet
if ( [jsonArray count] == 9 ) { // Only call this if the array count is 25
if(indexPath.row == [localJsonArray count] ) { // Here we check if we reached the end of the index, so the +1 row
if (cell == nil) {
cell = [[[PostTableCustomCellController alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
// Reset previous content of the cell, I have these defined in a UITableCell subclass, change them where needed
cell.authorName.text = nil;
cell.deadline.text = nil;
cell.budget.text = nil;
cell.description.text = nil;
// Here we create the ‘Load more’ cell
UILabel *loadMore =[[UILabel alloc]initWithFrame: CGRectMake(20,0,362,100)];
loadMore.textColor = [UIColor blackColor];
loadMore.highlightedTextColor = [UIColor darkGrayColor];
loadMore.backgroundColor = [UIColor clearColor];
loadMore.font=[UIFont fontWithName:#"Verdana" size:20];
loadMore.textAlignment=UITextAlignmentCenter;
loadMore.font=[UIFont boldSystemFontOfSize:20];
loadMore.text=#"Load More..";
[cell addSubview:loadMore];
}
}
return cell;
}
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
if (indexPath.row != [localJsonArray count] ) {
Post *myPost = [localJsonArray objectAtIndex:[indexPath row]];
NSString *text = myPost.description;
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH (CELL_CONTENT_MARGIN * 2), 88.0f);
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = MAX(size.height, 35.0f);
return height + (CELL_CONTENT_MARGIN * 2) + 28.0f;
}else{
return 100.0f;
}
}
(IBAction)nextTwentyFivePlease:(NSString *)thePostID{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.jsonArray = [Post findNextTwentyFiveRemote:thePostID];
if (self.localJsonArray == nil) {
self.localJsonArray = [[NSMutableArray alloc]init]; // init the local array if it’s empty
}
[self.localJsonArray addObjectsFromArray:self.jsonArray];
[self.tableView reloadData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
(IBAction)refresh {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.jsonArray = [Post findAllRemote];
if (self.localJsonArray == nil) {
self.localJsonArray = [[NSMutableArray alloc]init]; // init the local array if it’s empty
}
[self.localJsonArray addObjectsFromArray:self.jsonArray];
//self.localJsonArray = [Post findAllRemote];
[self.tableView reloadData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
pragma mark
pragma mark Table view delegate
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ( [jsonArray count] == 9 ) { // only check for the load more function if we have 25 results in the dynamic array
if (indexPath.row == [localJsonArray count] ) { // See if we reached the +1 in the results
Post *tempPost = [jsonArray objectAtIndex:indexPath.row];
NSLog('What is at tempPost');
[self nextTwentyFivePlease:tempPost.postID]; // function to load more results
}
} else {
NSInteger row = [indexPath row];
if (self.postDetailViewController == nil) {
PostDetailViewController *aPostDetail = [[PostDetailViewController alloc] initWithNibName:#"PostDetailView" bundle:nil];
self.postDetailViewController = aPostDetail;
postDetailViewController.post = [localJsonArray objectAtIndex:row];
[aPostDetail release];
}
postDetailViewController.title = [NSString stringWithFormat:#"Details"];
MackdabMobileAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[delegate.allMacksNavController pushViewController:postDetailViewController animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
pragma mark
pragma mark Timer set accessor methods
(void)setMinuteTimer:(NSTimer *)newTimer {
if (minuteTimer != newTimer) {
[minuteTimer invalidate];
minuteTimer = newTimer;
}
}
(void)setRegionsTimer:(NSTimer *)newTimer {
if (regionsTimer != newTimer) {
[regionsTimer invalidate];
regionsTimer = newTimer;
}
}
pragma mark
pragma mark Memory management
(void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
(void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
(void)dealloc {
[allPostsTableView release];
[postDetailViewController release];
[localJsonArray release]; // may need to remove this
[super dealloc];
}
#end
In your refresh method which is called in viewDidLoad, it appears that it is doing a bunch of network access to download some information. That would explain why it is taking so long to show your initial tab.
You'll want to do that network access in a background thread so that your UI doesn't block. Then once you've gotten the results back you can update your UI from the main thread. Something like:
- (IBAction) refresh {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self performSelectorInBackground:#selector(refreshInBackground) withObject:nil];
}
- (void) refreshInBackground {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
self.jsonArray = [Post findAllRemote];
if (self.localJsonArray == nil) {
self.localJsonArray = [[NSMutableArray alloc]init]; // init the local array if it’s empty
}
[self.localJsonArray addObjectsFromArray:self.jsonArray];
[self performSelectorOnMainThread:#selector(refreshComplete) withObject:nil waitUntilDone:NO];
}
- (void) refreshComplete {
[self.tableView reloadData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
EDIT: As Rob noted in the comment below, you'll want to add some kind of flag (or semaphore) to avoid firing off multiple -refresh calls at the same time. Threading and race conditions are always a tricky bit, but my goal above was just to get you moving in the right direction so that you can use background threads for your network calls.
I have a UIViewController (called AdjustViewController) that presents another UIViewController (called SourcePickerViewController) with a UIPickerView modally. I generate instances of the AdjustViewController and they in turn make a SourcePickerViewController. I make an NSDictionary and assign it and an integer to the AdjustViewController and it in turn sets the same properties in the SourcePickerController. This way I can reuse the controllers. The NSDictionary get set up in a UITableViewController that has all the AdjustViewControllers in it.
The problem comes when some of the pickers should have 1 component and some should have 2. The integer that I pass along is called numberOfComponents When I make a picker with numberOfComponents = 1 somehow it's changing to = 2 but I can't see how. I have NSLogs all over the place and I can see it happen as soon as the picker delegate method numberOfComponentsInPickerView is called. It's 1 right before and 2 right after.
There's obviously more code, but I think I have all the important parts. Although if that were true, maybe I'd know where the problem is!
Inside MenuViewController.m
- (void)viewDidLoad {
NSLog(#"ChemicalViewController launched");
self.title = #"Adjust Chemicals";
NSMutableArray *array = [[NSMutableArray alloc] init];
// Chlorine Controller
AdjustViewController *chlorineAdjustViewController = [[AdjustViewController alloc] initWithNibName:#"AdjustViewController" bundle:nil];
chlorineAdjustViewController.title = #"FC - Free Chlorine";
chlorineAdjustViewController.numberOfComponents = 2;
NSLog(#"Generating chlorine source dictionary");
NSDictionary *chlorineSourceDictionary = [self generateChlorineDictionary];
chlorineAdjustViewController.dictionaryOfSources = chlorineSourceDictionary;
[chlorineSourceDictionary release];
[array addObject:chlorineAdjustViewController];
[chlorineAdjustViewController release];
// CYA Controller
AdjustViewController *cyaAdjustViewController = [[AdjustViewController alloc] initWithNibName:#"AdjustViewController" bundle:nil];
cyaAdjustViewController.title = #"CYA - Cyanuric Acid";
cyaAdjustViewController.numberOfComponents = 1;
NSLog(#"Generating cya source dictionary");
NSDictionary *cyaSourceDictionary = [self generateCYADictionary];
cyaAdjustViewController.dictionaryOfSources = cyaSourceDictionary;
[cyaSourceDictionary release];
[array addObject:cyaAdjustViewController];
[cyaAdjustViewController release];
Inside AdjustViewController.m
// Present the picker for chlorine selection
- (IBAction)getChemicalSource {
SourcePickerViewController *sourcePickerViewController = [[SourcePickerViewController alloc] init];
sourcePickerViewController.delegate = self;
NSLog(#"getChemicalSource setting numberOfComponents %d", self.numberOfComponents);
sourcePickerViewController.numberOfComponents = self.numberOfComponents;
NSLog(#"getChemicalSource sending numberOfComponents %d", sourcePickerViewController.numberOfComponents);
sourcePickerViewController.dictionaryOfSources = self.dictionaryOfSources;
[self presentModalViewController:sourcePickerViewController animated:YES];
[sourcePickerViewController release];
}
#pragma mark -
#pragma mark Picker View Delegate Methods
// Returns the values from the picker if a source was chosen
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectSource:(NSString *)source
andConcentration:(NSString *)concentration
andConstant:(float)constant
andIsLiquid:(BOOL)isLiquid {
sourceField.text = [[NSString alloc] initWithFormat:#"%#, %#", source, concentration];
[self updateAdvice];
NSLog(#"Returned source = %#, concentration = %#, sourceConstant = %1.7f, isLiquid = %d", source, concentration, constant, isLiquid);
[self dismissModalViewControllerAnimated:YES];
}
// Returns from the picker without choosing a new source
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectCancel:(BOOL)didCancel {
[self updateAdvice];
NSLog(#"Returned without selecting source");
[self dismissModalViewControllerAnimated:YES];
}
Inside SourceViewController.m
- (void)viewDidLoad {
NSLog(#"SourcePickerViewController launched");
NSLog(#"viewDidLoad");
NSLog(#"Received numberOfComponents %d", self.numberOfComponents);
self.chemicalSources = dictionaryOfSources;
NSArray *components = [self.chemicalSources allKeys];
NSArray *sorted = [components sortedArrayUsingSelector:#selector(compare:)];
self.sources = sorted; // This array has the chemical sources
if (self.numberOfComponents = 2) {
NSString *selectedSource = [self.sources objectAtIndex:0];
NSArray *chemArray = [self.chemicalSources objectForKey:selectedSource];
NSMutableArray *concentrationArray = [[NSMutableArray alloc] init];
int num = [chemArray count];
for (int i=0; i<num; i++) {
[concentrationArray addObject:[[chemArray objectAtIndex:i] chemConcentration]];
}
self.concentrations = concentrationArray;
}
[super viewDidLoad];
}
#pragma mark -
#pragma mark Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
NSLog(#"numberOfComponentsInPickerView, self.numberOfComponents = %d", self.numberOfComponents);
return self.numberOfComponents;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
NSLog(#"numberOfRowsInComponent, self.numberOfComponents = %d", self.numberOfComponents);
if (component == kSourceComponent)
return [self.sources count];
return [self.concentrations count];
}
#pragma mark Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (component == kSourceComponent)
return [self.sources objectAtIndex:row];
return [self.concentrations objectAtIndex:row];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSLog(#"didSelectRow, self.numberOfComponents = %d", self.numberOfComponents);
if (numberOfComponents = 2) {
if (component == kSourceComponent) {
NSString *selectedSource = [self.sources objectAtIndex:row];
NSArray *chemArray = [self.chemicalSources objectForKey:selectedSource];
NSMutableArray *concentrationArray = [[NSMutableArray alloc] init];
int num = [chemArray count];
for (int i=0; i<num; i++) {
[concentrationArray addObject:[[chemArray objectAtIndex:i] chemConcentration]];
}
self.concentrations = concentrationArray;
[picker selectRow:0 inComponent:kConcentrationComponent animated:YES];
[picker reloadComponent:kConcentrationComponent];
}
}
}
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
if (component == kConcentrationComponent)
return 90;
return 205;
}
I didn't look through all of your code; Instead, I'd recommend writing out the properties for numberOfComponents instead of #synthesize'ing them. Just get rid of your #synthesize, and make:
- (int)numberOfComponents {
return m_numberOfComponents;
}
and
- (void)setNumberOfComponents(int aNumberOfComponents) {
m_numberOfComponents = aNumberOfComponents;
}
Then, set a breakpoint in your setNumberOfComponents function, and you should be able to see whenever it's getting called, so you can see what is going on. I hope that helps!