PDA

View Full Version : I can't change the text of a UILabel or the image in an ImageView, in a children subV




aneuryzma
May 9, 2011, 06:00 AM
I can't change the text of a UILabel or the image in an ImageView, in a children subView. (I don't get any exception, but I just see the original content as specified in UIBuilder).

The parent is EventsTableViewController:


- (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] autorelease];
}

NSArray *photoIds = [[NSArray alloc] initWithObjects:@"2654899252",@"2654072649",@"2654072293",nil];

NSEnumerator *e = [photoIds objectEnumerator];
id object;
CGFloat offset = 0.0;
while ((object = [e nextObject])) {

ImageWithCaptionView* imageWithCaptionView = [[[ImageWithCaptionView alloc] init] retain];

offset += 320.0;

UIGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];

[(UITapGestureRecognizer *)recognizer setNumberOfTouchesRequired:1];

imageWithCaptionView.userInteractionEnabled = YES;
[imageWithCaptionView addGestureRecognizer:recognizer];
recognizer.delegate = self;
[recognizer release];

dispatch_queue_t downloadQueue = dispatch_queue_create("Flickr downloader in EventsTableView", NULL);

dispatch_async(downloadQueue, ^{

NSData *image = [FlickrFetcher imageDataFromPhotoId:object];

if(image) {

//imageWithCaptionView.imageView.image = [UIImage imageWithData: image];

imageWithCaptionView.imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData: image]];

}
});

dispatch_release(downloadQueue);

[imageWithCaptionView.label setText:@"testBALBALBAL"];

[cell addSubview:imageWithCaptionView];
[imageWithCaptionView release];

}

[photoIds release];

return cell;
}



The subview ImageWithCaptionView:

@interface ImageWithCaptionView : UIView {
IBOutlet UIImageView *imageView;
IBOutlet UILabel *label;
}

@property (retain) IBOutlet UIImageView *imageView;
@property (retain) IBOutlet UILabel *label;


and

@implementation ImageWithCaptionView

@synthesize imageView, label;

-(id)init
{
UINib *nib = [[UINib nibWithNibName:NSStringFromClass([ImageWithCaptionView class]) bundle:nil] retain];

NSArray *myArray = [nib instantiateWithOwner:self options:nil];

for (id currentObject in myArray) {
if ([currentObject isKindOfClass:[ImageWithCaptionView class]]) {
self = (ImageWithCaptionView *) currentObject;
break;
}
}

[nib release];
return self;
}

thanks



jnoxx
May 9, 2011, 06:13 AM
Without reading through you're code. just from ur title.
you're doing a setMethod?
if it's a property, then u should be able to do
[whateverview setImageView:UIImagepointer];

aneuryzma
May 9, 2011, 06:17 AM
Yeah, but I actually only need to change the text in the UILabel rather than creating a new UILAbel (and having to define a new frame...etc).

Should I add a property for NSString text ? And then, how it is related to the property ?

thanks

chown33
May 9, 2011, 12:11 PM
@implementation ImageWithCaptionView

@synthesize imageView, label;

-(id)init
{
UINib *nib = [[UINib nibWithNibName:NSStringFromClass([ImageWithCaptionView class]) bundle:nil] retain];

NSArray *myArray = [nib instantiateWithOwner:self options:nil];

for (id currentObject in myArray) {
if ([currentObject isKindOfClass:[ImageWithCaptionView class]]) {
self = (ImageWithCaptionView *) currentObject;
break;
}
}

[nib release];
return self;
}

This -init method never invokes self = [super init];

This pattern of searching for an alternate self objects seems very strange to me. Where did you see it, and how do you know it works?

It seems to be some kind of singleton trickery, but it's not a pattern I'm familiar with, so if there's a reference that explains it, please point to it. If it's something you devised yourself, then please explain what it's intended to do.

If this is a singleton pattern, then it won't work, because you can't have a single UIView shared by multiple parent views. Adding any view as a subview of another view automatically removes it from any original parent view.

aneuryzma
May 9, 2011, 12:17 PM
This is not a singleton pattern.

I want to build a grid.

I have a UITableView with several rows and for each row I want to load 3 ImageWithCaptionView.

An ImageWithCaptionView is loaded by xib (I have designed it with a UIImageView and UILabel), and I need to customize these 2 elements for each child in the row.

I can currently see the imageViewCaptionView and it responds to gestures, but I can't customize the UIImageView and UILabel (even from imageViewCaptionView itself).

ppilone
May 9, 2011, 12:37 PM
Have you made sure that you've connected the UILabel and UIImageView to your IBOutlets in IB?

Edit: Looking through the code that you posted it looks like you have a few memory leaks...

aneuryzma
May 9, 2011, 12:39 PM
yeah they are wired.

The value I can see when I run the app is the original value (the one I wrote in the UI Builder).

If I try to wire the file owner to them I can see imageView and label are already checked.

PhoneyDeveloper
May 9, 2011, 01:41 PM
You've got some weird code there man.

Nobody uses objectEnumerator anymore. Use fast enumeration instead.

There's a chance that you can make that init method work but it's definitely a memory leak as written since you ignore the original object. I would write a Factory Method instead. Something like UIButton buttonWithStyle. Your's could be +(id)imageWithCaptionView Move your nib-loading code there and return the new view.

