So I know the reason for this error, but I'm not really sure how to get around it.
I have a TextField that I want to not be editable, but with scrolling enabled inside a view.
This is how I set up that view:
- (void)tableView:(UITableView *) tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSDictionary *component = self.resultsTuples[indexPath.row];
NSString* serverUrl = #"http://staging.toovia.com";
NSString* pageLink = component[#"$element"][#"ContactCard"][#"PageLink"];
NSString* recUrl =[NSString stringWithFormat:#"%#%#&format=json&fields=summary&fields=session_info", serverUrl,pageLink];
[AJAXUtils getAsyncJsonWithUrl:(NSURL *)[NSURL URLWithString:recUrl] callback:^(NSDictionary *returnjson){
if (returnjson != nil){
NSLog(#"RETURN JSON : %#", returnjson);
NSString *userPageLink = returnjson[#"Node"][#"SessionInfo"][#"PostingAs"][#"Key"];
self.detailController.userPageLink = userPageLink;
self.detailController.nodePage = returnjson[#"Node"][#"Key"];
NSString *selectedCard = component[#"$element"][#"Title"];
// [self.detailController setDescription:component[#"element"][#"ContactCard"][#"Description"]];
[self.detailController setPageTitle:selectedCard];
NSString* photoUrl = component[#"$element"][#"ContactCard"][#"Cover"][#"Medium"][#"Link"];
[self.detailController setRestaurantImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:photoUrl]]]];
self.detailController.title = selectedCard;
NSString* rating = component[#"$element"][#"Summary"][#"AverageRating"];
self.detailController.rating =(NSInteger)rating;
[self.navigationController pushViewController:self.detailController animated:YES];
}
}];
This is the viewWillAppear code for the view:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear: animated];
self.rateView.notSelectedStar =[UIImage imageNamed:#"star-off.png"];
self.rateView.fullSelectedStar = [UIImage imageNamed:#"star-on.png"];
self.rateView.rating = self.rating;
self.rateView.editable = YES;
self.rateView.maxRating = 5;
self.rateView.delegate = self;
_pageTitleLabel.text = _pageTitle;
_pageScoreLabel.text = _pageScore;
_trendingImageView.image = [UIImage imageNamed:#"trending.png"];
_restaurantImageView.image = _restaurantImage;
}
The full error is -
2013-12-16 01:22:50.362 ReviewApp[7332:441f] *** Assertion failure in void _UIPerformResizeOfTextViewForTextContainer(NSLayoutManager *, UIView<NSTextContainerView> *, NSTextContainer *, NSUInteger)(), /SourceCache/UIFoundation_Sim/UIFoundation-258.1/UIFoundation/TextSystem/NSLayoutManager_Private.m:1510
2013-12-16 01:22:50.365 ReviewApp[7332:441f] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Only run on the main thread!'
So I guess it's happening because the resizing of the text field (when content is set) has to happen on the main thread. How do I force this to happen/suppress this error? I'm not in control of the text field resizing am I?
Thanks
I'm guessing that the callback from AJAXUtils happens on a background thread. I don't know if that is your code or not but you should do all UI operations (like setting text in a label, setting an image in an image view, pushing a view controller) on the main thread.
dispatch_sync(dispatch_get_main_queue(), ^{
/* Do UI work here */
});
Swift 3.0 version
DispatchQueue.main.async(execute: {
/* Do UI work here */
})
You must always perform UI code on the main thread. You are not, hence the error. Try this:
[AJAXUtils getAsyncJsonWithUrl:(NSURL *)[NSURL URLWithString:recUrl] callback:^(NSDictionary *returnjson){
if (returnjson != nil){
NSLog(#"RETURN JSON : %#", returnjson);
NSString *userPageLink = returnjson[#"Node"][#"SessionInfo"][#"PostingAs"][#"Key"];
self.detailController.userPageLink = userPageLink;
self.detailController.nodePage = returnjson[#"Node"][#"Key"];
NSString *selectedCard = component[#"$element"][#"Title"];
// [self.detailController setDescription:component[#"element"][#"ContactCard"][#"Description"]];
[self.detailController setPageTitle:selectedCard];
NSString* photoUrl = component[#"$element"][#"ContactCard"][#"Cover"][#"Medium"][#"Link"];
[self.detailController setRestaurantImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:photoUrl]]]];
self.detailController.title = selectedCard;
NSString* rating = component[#"$element"][#"Summary"][#"AverageRating"];
self.detailController.rating =(NSInteger)rating;
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController pushViewController:self.detailController animated:YES];
});
}
}];
This ensures the display of the view controller is on the main thread. You may actually need to put a bit more of the code inside the call to dispatch_async.
Swift version
dispatch_async(dispatch_get_main_queue()){
/* Do UI work here */
}
Also, it's not a very good way of doing things in didSelectRow. You should have a network layer, separate the network code from view controllers otherwise you will end up with huge view controller, code won't be testable.
Related
The CRASH is:
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.
It is clear that the problem is that every time the ui is changed must be done on the main thread. In theory this might work
dispatch_async(dispatch_get_main_queue(), {
// code here
})
My Function is:
- (void)requestInBlock:(NSString *)keyThumbnails withArrayQuantity:(NSMutableArray *)quantityArray andOriginalQuantity:(NSInteger)originalQuantity
{
int limit = 5;
NSMutableArray *arrayFive = [[NSMutableArray alloc] init];
NSInteger idQuantityOriginalCount = [quantityArray count];
for (NSInteger i = 0; i < MIN(limit, idQuantityOriginalCount); i ++) {
[arrayFive addObject:[quantityArray objectAtIndex:0]];
[quantityArray removeObjectAtIndex:0];
}
NSInteger idLimitArrayCount = [arrayFive count];
NSInteger __block countProductsRequestLimit = 0;
for (NSNumber *j in arrayFive) {
UIImageView * thumbnailImageView = [[UIImageView alloc] initWithFrame:CGRectMake(xposThumbnails, 0, ratio * 2, 47)];
[thumbnailsView addSubview:thumbnailImageView];
NSURL *imageUrl = [NSURL URLWithString:[NSString stringWithFormat:#"https://s3-us-west-2.amazonaws.com/xxxxxxxxxxxxxx/%#_%#.jpg",keyThumbnails,j]];
[Utils loadFromURL:imageUrl callback:^(UIImage *image) {
countProductsRequestLimit++;
countThumbnailsGlobal++;
[thumbnailImageView setImage:image];
[cutVideoScroll addSubview:thumbnailsView];
if (!image) {
NSMutableDictionary *collectInfoFailImage = [[NSMutableDictionary alloc] init];
[collectInfoFailImage setValue:thumbnailImageView forKey:#"image"];
[collectInfoFailImage setValue:imageUrl forKey:#"imageUrl"];
[thumbnailsWithError addObject:collectInfoFailImage];
collectInfoFailImage = nil;
}
if (countThumbnailsGlobal == originalQuantity) {
[arrayFive removeAllObjects];
[self performSelector:#selector(reloadThumbnailsWithError) withObject:nil afterDelay:3];
} else if (idLimitArrayCount == countProductsRequestLimit) {
[arrayFive removeAllObjects];
[self requestInBlock:keyThumbnails withArrayQuantity:quantityArray andOriginalQuantity:originalQuantity];
}
}];
xposThumbnails += (ratio * 2);
}
loadingTimeLine.layer.zPosition = -1;
lblloadingTimeLine.layer.zPosition = -1;
}
I think the mistake is happening here loadFromURL. (It is not sure about this, because in all devices with I do my tests, this never happens, someone outside informed me and sent me error logs)
My question is:
which part of this code may be modifying the autoLayout, maybe [cutVideoScroll addSubview:thumbnailsView];?
Why only occurs on an iPod?
UPDATE:
I'm testing with.
dispatch_async(dispatch_get_main_queue(), ^{
[thumbnailImageView setImage:image];
[cutVideoScroll addSubview:thumbnailsView];
});
But error persist.
Thanks for your time.
You got it right, adding subview to view trigger layoutSubviews. So you should addViews only in main thread.
Maybe you have some code specifically for iPhone / iPod in any method which you override?
It seems to be a combination of 2 things.
dispatch_async(dispatch_get_main_queue(), ^{
[thumbnailImageView setImage:image];
[cutVideoScroll addSubview:thumbnailsView];
});
and in all cases, he was added to thumbnailImageView the image regardless validation if (! image)
Does any body know what I need to check if app freezes after some time ? I mean, I can see the app in the iPhone screen but no view responds.
I did some google and i found that, i've blocked the main thread somehow.
But my question is how to identify which method causes blocking of main thread ? is there any way to identify ?
I using XMPP for chat and core data for storing and retrieving the messages.I connect the app with XMPP Server in app delegate class.But i don't know how XMPP Delegate call again and again. According to me core data doing its work fine but in the offline mode XMPP try to disconnect and until it does not complete its task it freeze the app for some time and then works fine in the offline mode.
But I am not sure what is the culprit.I am not able to find the issue.
And i tried the pause button too when it freeze but in the left window i am not able to find which method make the app freeze.
Here is code which i am using
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"IMChatMessageCell";
CMMessageArchiving_Message_CoreDataObject *msgInfo = (CMMessageArchiving_Message_CoreDataObject *)[[self messageFetchResultController] objectAtIndexPath:indexPath];
IMPromos *promo = nil;
if (msgInfo.promo_Object)
{
promo = msgInfo.promo_Object;
////NSLog(#"%#", promo);
}
else
{
promo = [IMDataHandler fetchPromoForPromoID:msgInfo.promo_id];
msgInfo.promo_Object = promo;
}
if (msgInfo.mediaThumbnailPath)
return [self mediaMessageCell:tableView indexPath:indexPath messageInfo: msgInfo];
IMChatMessageCell chatMessageCell = (IMChatMessageCell)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (chatMessageCell == nil)
{
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:nil options:nil];
for (NSObject *obj in objects)
{
if ([obj isKindOfClass:[IMChatMessageCell class]])
{
chatMessageCell = (IMChatMessageCell *)obj;
break;
}
}
}
int imageWith;
NSString *message = [msgInfo body];
if (NO == msgInfo.isOutgoing)// left aligned
{
// Just set the textfield here ///
NSString* promoImage = [[IMUtils getImageFolderPath] stringByAppendingPathComponent: [chatMessageCell.cellPromo.promo_image lastPathComponent]];
//NSLog(#"%#",promoImage);
UIImage *image = [UIImage imageWithContentsOfFile:promoImage];
if (image)
{
chatMessageCell.userImageView.image = image;
if(!promo.promo_message.length)
{
chatMessageCell.userImageView.frame = CGRectIntegral(CGRectMake(padding, 15, image.size.width,image.size.height));
bubbleItemOrigin=USER_IMAGE_Y_POS_WITHOUT_HEADER;
if(!promo.promo_link.length)
bubbleItemOrigin-=5;
}
else
{
chatMessageCell.userImageView.frame = CGRectIntegral(CGRectMake(padding, USER_IMAGE_Y_POS, image.size.width, image.size.height));
bubbleItemOrigin=USER_IMAGE_Y_POS;
}
}
return chatMessageCell;
}
(void)viewWillAppear:(BOOL)animated
{
[self scrollToBottom];
[super viewWillAppear:animated];
[self refreshTitleView];
messagesFetchResultController.delegate = self;
messageTextField.font = [IMUtils appFontWithSize:font_size];
}
I am using this code.
When i am running the app without network then it freeze
and something like this printing into logs
and XMPP'S delegate get called.
Im fetching data from database through a php-API with this code:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadDataWithSpinner];
[self reloadAllData];
}
- (void) loadDataWithSpinner {
if (!self.spinner) [self setupSpinnerView];
self.sessions = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
self.userId = [[NSUserDefaults standardUserDefaults] integerForKey:#"CurrentUserId"] ;
self.sessions = [self getAllSessionsForUserId:self.userId];
dispatch_async(dispatch_get_main_queue(), ^(void){
if (self.sessions) {
self.spinnerBgView.hidden = YES;
[self setupChartsAndCountingLabelsWithData];
}
});
});
}
- (NSArray *) getAllSessionsForUserId: (int) userId {
NSData *dataURL = nil;
NSString *strURL = [NSString stringWithFormat:#"http://webapiurl.com/be.php?queryType=fetchAllBasicSessions&user_id=%i", userId];
dataURL = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
NSError *error = nil;
if (dataURL) {
NSArray *sessions = [NSJSONSerialization JSONObjectWithData:dataURL options:kNilOptions error:&error];
return sessions;
} else {
return Nil;
}
}
Is there something wrong with the code? I'm getting the correct data when testing in a web-browser with the same database call. But in the app it sometimes updates straight away, as it should, and sometimes it takes up to five minutes for the data to update. Even though i remove the app from the phone/simulator, the data sometime hasn't been update when opening the app again.
I would really appreciate some help.
I finally found the answer. Its nothing wrong with my code, as I thought. The problem lies on the server side. And, as they say in this thread:
NSURLConnection is returning old data
It seems to be badly configured server-side or proxy. Which makes the cache act all wierd. I tried another server and everything worked just fine!
Hope this helps someone with the same issue, cause it sure wasted me ALOT of time.
I'm using Nico Kreipke's FTPManager (click here to go to GiHub) to download some data from an FTP address.
The code works if it's run before the user's first interaction, after that it will usually fail (about 9 out of 10).
When it fails, the following message is written (0x_ are actually valid addresses):
request (0x_) other than the current request(0x0) signalled it was complete on connection 0x_
That message isn't written by neither my code nor by FTPManager, but by Apple's. On its GitHub, I've found some one with the same error, but the source of it could possible be the same as mine. (That person wasn't using ARC.)
If I try to print the objects of those addresses with the pocommand, the console writes that there's no description available.
Also, the memory keeps adding up until the app receives a memory warning, and soon after the OS terminates it.
By pausing the app when that message appears, I can see that the main thread is in a run loop.
CFRunLoopRun();
The Code
self.ftpManager = [[FTPManager alloc] init];
[self downloadFTPFiles:#"192.168.2.1/sda1/1668"];
ftpManageris a strong reference.
The downloadFTPFiles: method:
- (void) downloadFTPFiles:(NSString*) basePath
{
NSLog(#"Reading contents of path: %#", basePath);
FMServer* server = [FMServer serverWithDestination: basePath username:#"test" password:#"test"];
NSArray* serverData = [self.ftpManager contentsOfServer:server];
NSLog(#"Number of items: %d", serverData.count);
for(int i=0; i < serverData.count; i++)
{
NSDictionary * sDataI = serverData[i];
NSString* name = [sDataI objectForKey:(id)kCFFTPResourceName];
NSNumber* type = [sDataI objectForKey:(id)kCFFTPResourceType];
if([type intValue] == 4)
{
NSLog(#"%# is Folder", name);
NSString * nextDestination = [basePath stringByAppendingPathComponent: name];
[self downloadFTPFiles:nextDestination];
}
else
{
NSLog(#"%# is File", name);
[self.ftpManager downloadFile:name toDirectory:[NSURL fileURLWithPath:NSHomeDirectory()] fromServer:server];
}
}
}
What I've Done
I've tried running that code on several places:
The app delegate's application:didFinishLaunchingWithOptions:;
The viewDidLoad, viewWillAppear: and viewDidAppear: of the a view controller loaded just after the app launches and a view controller presented later.
By an action triggered with a button event.
The download of the data is always well performed when executed by the delegate or a view controller loaded with the app (with an exception). But when run after the user's first interaction with the app, it'll most likely fail with the mentioned error.
The exception for view controllers loaded before the user's first interaction is when the call is in either the viewWillAppear: or viewDidAppear: methods. When it's called a second time (for example, a tab of a tab bar controller) it'll also, most likely, fail.
The Question
Does anyone have an idea of what may be happening, or if I'm doing something wrong? Or any alternative solution, maybe?
Any help to solve this problem will be welcomed.
Thanks,
Tiago
I ended up sending the downloadFile:toDirectory:fromServer: message inside a dispatch_async block. I've also created an FTPManage for every file downloaded.
It worked, but I have no idea why.
I'm leaving this answer to whomever crosses with this problem.
If anyone can let me know why this technique worked, please comment bellow so I can update the answer.
Here's the new way I'm downloading each file:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FTPManager *manager = [[FTPManager alloc] init];
[manager downloadFile:name toDirectory:[NSURL fileURLWithPath:path] fromServer:server];
});
Again, If you know why this worked, let me know.
Thanks.
Full Method
- (void) downloadFTPFiles:(NSString*) basePath
{
NSLog(#"Reading contents of path: %#", basePath);
FMServer *server = [FMServer serverWithDestination:basePath username:#"test" password:#"test"];
NSArray *serverData = [self.ftpManager contentsOfServer:server];
NSLog(#"Number of items: %d", serverData.count);
for(int i=0; i < serverData.count; i++)
{
NSDictionary *sDataI = serverData[i];
NSString *name = [sDataI objectForKey:(id)kCFFTPResourceName];
NSNumber *type = [sDataI objectForKey:(id)kCFFTPResourceType];
if([type intValue] == 4)
{
NSLog(#"%# is Folder", name);
NSString *nextDestination = [basePath stringByAppendingPathComponent:name];
[self downloadFTPFiles:nextDestination];
}
else
{
NSLog(#"%# is File", name);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FTPManager *manager = [[FTPManager alloc] init];
[manager downloadFile:name toDirectory:[NSURL fileURLWithPath:path] fromServer:server];
});
}
}
}
I have created a custom UITableViewCell which is composed by 2 UILabels and a single UIImageView.
Data associated with cells is available with a NSObject class named CellInfo. CellInfo has 2 properties of NSString type and an UIImage property.
When I create a CellInfo instance, inside the initWithData method (CellInfo class), I do the following:
if(self = [super alloc])
{
//initialize strings variables
self.name = aName;
self.descritpion = aDescription;
[self grabImage]
}
return self;
where grabImage (within CellInfo class) using ASIHTTPrequest framework to grab images in asynchronous manner (in the following code NSURL is alaways the same but in reality it changes with data)
- (void)grabImage
{
NSURL *url = [NSURL URLWithString:#"http://myurl.com/img.png"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSData *data = [request responseData];
UIImage* img = [[UIImage alloc] initWithData:data];
self.image = img;
[img release];
// Send a notification if image has been downloaded
[[NSNotificationCenter defaultCenter] postNotificationName:#"imageupdated" object:self];
}];
[request setFailedBlock:^{
NSError *error = [request error];
// Set default image to self.image property of CellInfo class
}];
[request startAsynchronous];
}
I have also a UITableViewController that loads data into the custom cell like the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do stuff here...
// Configure the cell...
((CustomTableViewCell*)cell).nameOutlet.text = ((CellInfo*) [self.infoArray objectAtIndex:indexPath.row]).name;
((CustomTableViewCell*)cell).descriptionOutlet.text = ((CellInfo*) [self.infoArray objectAtIndex:indexPath.row]).descritpion;
((CustomTableViewCell*)cell).imageViewOutlet.image = ((CellInfo*) [self.infoArray objectAtIndex:indexPath.row]).image;
return cell;
}
In addiction, this UITableViewController observes notification from the CellInfo class because, at start up, images for visible cells are not displayed. This is the method that is called when the notification is captured:
- (void)imageUpdated:(NSNotification *)notif {
CellInfo * cInfo = [notif object];
int row = [self.infoArray indexOfObject:cInfo];
NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:0];
NSLog(#"Image for row %d updated!", row);
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
The code works well, but I would like to know if I'm doing right or there is a better way to do this.
My doubt is the following: is it correct to save downloaded images within each CellInfo instance or is it possible to follow another way to cache images using, for example, cache policy provided by ASIHTTPRequest?
P.S. grabImage is not called if the image for a specific CellInfo instance has already been downloaded.
I believe that's pretty neat. Instead of that you might subclass UIImageView class and create an initializer like [AsyncUIImageView initWithURL:] and then put that ASIHttpRequest logic inside the view.
After it finishes loading the picture, there could be two ways:
It can call [self setNeedsDisplay] (an UIView method) so image view is redrawn.
You can pass UITableViewCell or UITableView as a delegate to AsyncUIImgeView so that it could tell table view to reload that cell.