Why are my images loaded again and again in a table view? - ios

I am creating an app to display listings and each listing has an amount of x images to be displayed in a table cell.
To display the images, I have to create the UIImageView dynamically and load the images into the cell via a for loop (depending on the data received from a server call).
Now, I am able to add the images dynamically, but when I scroll the table view, the cellForRowAtIndexPath function runs again and the images are loaded again into the cell, hence creating more image views than the actual data.
I want to keep the image count constant in the cell and do not want to create more images in the cell as the table scrolls.
Here is the function code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *Cellidentifier1 = #"ClassCell";
ClassCell *cell = [tableView dequeueReusableCellWithIdentifier:Cellidentifier1 forIndexPath:indexPath];
// Configure the cell...
long row = [indexPath row];
for (int t = 0; t<individualSports.count; t++) {
UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake((250/10*count+10), 125, 20, 20)];
[imageView setImage:[UIImage imageNamed:#"cricket_unselected.png"]];
[cell addSubview:imageView];
}
return cell;
}

Your ClassCell could take care of this for you. If it is used for other purposes, just subclass it again;
#implementation ClassCell // or a new ClassCell subclass
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// for loop..
}
return self;
}
#end
Alternatively, you could just use a BOOL:
#interface ClassCell
#property BOOL hasImageViews;
#end
and then:
if (!cell.hasImageViews) {
cell.hasImageViews = YES;
// for loop..
}
Side note: I'm not really sure why you want to add the same picture that many times to a single cell, though; isn't it more likely that you want to use a checkbox of some kind? Also, you're iterating with t, but then applying the frame with count, meaning that all of your image views are overlapped on each other because they have the same frame within the cell.

Related

Form with custom UITableViewCells

