How to populate a UITableView in a UIView without cellForRowAtIndexPath - ios

I'am dealing with a problem the past few days. I have a UIScrollView in a xib file, in this UIScrollView i put UIViews (as xib too), and in them UIViews i put UITableViews (as xib too). But as a rookie i always populated my UITableView using cellForRowAtindexPath, you return the cell with your wanted text and it's all done, but this time it is not called, so maybe there is another way to populate it ?. As information i'am trying to use CollapseClick as my view. If someone could help me please or have the slightest idea about how to populate it feel free to answer please.
here is my code, i spare you all the xml parsing process.
.m were i init my UITableView
- (void)viewDidLoad
{
[super viewDidLoad];
newsTable = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewCellStyleDefault];
newsTable.delegate = self;
//newsTable.dataSource = self;
myCollapseClick.CollapseClickDelegate = self;
[myCollapseClick reloadCollapseClick];
// If you want a cell open on load, run this method:
[myCollapseClick openCollapseClickCellAtIndex:0 animated:YES];
/*
// If you'd like multiple cells open on load, create an NSArray of NSNumbers
// with each NSNumber corresponding to the index you'd like to open.
// - This will open Cells at indexes 0,2 automatically
NSArray *indexArray = #[[NSNumber numberWithInt:0],[NSNumber numberWithInt:2]];
[myCollapseClick openCollapseClickCellsWithIndexes:indexArray animated:NO];
*/
}
were i need to return the UIViews with the UITable views
-(UIView *)viewForCollapseClickContentViewAtIndex:(int)index {
switch (index) {
case 0:
return test1View;
break;
case 1:
return test2View;
break;
case 2:
return test3View;
break;
default:
return test1View;
break;
}
}
the good old cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
CGRect frame = CGRectMake(0, 0, 300, 50);
UILabel *lbl1 = [[UILabel alloc] initWithFrame:frame];
lbl1.textAlignment = NSTextAlignmentCenter;
[lbl1 setFont:[UIFont fontWithName:#"Helvetica" size:12.0]];
//self.tableView.separatorColor = [UIColor whiteColor];
//lbl1.backgroundColor = [UIColor purpleColor];
[lbl1 setTextColor:[UIColor blackColor]];
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
if (storyIndex == 0 && FlagS)
{
NSMutableArray * SIR = [[NSUserDefaults standardUserDefaults] objectForKey:#"sizingSheet"];
NSString * SID = [[SIR objectAtIndex: storyIndex] objectForKey: #"sizing_id"];
NSString *DID = [[SIR objectAtIndex: storyIndex] objectForKey: #"data_id"];
Request = [[restRequestManager alloc]init];
NSString *SDD = [NSString stringWithFormat:#"%#%#%#%#%#", #"sizing_id=", SID, #"&", #"data_id=", DID];
NSData* Mimage = [Request restTestRequesterImage:#"http://www.stackoverflow.com" serviceUri:#"question/Post" parameters:SDD technique:#"POST"];
cell.imageView.image = [UIImage imageWithData:Mimage];
return cell;
}
searchString = #"TH_MEASURE_CATEGORY_";
str=[category objectAtIndex:storyIndex];
NSArray *array = [[NSMutableArray alloc]init];
array = [str componentsSeparatedByString:#"#"];
for (NSString *tempStr in array) {
NSComparisonResult result = [tempStr compare:searchString options:(NSCaseInsensitiveSearch) range:NSMakeRange(0, [searchString length])];
if (result == NSOrderedSame) {
lbl1.text = [category objectAtIndex: storyIndex];
lbl1.text = [lbl1.text substringFromIndex: MIN(20, [lbl1.text length])];
[cell.contentView addSubview:lbl1];
}
else{
lbl1.text = [category objectAtIndex: storyIndex];
[cell.contentView addSubview:lbl1];
}
}
return cell;
}
and the .h without all the useless stuff
#interface MeasuresViewController: UIViewController <CollapseClickDelegate,UITextFieldDelegate, UITableViewDelegate, UITableViewDelegate> {
IBOutlet UIView *test1View;
IBOutlet UIView *test2View;
IBOutlet UIView *test3View;
__weak IBOutlet CollapseClick *myCollapseClick;
IBOutlet UITableView * newsTable;
BOOL *FlagS;
UIActivityIndicatorView * activityIndicator;
CGSize cellSize;
NSMutableArray * stories;
NSMutableString * currentCategory, * currentSubcategory, * currentValue, * currentName, * currentSrc;
NSDictionary *data, *data1, *data2, *data3, *data4;
}
- (void)parseXMLFileAtURL:(NSString *)URL;
-(BOOL)ifStringExists:(NSString *)stringSentToCheck selectYourCheckArray:(NSMutableArray *)SelectedArray;
#property (nonatomic, strong) NSMutableArray *photos;
#property (atomic, strong) NSMutableArray *assets;
#end

You commented out the line that sets the data source. If a data source is not set then cellForRowAtIndexPath: will not be called.
Your current viewDidLoad code is:
- (void)viewDidLoad {
[super viewDidLoad];
newsTable = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewCellStyleDefault];
newsTable.delegate = self;
//newsTable.dataSource = self;
myCollapseClick.CollapseClickDelegate = self;
[myCollapseClick reloadCollapseClick];
// If you want a cell open on load, run this method:
[myCollapseClick openCollapseClickCellAtIndex:0 animated:YES];
/*
// If you'd like multiple cells open on load, create an NSArray of NSNumbers
// with each NSNumber corresponding to the index you'd like to open.
// - This will open Cells at indexes 0,2 automatically
NSArray *indexArray = #[[NSNumber numberWithInt:0],[NSNumber numberWithInt:2]];
[myCollapseClick openCollapseClickCellsWithIndexes:indexArray animated:NO];
*/
}
Change it to
- (void)viewDidLoad
{
[super viewDidLoad];
newsTable = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewCellStyleDefault];
newsTable.delegate = self;
newsTable.dataSource = self;
myCollapseClick.CollapseClickDelegate = self;
[myCollapseClick reloadCollapseClick];
// If you want a cell open on load, run this method:
[myCollapseClick openCollapseClickCellAtIndex:0 animated:YES];
/*
// If you'd like multiple cells open on load, create an NSArray of NSNumbers
// with each NSNumber corresponding to the index you'd like to open.
// - This will open Cells at indexes 0,2 automatically
NSArray *indexArray = #[[NSNumber numberWithInt:0],[NSNumber numberWithInt:2]];
[myCollapseClick openCollapseClickCellsWithIndexes:indexArray animated:NO];
*/
}

Related

I have a Table View and five data sources

I have a table view. There are five data sources. When I click on the button when the data source switch, but click the first button will be empty, the other button click normal, has not found the reason.
This is the code:
#import "ViewController.h"
#interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
#property(nonatomic,strong)NSMutableArray * dataArr;
#property(nonatomic,strong)NSMutableArray * dataArr0;
#property(nonatomic,strong)NSMutableArray * dataArr1;
#property(nonatomic,strong)NSMutableArray * dataArr2;
#property(nonatomic,strong)NSMutableArray * dataArr3;
#property(nonatomic,strong)NSMutableArray * dataArr4;
#property(nonatomic,strong)UITableView * mainTab;
#end
static NSString * cellID = #"cellID";
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray * mutarr0 = [NSMutableArray arrayWithObjects:#"all",#"all",#"all",#"all",#"all", nil];
self.dataArr0 = mutarr0;
NSMutableArray * mutarr1 = [NSMutableArray arrayWithObjects:#"Waiting",#"Waiting",#"Waiting",#"Waiting",#"Waiting",#"Waiting", nil];
self.dataArr1 = mutarr1;
NSMutableArray * mutarr2 = [NSMutableArray arrayWithObjects:#"processing",#"processing",#"processing",#"processing", nil];
self.dataArr2 = mutarr2;
NSMutableArray * mutarr3 = [NSMutableArray arrayWithObjects:#"End",#"End",#"End",#"End",#"End",#"End",#"End", nil];
self.dataArr3 = mutarr3;
NSMutableArray * mutarr4 = [NSMutableArray arrayWithObjects:#"cancel",#"cancel",#"cancel",#"cancel",#"cancel", nil];
self.dataArr4 = mutarr4;
self.dataArr = mutarr0;
NSArray * segArr = #[#"all",#"Waiting",#"processing",#"End",#"cancel"];
UISegmentedControl * segC = [[UISegmentedControl alloc]initWithItems:segArr];
segC.frame = CGRectMake(0,64, self.view.frame.size.width,50);
[segC addTarget:self action:#selector(change:) forControlEvents:UIControlEventValueChanged];
UITableView * tabVC = [[UITableView alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(segC.frame),self.view.frame.size.width, self.view.frame.size.height - 50) style: UITableViewStylePlain];
tabVC.delegate = self;
tabVC.dataSource = self;
self.mainTab = tabVC;
[tabVC registerClass:[UITableViewCell class] forCellReuseIdentifier:cellID];
[self.view addSubview:segC];
[self.view addSubview:tabVC];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.dataArr.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
cell.textLabel.text = self.dataArr[indexPath.row];
return cell;
}
-(void)change:(UISegmentedControl *)sender{
if (sender.selectedSegmentIndex == 0) {
[self.dataArr removeAllObjects];
[self.dataArr addObjectsFromArray:self.dataArr0];
NSLog(#"%lu33333333333333",self.dataArr.count);
// NSLog(#"%lu44444444444444",self.dataArr0.count);
// //NSLog(#"%#-------%#",self.dataArr0[0],self.dataArr0[1]);
[self.mainTab reloadData];
}else if (sender.selectedSegmentIndex == 1){
[self.dataArr removeAllObjects];
[self.dataArr addObjectsFromArray:self.dataArr1];
NSLog(#"2");
[self.mainTab reloadData];
}else if (sender.selectedSegmentIndex == 2){
//
[self.dataArr removeAllObjects];
[self.dataArr addObjectsFromArray:self.dataArr2];
[self.mainTab reloadData];
NSLog(#"3");
}else if (sender.selectedSegmentIndex == 3){
[self.dataArr removeAllObjects];
[self.dataArr addObjectsFromArray:self.dataArr3];
[self.mainTab reloadData];
// self.view.backgroundColor = [UIColor blueColor];
NSLog(#"4");
}else if (sender.selectedSegmentIndex == 4){
[self.dataArr removeAllObjects];
[self.dataArr addObjectsFromArray:self.dataArr4];
[self.mainTab reloadData];
// self.view.backgroundColor = [UIColor orangeColor];
NSLog(#"5");
}
}
#end
Your primary problem is that in viewDidLoad you do:
self.dataArr = mutarr0;
And then in your change: method you do:
[self.dataArr removeAllObjects];
This erases all of the values from mutarr0 (which is also self.dataArr0).
The best way to fix this is to change all lines of the form:
[self.dataArr removeAllObjects];
[self.dataArr addObjectsFromArray:self.dataArr4];
to:
self.dataArr = self.dataArr4;
That's it. No need to remove or add any objects.
With that working, there are other big improvements you can make. Mainly, get rid of all of the array properties. You don't need 5 separate array properties. Just create one that represents an array of your arrays.
Now your code can be simplified to just:
#interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
#property(nonatomic,strong) NSMutableArray *current;
#property(nonatomic,strong) NSArray *data;
#property(nonatomic,strong)UITableView * mainTab;
#end
static NSString * cellID = #"cellID";
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *mutarr0 = [NSMutableArray arrayWithObjects:#"all",#"all",#"all",#"all",#"all", nil];
NSMutableArray *mutarr1 = [NSMutableArray arrayWithObjects:#"Waiting",#"Waiting",#"Waiting",#"Waiting",#"Waiting",#"Waiting", nil];
NSMutableArray *mutarr2 = [NSMutableArray arrayWithObjects:#"processing",#"processing",#"processing",#"processing", nil];
NSMutableArray *mutarr3 = [NSMutableArray arrayWithObjects:#"End",#"End",#"End",#"End",#"End",#"End",#"End", nil];
NSMutableArray *mutarr4 = [NSMutableArray arrayWithObjects:#"cancel",#"cancel",#"cancel",#"cancel",#"cancel", nil];
self.data = #[ mutarr0, mutarr1, mutarr2, mutarr3, mutarr4 ];
self.current = self.data[0];
NSArray * egArr = #[ #"all", #"Waiting", #"processing", #"End", #"cancel" ];
UISegmentedControl * segC = [[UISegmentedControl alloc]initWithItems:segArr];
segC.frame = CGRectMake(0,64, self.view.frame.size.width,50);
[segC addTarget:self action:#selector(change:) forControlEvents:UIControlEventValueChanged];
UITableView * tabVC = [[UITableView alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(segC.frame),self.view.frame.size.width, self.view.frame.size.height - 50) style: UITableViewStylePlain];
tabVC.delegate = self;
tabVC.dataSource = self;
self.mainTab = tabVC;
[tabVC registerClass:[UITableViewCell class] forCellReuseIdentifier:cellID];
[self.view addSubview:segC];
[self.view addSubview:tabVC];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.current.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
cell.textLabel.text = self.current[indexPath.row];
return cell;
}
-(void)change:(UISegmentedControl *)sender{
if (sender.selectedSegmentIndex != UISegmentedControlNoSegment {
self.current = self.data[sender.selectedSegmentIndex];
[self.mainTab reloadData];
}
}
#end
You don't need to change your code structure, though I agree it could be written more efficiently. But the main problem is in this line:
self.dataArr = mutarr0;
You self.dataArr was NEVER initialized as an independent property. Just replace that line with these and it should work:
self.dataArr = [[NSMutableArray alloc] init]; // initialize it first!
[self.dataArr addObjectsFromArray:mutarr0]; // and then use it
You should also initialize all the other properties,
dataArr0-dataArr5.
The problem is.
self.dataArr = mutarr0;
self.dataArr will point to mutarr0, self.dataArr does not have its own memory. You can see
this point
When you remove
if (sender.selectedSegmentIndex == 0) {
[self.dataArr removeAllObjects];
...
}
mutarr0 objects is removed , so you should create the memory for self.dataArr. Upstairs gave a good resolution.

Global array with custom objects is not keeping objects' state

I am creating a bucket list app and I need to be able to save each BucketListGoal from a UITextField in a UITableViewCell. Here is my relevant code below -
#interface BucketListGoalViewController () <UITableViewDataSource, UITableViewDelegate, CreateGoalTableViewCellDelegate, UITextFieldDelegate, UIPopoverPresentationControllerDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSArray *goals;
#property NSInteger path;
#end
#implementation BucketListGoalViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.navigationItem.title = #"Goals";
self.count = 0;
self.goals = [[NSArray alloc] init];
[self createNewGoal];
self.tableView.allowsSelection = NO;;
[self.tableView registerClass:[CreateGoalTableViewCell class]forCellReuseIdentifier:#"Cell"];
[self.tableView registerNib:[UINib nibWithNibName:#"CreateGoalTableViewCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleCoordinatesFromBucketList:)
name:#"Coordinates"
object:nil];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Cell";
CreateGoalTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell.textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
cell.tag = indexPath.row;
cell.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textField.tag = indexPath.row;
cell.textField.delegate = self;
BucketListGoal *goal = [self.goals objectAtIndex:indexPath.row];
goal.tag = indexPath.row;
if (goal.address != nil) {
[cell.locationButton setTitle:goal.address forState:UIControlStateNormal];
}
goal.name = cell.detailTextLabel.text;
cell.textField.placeholder = #"Write the description of your goal here.";
cell.numberLabel.text = [NSString stringWithFormat:#"%ld)", (long)indexPath.row + 1];
return cell;
}
-(void)createNewGoal {
BucketListGoal *goal = [BucketListGoal new];
NSMutableArray *copy = [self.goals mutableCopy];
[copy addObject:goal];
self.goals = copy;
[self.tableView reloadData];
}
-(void)textFieldDidChange:(UITextField *)textField{
NSMutableArray *copy = [self.goals mutableCopy];
BucketListGoal *goal = [self.goals objectAtIndex:textField.tag];
goal.name = textField.text;
[copy replaceObjectAtIndex:textField.tag withObject:goal];
self.goals = copy;
}
I create the goal from a bar button item at the top of the view. The issue is that self.goals is only keeping the goal.name of the latest item of the array. All the other previous items from the text fields continue being nil. What am I missing here?

How to Asynchronously load UITableViewcell images so that scrolling doesn't lag

I've tried using ASyncImageView for this purpose, but I'm a bit confused as to how I'd implement it for my specific case. I currently have a MatchCenterViewController that contains a table inside of it. It's loading the images for the cells synchronously, which is causing a lot of lag when scrolling through the table. How can I modify the way I'm loading the remote images so that it's done asynchronously? My code is below:
#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#import "MatchCenterCell.h"
#interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *matchCenter;
#property (nonatomic, assign) BOOL matchCenterDone;
#property (nonatomic, assign) BOOL hasPressedShowMoreButton;
#end
#implementation MatchCenterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_matchCenterDone = NO;
_hasPressedShowMoreButton = NO;
// Set up MatchCenter table
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewCellStyleSubtitle];
self.matchCenter.frame = CGRectMake(0,70,320,self.view.frame.size.height-100);
self.edgesForExtendedLayout = UIRectEdgeAll;
self.matchCenter.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, CGRectGetHeight(self.tabBarController.tabBar.frame), 0.0f);
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
self.expandedSection = -1;
_matchCenterArray = [[NSArray alloc] init];
// Refresh button
UIImageView *refreshImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"refresh.png"]];
refreshImageView.frame = CGRectMake(280, 30, 30, 30);
refreshImageView.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(refreshPressed:)];
[refreshImageView addGestureRecognizer:tapGesture];
[self.view addSubview:refreshImageView];
// Preparing for MC and indicating loading
self.matchCenterArray = [[NSArray alloc] init];
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview: activityIndicator];
[activityIndicator startAnimating];
_matchCenterDone = NO;
// Disable ability to scroll until table is MatchCenter table is done loading
self.matchCenter.scrollEnabled = NO;
[PFCloud callFunctionInBackground:#"MatchCenter3"
withParameters:#{}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[activityIndicator stopAnimating];
[_matchCenter reloadData];
_matchCenterDone = YES;
self.matchCenter.scrollEnabled = YES;
NSLog(#"Result: '%#'", result);
}
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _matchCenterArray.count;
}
//the part where i setup sections and the deleting of said sections
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 21.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 40;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
//code snipped out for conciseness
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Header code snipped out for conciseness
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *currentSectionDictionary = _matchCenterArray[section];
NSArray *top3ArrayForSection = currentSectionDictionary[#"Top 3"];
return (top3ArrayForSection.count-1 < 1) ? 1 : top3ArrayForSection.count-1;
}
// Cell layout
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Initialize cell
static NSString *CellIdentifier = #"MatchCenterCell";
MatchCenterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[MatchCenterCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//[cell.contentView addSubview:cell.priceLabel];
[cell.contentView addSubview:cell.conditionLabel];
// No cell seperators = clean design
tableView.separatorColor = [UIColor clearColor];
NSDictionary *currentSectionDictionary = _matchCenterArray[indexPath.section];
NSArray *top3ArrayForSection = currentSectionDictionary[#"Top 3"];
if (top3ArrayForSection.count-1 < 1) {
// title of the item
cell.textLabel.text = #"No items found, but we'll keep a lookout for you!";
cell.textLabel.font = [UIFont systemFontOfSize:12];
}
else {
// title of the item
cell.textLabel.text = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Title"];
cell.textLabel.font = [UIFont systemFontOfSize:14];
// price + condition of the item
NSString *price = [NSString stringWithFormat:#"$%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Price"]];
NSString *condition = [NSString stringWithFormat:#"%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Item Condition"]];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# - %#", price, condition];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];
// image of the item
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Image URL"]]];
[[cell imageView] setImage:[UIImage imageWithData:imageData]];
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 2.5;
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == self.expandedSection || indexPath.row <= 3) {
return 65;
}
return 0;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (_matchCenterDone == YES) {
self.itemURL = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Item URL"];
[self performSegueWithIdentifier:#"WebViewSegue" sender:self];
}
}
#end
#implementation MoreButton
#end
// Use background thread to avoid the laggy tableView
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Download or get images here
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"url"]];
UIImage *cellImage = [[UIImage alloc] initWithData:imageData];
// Use main thread to update the view. View changes are always handled through main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Refresh image view here
[cell.imageView setImage:cellImage];
[cell.imageView.layer setMasksToBounds:YES];
[cell.imageView.layer setCornerRadius:2.5f];
[cell setNeedsLayout];
});
});
The most common solution to this is AFNetworking's AFImageView. It handles this situation perfectly. It should take you no time at all to implement, so give it a go.
Guy Kogus' answer works great. He's right, I got into all kinds of issues like he mentions in the comment above, doing similar things like the first answer.
Still, here's an example on how to use AFNetworking's UIImageView category. Assuming the code below is in a Cell (or something inheriting from a UIView).
First import the class:
#import "UIImageView+AFNetworking.h"
Then add this code in your UITableViewCell:
NSString *url = #"http://www.domain.www/some_image.jpg";
[self.productImage setImageWithURL:[NSURL URLWithString:url]
placeholderImage:[UIImage imageNamed:#"placeholderImg.png"]];
[self setNeedsLayout];
Not 100% sure if setNeedsLayout is necessary in this case. Feel free to correct this.

How to add dynamically rows to a UITableView in iOS?

I'm developing a view controller for a chat application and I want to show a UIViewController that contains a UITableView (where messages are shown with different format [if is your message or if is a message from other person], a UITextField (to write your messages) and a UIButton (to send the message)
I'm using SRWebSocket example but they use a UITableViewController (that runs perfectly but don't allow me to modify tableview size or to add the others components to the view by storyboard)
This is the code that I have in my Controller:
ChatViewController.h
#import <UIKit/UIKit.h>
#import "SRWebSocket.h"
#import "ChatCell.h"
#import "Message.h"
#import "Person.h"
#import "Program.h"
#import "DateFactory.h"
#interface ChatViewController : UIViewController <UITableViewDataSource,UITableViewDelegate,SRWebSocketDelegate, UITextViewDelegate, UITextFieldDelegate>
#property (strong, nonatomic) NSDictionary *programSegue;
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (nonatomic, retain) IBOutlet UITextView *inputView;
- (IBAction)goingUp:(id)sender;
#property (weak, nonatomic) IBOutlet UITextField *inputText;
#end
ChatViewController.m
Code that fails:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
in:
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
{
NSLog(#"Received \"%#\"", message);
NSError *e;
NSDictionary *allJSON =
[NSJSONSerialization JSONObjectWithData: [message dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &e];
NSString *kindJSON = [allJSON objectForKey:#"kind"];
NSString *userJSON = [allJSON objectForKey:#"user"];
NSString *messageJSON = [allJSON objectForKey:#"message"];
NSArray *membersJSON = [allJSON objectForKey:#"members"];
DateFactory *dateFactory = [DateFactory alloc];
NSString *formatDate = #"dd/MM/YYYY HH:mm";
NSString *dateString = [dateFactory dateToString:[NSDate date] withFormat:formatDate];
switch([#[#"join", #"talk", #"quit"] indexOfObject:kindJSON]){
// join
case 0:
break;
// talk
case 1:
[_messages addObject:[[Message alloc] initWithMessage:messageJSON fromMe:NO]];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];
break;
// quit
case 2:
[[self.navigationItem.titleView.subviews objectAtIndex:1] setText:
[NSString stringWithFormat:#"Sin conexión desde %#", dateString]];
break;
}
}
ERROR
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update'
Full code:
#import "ChatViewController.h"
#interface ChatViewController ()
#end
#implementation ChatViewController{
SRWebSocket *_webSocket;
NSMutableArray *_messages;
Person *person;
Program *program;
}
#synthesize programSegue;
#synthesize tableView;
#synthesize inputText;
#synthesize inputView = _inputView;
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
return [inputText resignFirstResponder];
}
#pragma mark - View lifecycle
- (void)viewDidLoad;
{
[super viewDidLoad];
[inputText setDelegate:self];
person = [programSegue objectForKey:#"PERSON"];
program = [programSegue objectForKey:#"PROGRAM"];
self.navigationItem.title = person.name;
// Creates picture to be shown in navigation bar
UIButton* picture = (UIButton *) [[UIImageView alloc] initWithImage:[UIImage imageNamed:person.imageURL]];
CGRect buttonFrame = picture.frame;
buttonFrame.size = CGSizeMake(38, 38);
picture.frame = buttonFrame;
UIBarButtonItem *pictureItem = [[UIBarButtonItem alloc] initWithCustomView:picture];
self.navigationItem.rightBarButtonItem = pictureItem;
// Set title and subtitle
CGRect frame = self.navigationController.navigationBar.frame;
UIView *twoLineTitleView = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth(frame), 0, CGRectGetWidth(frame), CGRectGetHeight(frame))];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 6, CGRectGetWidth(frame), 20)];
titleLabel.backgroundColor = [UIColor clearColor];
[titleLabel setTextColor:[UIColor whiteColor]];
titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[titleLabel setTextAlignment:NSTextAlignmentCenter];
[titleLabel setFont:[UIFont boldSystemFontOfSize:16]];
[titleLabel setShadowColor:[UIColor grayColor]];
titleLabel.text = person.name;
[twoLineTitleView addSubview:titleLabel];
UILabel *subTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 26, CGRectGetWidth(frame), 14)];
subTitleLabel.backgroundColor = [UIColor clearColor];
[subTitleLabel setTextColor:[UIColor whiteColor]];
subTitleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[subTitleLabel setTextAlignment:NSTextAlignmentCenter];
[subTitleLabel setFont:[UIFont boldSystemFontOfSize:12]];
[titleLabel setShadowColor:[UIColor grayColor]];
subTitleLabel.text = #"subtitleg";
[twoLineTitleView addSubview:subTitleLabel];
self.navigationItem.titleView = twoLineTitleView;
// Start messages
_messages = [[NSMutableArray alloc] init];
[self.tableView reloadData];
}
- (void)_reconnect;
{
_webSocket.delegate = nil;
[_webSocket close];
_webSocket = [[SRWebSocket alloc] initWithURLRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:
[NSString stringWithFormat:#"ws://81.45.19.228:8000/room/chat?username=enrimr&pid=%#", person.name]]]];
_webSocket.delegate = self;
//self.title = #"Opening Connection...";
[[self.navigationItem.titleView.subviews objectAtIndex:1] setText:#"Conectando..."];
[_webSocket open];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self _reconnect];
}
- (void)reconnect:(id)sender;
{
[self _reconnect];
}
- (void)viewDidAppear:(BOOL)animated;
{
[super viewDidAppear:animated];
[_inputView becomeFirstResponder];
[self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
_webSocket.delegate = nil;
[_webSocket close];
_webSocket = nil;
}
#pragma mark - UITableViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return _messages.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
{
ChatCell *chatCell = (id)cell;
Message *message = [_messages objectAtIndex:indexPath.row];
chatCell.text.text = message.message;
chatCell.date.text = message.fromMe ? #"Me" : #"Other";
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
Message *message = [_messages objectAtIndex:indexPath.row];
ChatCell *cell = (ChatCell *)[self.tableView dequeueReusableCellWithIdentifier:#"programCell" forIndexPath:indexPath];
if (!cell) {
if (message.fromMe){
cell = [[ChatCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"SentCell"];
[cell.text setText:message.message];
[cell.date setText:#"00:00"];
}
else {
cell = [[ChatCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"ReceivedCell"];
[cell.text setText:message.message];
[cell.date setText:#"00:00"];
}
}
return cell;
}
#pragma mark - SRWebSocketDelegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
{
NSLog(#"Websocket Connected");
//self.title = #"Connected!";
[[self.navigationItem.titleView.subviews objectAtIndex:1] setText:#"Conectado"];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
{
NSLog(#":( Websocket Failed With Error %#", error);
self.title = #"Connection Failed! (see logs)";
_webSocket = nil;
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
{
NSLog(#"Received \"%#\"", message);
NSError *e;
NSDictionary *allJSON =
[NSJSONSerialization JSONObjectWithData: [message dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &e];
NSString *kindJSON = [allJSON objectForKey:#"kind"];
NSString *userJSON = [allJSON objectForKey:#"user"];
NSString *messageJSON = [allJSON objectForKey:#"message"];
NSArray *membersJSON = [allJSON objectForKey:#"members"];
DateFactory *dateFactory = [DateFactory alloc];
NSString *formatDate = #"dd/MM/YYYY HH:mm";
NSString *dateString = [dateFactory dateToString:[NSDate date] withFormat:formatDate];
switch([#[#"join", #"talk", #"quit"] indexOfObject:kindJSON]){
// join
case 0:
break;
// talk
case 1:
[_messages addObject:[[Message alloc] initWithMessage:messageJSON fromMe:NO]];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];
break;
// quit
case 2:
[[self.navigationItem.titleView.subviews objectAtIndex:1] setText:
[NSString stringWithFormat:#"Sin conexión desde %#", dateString]];
break;
}
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
{
NSLog(#"WebSocket closed");
//self.title = #"Connection Closed! (see logs)";
[[self.navigationItem.titleView.subviews objectAtIndex:1] setText:#"Offline"];
_webSocket = nil;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
{
if ([text rangeOfString:#"\n"].location != NSNotFound) {
NSString *message = [[textView.text stringByReplacingCharactersInRange:range withString:text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[_webSocket send:message];
[_messages addObject:[[Message alloc] initWithMessage:message fromMe:YES]];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];
textView.text = #"";
return NO;
}
return YES;
}
- (void) animateTextField: (UITextField*) textField up: (BOOL)up
{
const int movementDistance = 218;
const float movementDuration = 0.3f;
int movement = (up ? -movementDistance : movementDistance);
[UIView beginAnimations: #"anim" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
- (IBAction)goingUp:(id)sender {
[self animateTextField:inputText up:TRUE];
}
#end
When you use insertRowsAtIndexPaths you have to first update the table view data source. So before you call the insertRowsAtIndexPaths you should do something like _messages addObject:newMessage.
Just as a helper rule, whenever you update the rows of a table view without using reloadData method, you have to update the tableView`s data source to reflect the index paths that will be updated. So if you delete on row from your table view, the data associated with that row must be deleted from data source, also if you add a row to the table view, you have to add the associated data of the new row into the data source. ALWAYS UPDATE THE DATASOURCE FIRST.
And every time you update the rows of a table view you should use the update method between beginUpdates and endUpdates method calls.
The problem was that I forgot to set
[tableView setDataSource:self];
[tableView setDelegate:self];
in my viewDidLoad. These two lines will fix my problem.

UITableView with Json file, loading images to cell disappear

I am using asyncimageview classes nicely provided by: http://www.markj.net/iphone-asynchronous-table-image/
I am getting image urls from a json file and loading each image to a cell.
Problem:
When i scroll up, and scroll back down to the same cell, the image reloads (disappears and appears again). I can't figure out why the image keeps reloading? Does anyone have suggestions or a solution to how I could make it stop? Thanks in advance!
// Asks the data source to return a cell to insert in a particular table view location
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Gets the current news article
NewsArticle *theCurrentArticle = [self.listofNewsArticles objectAtIndex:[indexPath row]];
//Gets the title from the current article
NSString *theTitle = theCurrentArticle.title;
//Gets the image url
NSString *imageUrl = theCurrentArticle.imageURL;
//Gets the description of the current news article
NSString *theDescription = theCurrentArticle.description;
NewsCustomCell *cell = (NewsCustomCell *)[tableView dequeueReusableCellWithIdentifier:#"NewsContent"];
__block NewsCustomCell *aCell;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (cell == nil) {
aCell = (NewsCustomCell *)[tableView dequeueReusableCellWithIdentifier:#"NewsContent"];
} else {
AsyncImageView* oldImage = (AsyncImageView*)
[cell.contentView viewWithTag:999];
[oldImage removeFromSuperview];
}
AsyncImageView *imageView = [[AsyncImageView alloc] initWithFrame:CGRectMake(5, 5, 100, 100)];
imageView.tag = 999;
dispatch_async(dispatch_get_main_queue(), ^{
[imageView loadImageFromURL:[NSURL URLWithString:imageUrl]];
[cell.contentView addSubview:imageView];
cell.titleLabel.text = theTitle;
cell.descriptionLabel.text = theDescription;
cell.imageLabel.contentMode = UIViewContentModeScaleAspectFill;
});
});
return cell;
}
Heres what the current app looks like:
Solution:
Just worked with this class I finally found.
https://github.com/rs/SDWebImage
This handles caching, async downloads. Wish I found this sooner..
I hope you have Write AsyncImageView and ImageCache and ImageCacheObject classes.
Write this code inside cellForRowAtIndexPath:
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Quest_Forum *Quest_ForumObj=[queArr objectAtIndex:i+indexPath.row];
// NSLog(#"cell for row at... i val.. %d",i+indexPath.row);
for(UIView * view in cell.contentView.subviews){
if([view isKindOfClass:[AsyncImageView class]])
{
// NSLog(#"remove old images");
[view removeFromSuperview];
view = nil;
}
}
AsyncImageView *asyncImageView = nil;
UIImageView *cellImage = (UIImageView *)[cell viewWithTag:1]; // Inside tableview i have taken tableviewcell and given tag 1 to that imageview
asyncImageView = [[AsyncImageView alloc] initWithFrame:cellImage.frame] ;
[asyncImageView loadImageFromURL:Quest_ForumObj.Quest_Image];
asyncImageView.backgroundColor=[UIColor clearColor];
[cell.contentView addSubview:asyncImageView];
UILabel *lblQuest = (UILabel *)[cell viewWithTag:2]; // Given tag 2 to the label inside tableViewCell
lblQuest.text=Quest_ForumObj.Question;
In ImageCacheObject.h
#class ImageCacheObject;
#interface ImageCache : NSObject
{
NSUInteger totalSize; // total number of bytes
NSUInteger maxSize; // maximum capacity
NSMutableDictionary *myDictionary;
}
#property (nonatomic, readonly) NSUInteger totalSize;
-(id)initWithMaxSize:(NSUInteger) max;
-(void)insertImage:(UIImage*)image withSize:(NSUInteger)sz forKey:(NSString*)key;
-(UIImage*)imageForKey:(NSString*)key;
In ImageCacheObject.m
#import "ImageCacheObject.h"
#synthesize totalSize;
-(id)initWithMaxSize:(NSUInteger) max
{
if (self = [super init])
{
totalSize = 0;
maxSize = max;
myDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
-(void)dealloc // Don't write this method if you are using ARC
{
[myDictionary release];
[super dealloc];
}
-(void)insertImage:(UIImage*)image withSize:(NSUInteger)sz forKey:(NSString*)key
{
// NSLog(#"count of insert image%d",sz);
ImageCacheObject *object = [[ImageCacheObject alloc] initWithSize:sz Image:image];
while (totalSize + sz > maxSize)
{
NSDate *oldestTime;
NSString *oldestKey;
for (NSString *key in [myDictionary allKeys])
{
ImageCacheObject *obj = [myDictionary objectForKey:key];
if (oldestTime == nil || [obj.timeStamp compare:oldestTime] == NSOrderedAscending)
{
oldestTime = obj.timeStamp;
oldestKey = key;
}
}
if (oldestKey == nil)
break; // shoudn't happen
ImageCacheObject *obj = [myDictionary objectForKey:oldestKey];
totalSize -= obj.size;
[myDictionary removeObjectForKey:oldestKey];
}
[myDictionary setObject:object forKey:key];
[object release];
}
-(UIImage*)imageForKey:(NSString*)key
{
ImageCacheObject *object = [myDictionary objectForKey:key];
if (object == nil)
return nil;
[object resetTimeStamp];
return object.image;
}
In ImageCacheObject.h
#interface ImageCacheObject : NSObject
{
NSUInteger size; // size in bytes of image data
NSDate *timeStamp; // time of last access
UIImage *image; // cached image
}
#property (nonatomic, readonly) NSUInteger size;
#property (nonatomic, retain, readonly) NSDate *timeStamp;
#property (nonatomic, retain, readonly) UIImage *image;
-(id)initWithSize:(NSUInteger)sz Image:(UIImage*)anImage;
-(void)resetTimeStamp;
In ImageCacheObject.m
#synthesize size;
#synthesize timeStamp;
#synthesize image;
-(id)initWithSize:(NSUInteger)sz Image:(UIImage*)anImage
{
if (self = [super init])
{
size = sz;
timeStamp = [[NSDate date] retain];
image = [anImage retain];
}
return self;
}
-(void)resetTimeStamp
{
[timeStamp release];
timeStamp = [[NSDate date] retain];
}
-(void) dealloc
{
[timeStamp release];
[image release];
[super dealloc];
}

Resources