Your cellForRowAtIndexPath code is odd.You create new imageViews for some reason. That doesn't make sense if your cell already has the imageViews from the nib. Also you create these new imageViews every time the method is called, even if the cell is dequeued. That's wrong and will end up with an unlimited number of imageViews added to your cell.

I'm a little unclear on the gcd code. Does the image get set on a background thread? If so that's illegal. You need to set the image on the main thread.

aneuryzma
May 10, 2011, 02:29 AM
First of all, about the enumeration: I've just googled for "iterate array objective-c" and the first result is this one: http://stackoverflow.com/questions/992901/how-do-i-iterate-over-an-nsarray

As you suggested I've converted to fast enumeration. Is this correct ?

id object;
for (object in photoIds) {
...
}


You wrote:
There's a chance that you can make that init method work but it's definitely a memory leak as written since you ignore the original object.

Well, in the init method I'm overwriting self with what I load from xib. I couldn't find a lot of documentation about creating views (and not viewControllers) directly from xib.



You create new imageViews for some reason. That doesn't make sense

Ok, so should I just set the UIImage inside UIImageView instead ? (the commented line above). Anyway it doesn't work, and I don't understand why, if I'm assigning a new image, I still see the original image of the xib file.



Also you create these new imageViews every time the method is called, even if the cell is dequeued.
This is a bit unclear to me. I thought the method is called only for a new row visible on screen. You mean I should include the code inside the if (cell == nil) statement ?




I'm a little unclear on the gcd code. Does the image get set on a background thread? If so that's illegal. You need to set the image on the main thread.
Yeah it is a background thread. I've modified the code:


dispatch_queue_t callerQueue = dispatch_get_current_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Flickr downloader in EventsTableView", NULL);

dispatch_async(downloadQueue, ^{

NSData *image = [FlickrFetcher imageDataFromPhotoId:object];

if(image) {

dispatch_async(callerQueue, ^{
imageWithCaptionView.imageView.image = [UIImage imageWithData: image];

//imageWithCaptionView.imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData: image]];
});
}

});

dispatch_release(downloadQueue);


By the way, why would you use a Factory Method pattern in this case ? I only have 1 class to instantiate multiple times

PhoneyDeveloper
May 10, 2011, 10:21 AM
Usually more like this

for (id object in photoIds) {

or

for (UIView* v in photoIds) {

The right way to load a view from a nib uses UINib or loadNibNamed: and gets the appropriate object(s) from the nib by use of an IBOutlet. Not trawling through the objects array like you're doing. Also in your init method you are ignoring self, you're not calling super init. You are just assigning to self. If you use a factory method to load the view from the nib then you avoid these problems. (Although I guess setting up the outlet might be unusual since it's a class method.) It doesn't have to be a factory method but you should avoid all that junk in the cell's init method.

It is a fundamental part of how tableviews work that table view cells are reused. So yes, code that runs only one time when the cell is created has to be inside the if (cell == nil) code.

aneuryzma
May 11, 2011, 01:29 AM
The right way to load a view from a nib uses UINib or loadNibNamed: and gets the appropriate object(s) from the nib by use of an IBOutlet.


This is exactly what I'm doing.

Not trawling through the objects array like you're doing.


the reason why I'm "iterating" through the NSStrings array is because I need multiple sub views per row from the same UIView subclass. I'm instancing each view with UINib as you said (and it works) and then passing the NSString to set the UILabel (this doesn't work).


Also in your init method you are ignoring self, you're not calling super init. You are just assigning to self.


Yes, that's because I'm loading it from a nib. Isn't that correct ?




It is a fundamental part of how tableviews work that table view cells are reused. So yes, code that runs only one time when the cell is created has to be inside the if (cell == nil) code.

Yeah, but even if I'm re-using queued cells, I stil need to update its components with the new text, outside the if statement. Correct ?



In the end, my question is still unanswered: "why isn't the label updated, instead of containing the original content (written in xib in UIBuilder)" ?

thanks

aneuryzma
May 11, 2011, 08:21 AM
mhmmm... I've found an article that says to not use xib files for UITableView cells ativsoftware.com/wordpress/?p=9

I should probably build it programmatically...

PhoneyDeveloper
May 11, 2011, 11:30 AM
You'll have to point out the IBOutlets that you're using when loading the nib.

Yeah, but even if I'm re-using queued cells, I stil need to update its components with the new text, outside the if statement. Correct ?

Yes. You only create the subviews one time but you need to set their contents every time.

In the end, my question is still unanswered: "why isn't the label updated, instead of containing the original content (written in xib in UIBuilder)" ?

As I mentioned there are a lot of problems with the code you show. Is the label nil? What is its frame?

BTW, if you add subviews to a tableviewcell you add them to the cell.contentView. Also, why don't you just add the imageview and label to a UITableViewCell that's in the nib?

I've found an article that says to not use xib files for UITableView cells

It certainly works fine if you do it right. There is some opinion that using nibs for table view cells will be slower than building the cell in code.

What I recommend that you do is to simplify this. Remove all the complicated code that uses gcd and the nib. Load up a normal default tableview cell. Add an image from your bundle and some constant text. When that works add features one by one so that you get them working. IMO, as is it's too complicated to figure out why it isn't working.