I have a form in my app that exists out of a UITableView with custom cells. These cells can contain a UITextField, UISegmentedControl or a UISwitch. This is how I set this up:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableViewInner cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DetailTableViewCell *cell;
static NSString *MyIdentifier = #"MyIdentifier";
DetailTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[DetailTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
[cell setTextField:#"John Appleseed"];
// or
[cell setSegment];
[cell setSegmentIndex:1];
// or
[cell setSwitch];
[cell setSwitchEnabled:YES];
return cell;
}
Now, when a user taps the save button I need to fetch all this information and init a model with it, like this:
[[Restaurant alloc] initWithName:#"Name here" withNotifications:1 withFrequency:1 withDate:#"Date here" andWithDistance:#"Distance here"];
What's the best and cleanest way possible to convert all these inputs to data in my model? I feel like looping over all the cells is a bit over the top.
like looping over all the cells is a bit over the top
It isn't just over the top: it's totally wrong. The data doesn't live in the cells; it lives in the data. Model, view, controller; the cell is just view! Its job is to represent the model (the data). There should be nothing to loop over, therefore; you should already have the data as data.
Now, when a user taps the save button I need to fetch all this information
Actually, what I would do is capture the information when the user makes the change. Give the text field, switch, or segmented control a control action-target so that a message is sent to you telling you that something happened (e.g. the switch value changed, the text was edited, and so on) and capture the data right then.
The only question then becomes: I've received a message from a control: what row of the table is it in? To find out, walk the hierarchy up from the control until you come to the cell, and then ask the table what row this cell represents:
UIView* v = sender; // the control
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
A more cleaner approach using custom blocks.
DetailTableViewCell.h
typedef void (^ saveBlock_block_t )(WhateverYourReturnObjectType *obj);
#interface DetailTableViewCell : UITableViewCell
- (void)configureCell:(NSString *)textFieldVal
cellBlock:(saveBlock_block_t)cellBlock;
#end
DetailTableViewCell.m
#interface DetailTableViewCell ()
{
#property (copy, nonatomic) saveBlock_block_t saveBlock;
}
#end
#implementation DetailTableViewCell
- (void)configureCell:(NSString *)textFieldVal
cellBlock:(saveBlock_block_t)cellBlock
{
[cell setTextField: textFieldVal];
[self setSaveBlock:cellBlock];
}
-(IBAction)saveButtonAction:(id)sender //Action on your save button
{
self.cellBlock(obj); // Whatever object you want to return to class having your table object
}
#end
Then from cellForRowAtIndexPath call it as -
[cell configureCell:#"John Appleseed”
cellBlock:^(WhateverYourReturnObjectType *obj){
//Do what you want to do with 'obj' which is returned by block instance in the cell
}];

How to use UITableView to display 2 UILabels and a grid of images, also auto update the Cell height

I need to build a UITableView to display a list of posts, and each post will contain 3 sections.
Title (UILabel with 1 line of text)
Content (UILabel with multi lines of texts)
Grid of Images (number of images will vary from each row)
I have followed this post.
I am able to add the Title and Content, and with autolayout it works as I need it to. However, I cannot add the grid of images.
I have create a custom cell view class just like AutoSizeCell.h/AutoSizeCell.m in the above post. Also I have created a modal class to have three properties (title, content and NSMutableArray of image names I need to display in the grid) However, it seems I cannot pass the images names to AutoSizeCell.m, so I cannot display the image grid.
#implementation AutoSizeCellContents
-(id)init
{
self = [super init];
if (self) {
self.images = [[NSMutableArray alloc] init];
}
return self;
}
#end
controller:
-(void)configureCell:(AutoSizeCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
// Configure the cell for this indexPath
cell.category.text = [self getCategoryAtIndexPath:indexPath];
cell.pastTense.text = [self getPastTenseAtIndexPath:indexPath];
for (NSString *imageName in [self getImagesAtIndexPath:indexPath]) {
NSLog(#"image name %#",imageName);
[cell.images addObject:#"hellp"];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create a reusable cell
AutoSizeCell *cell = [tableView dequeueReusableCellWithIdentifier:#"plerp"];
if(!cell) {
cell = [[AutoSizeCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"plerp"];
[self configureCell:cell atIndexPath:indexPath];
}
return cell;
}
I believe you need to override -(id)initWithCoder:(NSCoder*)aDecoder instead of init.
Alternatively, don't do either of these things. Get rid of the init entirely and do this.
cell.images = [self getImagesAtIndexPath:indexPath]
Less code, less relying on cell state. And I'm pretty sure always adding images is going to cause issues when the cell gets reused unless you override prepareForReuse. Just setting the whole array is easier.

PFQueryTableViewController jump on fetch

Edit 1
To be clear, [self loadObjects] is not my method it is a method on the PFQueryTableViewController class supplied by parse to pull in new data.
I suspect this might be being caused by the table drawing code, as the tablecellview is configured to be auto-adjust it's height.
Here is the table drawing code:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
object:(PFObject *)object
{
//Setup estimated heights and auto row height
self.tableView.estimatedRowHeight = 68.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
//Give the cell a static identifier
static NSString *cellIdentifier;
socialPost* post = object;
//Check to see what sort of cell we should be creating, text, image or video
if (object[#"hasImage"] != nil) {
cellIdentifier = #"posts_with_image";
} else {
cellIdentifier = #"posts_no_image";
}
//Create cell if needed
hashtagLiveCellView *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[hashtagLiveCellView alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
// Configure the cell to show our imformation, loading video and images if needed
cell.postTitle.text = [NSString stringWithFormat:#"#%#",object[#"userName"]];
cell.postText.text = [NSString stringWithFormat:#"%#",
object[#"text"]];
[cell.userImage setImageWithURL:[NSURL URLWithString:post.userImageURL]];
[cell.postImage setImageWithURL:[NSURL URLWithString:post.imageURL]];
//Set ID's on the custom buttons so we know what object to work with when a button is pressed
cell.approveButtonOutlet.stringID = object.objectId;
[cell.approveButtonOutlet addTarget:self action:#selector(approvePostCallback:) forControlEvents:UIControlEventTouchUpInside];
cell.deletButtonOutlet.stringID = object.objectId;
[cell.deletButtonOutlet addTarget:self action:#selector(deletePostCallback:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
Original
I have a PFQueryTableViewController that i am loading with object from parse.
I have a scheduled task set to run every 20 seconds that calls:
[self loadObjects]
To fetch any new objects, or any changed to objects that have happened.
That all works fine, however if i am scrolled halfway down the tableview when the loadObjects is called the page jumps back to the top. Even if there are no new or changed data available.
Is there an easy way around this, before i start looking into hacky ways to catch the reload and force the table to stay where it is.
Thanks
Gareth
When you're calling loadObjects you load the objects from start. And there for you get the first results again.
Try to change [self loadObjects]; to [self.tableView reloadData];.

UISlider in Dynamic UITableView

I am creating an app in where I need to add a bunch of sliders in a Dynamic UITableView. I added the slider like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
UISlider *slider = [[UISlider alloc]init];
//ALL OTHER CODE
[cell addSubview:slider];
return cell;
}
Now the slider is added to the UITableView but the if I changed the value of the first slider another slider changes with it.I know this is something to do with dequeing the cell but how do I fix it?
EDIT:
I tried #daveMack answer like this:
CustomCell.m:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.checkBox = [[M13Checkbox alloc]initWithTitle:#"Checkbox!"];
self.checkBox.checkAlignment = M13CheckboxAlignmentLeft;
[self addSubview:self.checkBox];
}
return self;
}
Cell For Row At Index Path:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CheckboxCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:#""];
if (!cell) {
cell = [[CheckboxCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
return cell;
}
return cell;
}
No, no, no! I would not suggest you add views like that to your UITableViewCell. Funky stuff can happen as you have experienced yourself because of the dequeueing process.
I suggest you do the following:
Create a custom table view cell with its appropriate .h, .m file and .xib file
In your custom cell you can add WHATEVER views you like and however many views that you like.
Make sure you create a property of type UIScrollView in your .h file and link it to the interface builder to your custom cell's slider, call the property slider.
Now in your main view controller where you are creating your table, make sure you have an NSMutableArray and name it something like sliderValuesArray that can store all your slider values for each cell. You want to make sure that the number of cells is equal to the number of elements in your sliderValuesArray.
Then you can do something like this in your cellForRowAtIndexPath delegate method:
In your cellForRowAtIndexPath method do something like this:
myCustomCell.slider.maximumValue = 100;
myCustomCell.slider.minimumValue = 1;
myCustomCell.slider.continuous = TRUE;
//set a method which will get called when a slider in a cell changes value
[myCustomCell.slider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
//Keep a reference to each slider by assigning a tag so that we can determine
//which slider is being changed
myCustomCell.slider.tag = indexPath.row;
//Grab the value from the sliderValuesArray and set the slider knob to that position
myCustomCell.slider.value = [[sliderValuesArray objectAtIndex:indexPath.row] intValue];
Now in your sliderChanged method you can do this:
-(void)sliderChanged:(UISlider)sender{
//Grab the slider value, it needs to be converted to an NSNumber so that we can
//store it successfully as an object in our sliderValuesArray
NSNumber sliderValue = [NSNumber numberWithInt:sender.value];
//This is how we determine which position in our slidersArray we want to update,
//Based on the tag we set our slider view on initialisation in our cellForRowAtIndexPath
int cellPosition = sender.tag;
//Use the cellPosition to update the correct number in our sliderValuesArray
//with the sliderValue retrieved from the slider that the user is sliding
[sliderValuesArray replaceObjectAtIndex:cellPosition withObject:sliderValue];
}
I ran into this same issue awhile back. The solution I came up with was to subclass UITableViewCell. Then I added the slider in the init method of the subclass and exposed it via a property.
Now, when you want to change the value of JUST ONE slider, you would do something like:
[cell slider]setValue:(someValue)];

UITextField inside UITableViewCell - method for preventing text reset

I have a UITableView tall enough that it necessitates scrolling. The top-most cell in the table contains a UITextField for the user to enter some text.
The standard way to build this might be to create and add the text field and add it to a cell created or recycled in cellFOrRowAtIndexPath: However, this constant re-creation means that the text entered in the field is erased when the cell is scrolled out and back into view.
The solutions I've found so far suggest using UITextField delegation to track the text as it changes and store it in an iVar or property. I would like to know why this is recommended instead of the simpler approach I am using:
I am creating the UITextField in the init method of the UITableViewController and immediately storing it in a property. In cellFOrROwAtIndexPath I am simply adding the pre-existing field instead of initializing a new one. The cell itself can be recycled without issue, but because I am always using the one and only UITextField, the content is maintained.
Is this a reasonable approach? What might go wrong? Any improvements (perhaps I could still create the field in cellForRowAtIndexPath but first check if the property is nil?)
When you are creating cells in cellForRowAtIndexPath you have to use one reusable identifier for that first cell (ie. cellId1) and another for the rest (ie. cellId2).
If you do this, when you get the cell for the first element by calling [tableView dequeueReusableCellWithIdentifier:#"cellId1"] you will always get the same Object and will not be reused by other cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = nil;
// Only for first row
if (indexPath.row == 0) {
static NSString *cellId1 = #"cellId1";
cell = [tableView dequeueReusableCellWithIdentifier:cellId1];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId1];
}
}
else {
static NSString *cellId2 = #"cellId2";
cell = [tableView cellId2];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault cellId2];
}
}
// do whatever
return cell;
}
If there is only one UITextField, then I agree that your approach would be better/same as compared to using UITextField delegation (I think).
However, let us assume that you want to "expand" your view so that there are about 7-8 or more TextFields now. Then if you go about using your approach, then the problem will be that you will be storing 7-8 or more TextFields in memory and maintaining them.
In such a situation, a better approach would be that you create only that number of textfields as visible on screen. Then you create a dictionary which would maintain the content present in the textfield (which you can get by UITextFieldDelegate methods). This way, the same textfield can be used when the cell is reused. Only the values will change and will be dictated by the values in the dictionary.
On a sidenote, do minimal creation in cellForRowAtIndexPath as that is called during every table scroll and so creating a textField in cellForRowAtIndexPath can be expensive.
#import "ViewController.h"
#import "TxtFieldCell.h"
#define NUMBER_OF_ROWS 26
#interface ViewController ()<UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tablView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tablView.datasource = self; //set textfield delegate in storyboard
textFieldValuesArray = [[NSMutableArray alloc] init];
for(int i=0; i<NUMBER_OF_ROWS; i++){
[textFieldValuesArray addObject:#""];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - TableView Datasource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TxtFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TxtFieldCellId" forIndexPath:indexPath];
cell.txtField.tag = indexPath.row;
if (textFieldValuesArray.count > 0) {
NSString *strText = [textFieldValuesArray objectAtIndex:indexPath.row];
cell.txtField.text = strText;
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return NUMBER_OF_ROWS;
}
#pragma mark - TextField Delegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
[textFieldValuesArray replaceObjectAtIndex:textField.tag withObject:textField.text];
}

Resources