From b2e164c9420711f01779db9907be12e545b6b9f9 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Wed, 2 Feb 2011 10:59:21 -0800 Subject: [PATCH] Fixed re-load view hierarchy bug. This closes issue #23 on GitHub. The issue was related to how we re-construct the view hierarchy after a low memory warning from the system. If the user had changed to a month different than the initial month when KalViewController was created, it would fail to find the appropriate KalTileView for the value that it had stored as the initially selected date. The solution is to re-cache the initially selected date when a low memory warning occurs. Original bug report by jgchristian: Steps to reproduce: 1. Start your Holiday app 2. Slide to the next month (i.e. not the current month) 3. Select an event and view the holiday details 4. When on the details screen, quit to the springboard by single clicking the home button (not double clicking to background). We need to force the device to want to reload the grid when we return - so you may need to do some memory intensive stuff - but not generally the case for me. 5. Relaunch the Holiday app. This returns you to the details screen 6. Now press back to return to the grid. Crash log below From the looks of the code this is happening because the 'logic' object is still on the next month (correct), but when loadView is triggered by the OS trying to re-instantiate the view it defaults to setting the current date on the grid - which was in the previous month so assertion fails as cell is nil. Thought about maintaining last user-selected date but started to get fiddly. 2011-02-02 08:20:24.961 Holiday[3471:307] Received memory warning. Level=2 2011-02-02 08:20:28.382 Holiday[3471:307] Assertion failure in -[KalMonthView tileForDate:], /Users/j.christian/dev/Kal/src/KalMonthView.m:84 2011-02-02 08:20:28.510 Holiday[3471:307] Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to find corresponding tile for date 2/2/2011' --- .../Holiday/Holiday.xcodeproj/project.pbxproj | 4 +-- .../NativeCal.xcodeproj/project.pbxproj | 4 +-- src/Kal.xcodeproj/project.pbxproj | 4 +-- src/KalViewController.h | 5 +-- src/KalViewController.m | 33 ++++++++++++++----- 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/Examples/Holiday/Holiday.xcodeproj/project.pbxproj b/Examples/Holiday/Holiday.xcodeproj/project.pbxproj index 732f7e0..d379ce1 100755 --- a/Examples/Holiday/Holiday.xcodeproj/project.pbxproj +++ b/Examples/Holiday/Holiday.xcodeproj/project.pbxproj @@ -353,7 +353,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 3.1; OTHER_LDFLAGS = "-all_load"; PREBINDING = NO; - SDKROOT = iphoneos4.1; + SDKROOT = iphoneos; USER_HEADER_SEARCH_PATHS = ". ../../src"; }; name = Debug; @@ -370,7 +370,7 @@ OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; OTHER_LDFLAGS = "-all_load"; PREBINDING = NO; - SDKROOT = iphoneos4.1; + SDKROOT = iphoneos; USER_HEADER_SEARCH_PATHS = ". ../../src"; }; name = Release; diff --git a/Examples/NativeCal/NativeCal.xcodeproj/project.pbxproj b/Examples/NativeCal/NativeCal.xcodeproj/project.pbxproj index 667b1b8..ea576b1 100755 --- a/Examples/NativeCal/NativeCal.xcodeproj/project.pbxproj +++ b/Examples/NativeCal/NativeCal.xcodeproj/project.pbxproj @@ -286,7 +286,7 @@ ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-all_load"; PREBINDING = NO; - SDKROOT = iphoneos4.1; + SDKROOT = iphoneos; }; name = Debug; }; @@ -302,7 +302,7 @@ OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; OTHER_LDFLAGS = "-all_load"; PREBINDING = NO; - SDKROOT = iphoneos4.1; + SDKROOT = iphoneos; }; name = Release; }; diff --git a/src/Kal.xcodeproj/project.pbxproj b/src/Kal.xcodeproj/project.pbxproj index a60c98c..22306ab 100644 --- a/src/Kal.xcodeproj/project.pbxproj +++ b/src/Kal.xcodeproj/project.pbxproj @@ -279,7 +279,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 3.1; OTHER_LDFLAGS = "-ObjC"; PREBINDING = NO; - SDKROOT = iphoneos4.1; + SDKROOT = iphoneos; }; name = Debug; }; @@ -293,7 +293,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 3.1; OTHER_LDFLAGS = "-ObjC"; PREBINDING = NO; - SDKROOT = iphoneos4.1; + SDKROOT = iphoneos; }; name = Release; }; diff --git a/src/KalViewController.h b/src/KalViewController.h index 26fb154..ba9ddb9 100644 --- a/src/KalViewController.h +++ b/src/KalViewController.h @@ -26,15 +26,16 @@ UITableView *tableView; id delegate; id dataSource; - NSDate *initialSelectedDate; + NSDate *initialDate; // The date that the calendar was initialized with *or* the currently selected date when the view hierarchy was torn down in order to satisfy a low memory warning. + NSDate *selectedDate; // I cache the selected date because when we respond to a memory warning, we cannot rely on the view hierarchy still being alive, and thus we cannot always derive the selected date from KalView's selectedDate property. } @property (nonatomic, assign) id delegate; @property (nonatomic, assign) id dataSource; +@property (nonatomic, retain, readonly) NSDate *selectedDate; - (id)initWithSelectedDate:(NSDate *)selectedDate; // designated initializer. When the calendar is first displayed to the user, the month that contains 'selectedDate' will be shown and the corresponding tile for 'selectedDate' will be automatically selected. - (void)reloadData; // If you change the KalDataSource after the KalViewController has already been displayed to the user, you must call this method in order for the view to reflect the new data. - (void)showAndSelectDate:(NSDate *)date; // Updates the state of the calendar to display the specified date's month and selects the tile for that date. -- (NSDate *)selectedDate; // The currently selected date. You should only use the actual date components from this NSDate object. The hours/minutes/seconds/timezones are undefined. @end diff --git a/src/KalViewController.m b/src/KalViewController.m index 09930f7..62245b8 100644 --- a/src/KalViewController.m +++ b/src/KalViewController.m @@ -31,18 +31,21 @@ void mach_absolute_difference(uint64_t end, uint64_t start, struct timespec *tp) NSString *const KalDataSourceChangedNotification = @"KalDataSourceChangedNotification"; @interface KalViewController () +@property (nonatomic, retain, readwrite) NSDate *initialDate; +@property (nonatomic, retain, readwrite) NSDate *selectedDate; - (KalView*)calendarView; @end @implementation KalViewController -@synthesize dataSource, delegate; +@synthesize dataSource, delegate, initialDate, selectedDate; -- (id)initWithSelectedDate:(NSDate *)selectedDate +- (id)initWithSelectedDate:(NSDate *)date { if ((self = [super init])) { - logic = [[KalLogic alloc] initForDate:selectedDate]; - initialSelectedDate = [selectedDate retain]; + logic = [[KalLogic alloc] initForDate:date]; + self.initialDate = date; + self.selectedDate = date; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(significantTimeChangeOccurred) name:UIApplicationSignificantTimeChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadData) name:KalDataSourceChangedNotification object:nil]; } @@ -94,6 +97,7 @@ - (void)significantTimeChangeOccurred - (void)didSelectDate:(KalDate *)date { + self.selectedDate = [date NSDate]; NSDate *from = [[date NSDate] cc_dateByMovingToBeginningOfDay]; NSDate *to = [[date NSDate] cc_dateByMovingToEndOfDay]; [self clearTable]; @@ -169,21 +173,33 @@ - (NSDate *)selectedDate // ----------------------------------------------------------------------------------- #pragma mark UIViewController +- (void)didReceiveMemoryWarning +{ + self.initialDate = self.selectedDate; // must be done before calling super + [super didReceiveMemoryWarning]; +} + - (void)loadView { if (!self.title) self.title = @"Calendar"; - KalView *kalView = [[KalView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] delegate:self logic:logic]; + KalView *kalView = [[[KalView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] delegate:self logic:logic] autorelease]; self.view = kalView; tableView = kalView.tableView; tableView.dataSource = dataSource; tableView.delegate = delegate; [tableView retain]; - [kalView selectDate:[KalDate dateFromNSDate:initialSelectedDate]]; - [kalView release]; + [kalView selectDate:[KalDate dateFromNSDate:self.initialDate]]; [self reloadData]; } +- (void)viewDidUnload +{ + [super viewDidUnload]; + [tableView release]; + tableView = nil; +} + - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; @@ -202,7 +218,8 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationSignificantTimeChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:KalDataSourceChangedNotification object:nil]; - [initialSelectedDate release]; + [initialDate release]; + [selectedDate release]; [logic release]; [tableView release]; [super dealloc];