I implemented UIRefreshControl on a Table View inside a UITableViewController. Today my friend (first tester) was playing with the app and he was able to make crash by pulling to refresh and while it's refreshing, pulling on it again very quickly a couple of times. I know it's not a common thing to do but I can recreate it very easily. I'm guessing there's something wrong with the way I'm implementing the refresh control since I tried to recreate it with the Gmail app and it didn't crash.
This is the error I get:
[1008:907] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSCFConstantString stringByAppendingString:]: nil argument'
* First throw call stack:
(0x343f32a3 0x3c08d97f 0x343f31c5 0x34c5c5e1 0x496b5 0x3624654d 0x3622b313 0x362427cf 0x361fe803 0x35fa8d8b 0x35fa8929 0x35fa985d 0x35fa9243 0x35fa9051 0x35fa8eb1 0x343c86cd 0x343c69c1 0x343c6d17 0x34339ebd 0x34339d49 0x37efb2eb 0x3624f301 0x46da5 0x3c4c4b20)
libc++abi.dylib: terminate called throwing an exception
(lldb)
Under ViewDidLoad
// pull to refresh
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(updateTableView:) forControlEvents:UIControlEventValueChanged];
[self setRefreshControl:refreshControl];
And this is the updateTableView method:
- (void)updateTableView:(id)sender {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, h:mm:ss a"];
NSString *lastUpdated = [NSString stringWithFormat:#"Last updated on %#", [formatter stringFromDate:[NSDate date]]];
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:lastUpdated];
if(topOptions.selectedSegmentIndex == 0) {
//fetch the feed
_feed = [[FetchData alloc] initFromURLWithString:#"some json file" completion:^(JSONModel *model, JSONModelError *err) {
[self.tableView reloadData];
[(UIRefreshControl *)sender endRefreshing];
}];
} else if (topOptions.selectedSegmentIndex == 1) {
//fetch the feed
_feed = [[FetchData alloc] initFromURLWithString:#"another json file" completion:^(JSONModel *model, JSONModelError *err) {
[self.tableView reloadData];
[(UIRefreshControl *)sender endRefreshing];
}];
}
}
I'm guessing (from the nil argument) that by doing the refresh request many times it reaches a point that it can't find a value? I'm kind of new at this so any idea is really appreciated.
If you need any other part of the code let me know.
Related
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.mediaPlaybackRequiresUserAction = NO;
config.allowsInlineMediaPlayback = YES;
config.preferences.javaScriptEnabled=true;
config.allowsAirPlayForMediaPlayback = YES;
//Create new UIWebView for launched-app
WKWebView *appWebView = [[WKWebView new] initWithFrame:portalSizes configuration:config];
appWebView.navigationDelegate = self;
appWebView.UIDelegate=self;
appWebView.allowsLinkPreview=false;
appWebView.tag = WebView_Tag + launchedApps.count;
[appWebView setFrame:CGRectMake(appSize.width * launchedApps.count, 0, appSize.width, appSize.height)];
[appWebView setNavigationDelegate:self];
//[appWebView setAllowsInlineMediaPlayback:YES];
appWebView.scrollView.tag = ScrollView_Tag + launchedApps.count;
appWebView.scrollView.delegate = self;
[appWebView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM _webView:renderingProgressDidChange:]: unrecognized selector sent to instance 0x28254cab0'
terminating with uncaught exception of type NSException
The initialisation of variable appWebView is incorrect. [WKWebView new] is shortcut for [[WKWebView alloc] init] so your are potentially initialising the control twice.
Replace [[WKWebView new] initWithFrame:portalSizes configuration:config]
with [[WKWebView alloc] initWithFrame:portalSizes configuration:config] and check if this helps to prevent your code from crashing.
(Also using the tag property of a control to store the sum of a variable and a counter is a bit weird, why not store this all in a regular variable?)
I have the following situation. I developed an app that frequently retrieves values for POI's that I want to show as annotations on a map. For this I wrote the method below, called after when a new set of POIs was retrieved:
-(void)showAnnotation{
[self removeAllPinsButUserLocation];
annotationArray = [[NSMutableArray alloc] init];
for (Poi *poi in [parser.pois allValues]) {
myAnnotation *arr = [[myAnnotation alloc] init];
arr.title = [NSString stringWithFormat:#"%# (%#)",poi.description, sensor.name];
arr.subtitle = [NSString stringWithFormat:#"%#, %# %#",poi.street, poi.postalcode, poi.city];
arr.coordinate = CLLocationCoordinate2DMake(poi.lattitude, poi.longitude);
[annotationArray addObject:arr];
arr = nil;
}
[self.mapView addAnnotations:annotationArray];
[self.mapView showAnnotations:annotationArray animated:NO];
}
Problem is that I get an error (Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSArrayM: 0x14ebe9b0> was mutated while being enumerated.')
However only if I set animated:NO in the last line, but not when set to animated:YES...
Does anyone have an answer for this?
Thanks for replying!
Eelco
I am trying to fetch events from a specific calendar only. On my iPhone I added a calendar "CalendarTest". What I want to do is in my app only fetch the events from that Calendar. I am doing the following.
self.defaultCalendar = [eventStore calendarWithIdentifier:#"CalendarTest"];
NSArray *calendarArray = [NSArray arrayWithObject:defaultCalendar];
// asynchronous callback on the main thread
[events removeAllObjects];
NSLog(#"Fetching events from EventKit between %# and %# on a GCD-managed background thread...", fromDate, toDate);
dispatch_async(eventStoreQueue, ^{
NSDate *fetchProfilerStart = [NSDate date];
NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:fromDate endDate:toDate calendars:calendarArray];
NSArray *matchedEvents = [eventStore eventsMatchingPredicate:predicate];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Fetched %d events in %f seconds", [matchedEvents count], -1.f * [fetchProfilerStart timeIntervalSinceNow]);
[events addObjectsFromArray:matchedEvents];
[delegate loadedDataSource:self];
});
});
But when I build and run I get the following error it crashes with the following error.
2012-11-20 11:45:35.445 CalendarApp[2685:1d03] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'
Anybody has an idea? Kind regards!
I think you should add self.defaultCalendar to your calendarArray. That might do the trick.
I have a viewController in an app that retrieves Data from JSON, parses it and populates in UITableView. I am using thread to load data so that app does not hang when it is retrieving data.
Problem:
numberOfRowsInSection returns 0 and UITableView does not get populated sometimes when app is started. While sometimes, everything works fine. It is all random :S
Possible Explanation:
The problem is, it seems like, sometimes numberOfRowsInSection is called before data is retrieved. numberOfRowsInSection returns the value of count of a NSMutableArray called 'subjects'. Objects in 'subjects' are added when loadData is called. So the numberOfRowsInSection should return the count of 'subjects' and it should not be called after 'subjects' is populated.
Sometimes when I start the app, numberOfRowsInSection is called after 'subjects' is populated and UITableView shows data but sometimes when I start the app, numberOfRowsInSection is called before 'subjects' is populated and UITableView shows no data.
Code:
Here is my code:
-(void)loadData:(id)sender
{
dispatch_queue_t getRemindersQueue=dispatch_queue_create("reminders JSON downloader with reload Button", NULL);
dispatch_async(getRemindersQueue, ^{
[self getReminders];
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationItem.rightBarButtonItem=sender;
[self.tableView reloadData];
});
});
dispatch_release(getRemindersQueue);
}
-(void)getReminders
{
NSURL * aURL = [NSURL URLWithString: #"http://www.merrycode.com/apps/IELTS/RemindersJSON"];
NSURLRequest *request = [NSURLRequest requestWithURL:aURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
NSError *responseError=nil;
// Perform request and get JSON back as a NSData object
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&responseError];
if(responseError)
{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *parsingError = [[UIAlertView alloc] initWithTitle:#"Network Error"
message:#"Can not reach the servers. Make sure you are connected to the internet."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[parsingError show];
});
return;
}
NSString *str = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSLog(#" String of Reminders JSON: %#",str);
NSString *newStr= [self stringByRemovingControlCharacters:str];
response = [newStr dataUsingEncoding:NSUTF8StringEncoding];
NSError *jsonParsingError = nil;
NSArray *publicTimeline = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonParsingError];
NSLog(#"%#", jsonParsingError);
NSLog(#" publicTimeline Array Count: %d", [publicTimeline count]);
if([publicTimeline count] == 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *parsingError = [[UIAlertView alloc] initWithTitle:#"Error Retriving Data"
message:#"There was an error reciving data from the server."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[parsingError show];
});
return;
}
NSDictionary *colleges;
for(int i=0; i<[publicTimeline count];i++)
{
colleges= [publicTimeline objectAtIndex:i];
NSLog(#"Reminders: %#", [colleges objectForKey:#"title"]);
[self.subjects addObject:[colleges objectForKey:#"title"]];
[self.dates addObject:[colleges objectForKey:#"date"]];
[self.description addObject:[colleges objectForKey:#"desc"]];
}
[self.subjectsInNSUserDefaults removeAllObjects];
[self.datesInNSUserDefaults removeAllObjects];
[self.descriptionInNSUserDefaults removeAllObjects];
[self.userDefaults setObject:self.subjects forKey:#"SUBJECTS"];
[self.userDefaults setObject:self.dates forKey:#"DATES"];
[self.userDefaults setObject:self.description forKey:#"DESCRIPTION"];
[self.userDefaults synchronize];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"Array Count in numberOfRowsInSection: %d",[self.subjects count]);
return [self.subjects count];
}
Couple of ideas...
You said this is a UIViewController (as opposed to UITableViewController). How are you setting up your UITableView and the delegate? If you are still setting up your UITableView or its delegates while this background thread is running, it is theoretically possible that the background thread could complete before you are done setting up your UITableView, which could create strange issues (and explain why this happens "randomly").
Also, have you checked to make sure your response object is populated with information about colleges in the cases where your UITableView isn't getting populated (and not some sort of other response, or no response at all)? I see where you are checking for response errors, but you seem to assume that if there isn't an error, it will be a response with information about colleges (which may or may not be a safe assumption).
If you're correct about the data retrieval being the problem, then I have also had this problem. My inelegant solution was just to set a timer to populate the table at a time when I knew the data would be loaded.
I'm trying to fetch EKEvents from the Event Store to populate a UITableView and display a month list view.
Basically it works and I'm doing it like this:
- (void) reloadEvents
{
for ( NSString *entry in self.calendarA )
{
NSMutableArray *tempArray = [[[NSMutableArray alloc] init] autorelease];
[tempArray addObjectsFromArray:[appDelegate.eventStore eventsMatchingPredicate:[appDelegate.eventStore predicateForEventsWithStartDate:[NSDate fromString:entry] endDate:[[NSDate fromString:entry] midnight] calendars:nil]]];
[tempArray addObjectsFromArray:[self initializeItems:[NSDate fromString:entry] withEndDate:[[NSDate fromString:entry] midnight]]];
[tempArray sortUsingDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"startDate" ascending:YES] autorelease]]];
[[self.calendarD objectForKey:entry] addObjectsFromArray:tempArray];
}
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self redrawTableCells];
});
}
reloadEvents is called from within a
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
[self reloadEvents];
});
as fetching events happens synchronously and it locks the UI for that time, I'm using GCD. The NSDate parts are my own categories on NSDate.
Now, when my view controller loads, events are fetched from the Event Store and displayed correctly. The view controller also listens for EKEventStoreChangedNotification and that's where my app crashes. When I change an event outside my app it receives a notification and tries to reload the event data, but then...
*** -[CFString length]: message sent to deallocated instance 0x666f530
EDIT
I've changed reloadEvents to the following:
- (void) reloadEvents
{
NSArray *daysArray = [[self.calendarD allKeys] sortedArrayUsingSelector:#selector(compare:)];
for ( NSString *entry in daysArray )
{
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
[tempArray addObjectsFromArray:[appDelegate.eventStore eventsMatchingPredicate:[appDelegate.eventStore predicateForEventsWithStartDate:[NSDate fromString:entry] endDate:[[NSDate fromString:entry] midnight] calendars:nil]]];
[tempArray addObjectsFromArray:[self initializeItems:[NSDate fromString:entry] withEndDate:[[NSDate fromString:entry] midnight]]];
[tempArray sortUsingDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"startDate" ascending:YES] autorelease]]];
[[self.calendarD objectForKey:entry] addObjectsFromArray:tempArray];
[tempArray release];
}
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self redrawTableCells];
});
}
and with this, the app doesn't crash anymore.
Seems like something changed calendarA and therefore the entry was already deallocated (which, after having found the cause of the problem, is absolutely logical).