Trouble with Custom UITableViewCells and UITableView - ios

I have implemented a Twitter feed in a UITableViewController and I'm using a custom cell with a UITextView in it so it has link detection. The problem I'm having is that only the first cell shows up and if I start scrolling the app crashes with EXC_BAD_ACCESS on cell.tweetText.text = t[#"text"];. The feed shows up correctly if I don't use a custom cell, but then you can't tap on a link.
TweetCell.h
#import <UIKit/UIKit.h>
#interface TweetCell : UITableViewCell <UITextViewDelegate>
#property (weak, nonatomic) IBOutlet UITextView *tweetText;
#end
tableviewcontroller.h
#import <UIKit/UIKit.h>
#interface GRSTwitterTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *twitterFeed;
#end
tableviewcontroller.m
#import "GRSTwitterTableViewController.h"
#import "TweetCell.h"
#import "STTwitter.h"
#interface GRSTwitterTableViewController ()
#end
#implementation GRSTwitterTableViewController
#synthesize twitterFeed;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"";
self.tableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStyleGrouped];
UIBarButtonItem *openTwitterButton = [[UIBarButtonItem alloc]initWithTitle:#"Open in Twitter" style:UIBarButtonItemStylePlain target:self action:#selector(openTwitter:)];
self.navigationItem.rightBarButtonItem = openTwitterButton;
STTwitterAPI *twitter = [STTwitterAPI twitterAPIAppOnlyWithConsumerKey:#"xz9ew8UZ6rz8TW3QBSDYg" consumerSecret:#"rm8grg0aIPCUnTpgC5H1NMt4uWYUVXKPqH8brIqD4o"];
[twitter verifyCredentialsWithSuccessBlock:^(NSString *username)
{
[twitter getUserTimelineWithScreenName:#"onmyhonorband" count:50 successBlock:^(NSArray *statuses)
{
twitterFeed = [NSMutableArray arrayWithArray:statuses];
[self.tableView reloadData];
}
errorBlock:^(NSError *error)
{
NSLog(#"%#", error.debugDescription);
}];
}
errorBlock:^(NSError *error)
{
NSLog(#"%#", error.debugDescription);
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)openTwitter:(id)sender
{
NSURL *twitterURL = [NSURL URLWithString:#"twitter:///user?screen_name=onmyhonorband"];
NSURL *safariURL = [NSURL URLWithString:#"https://twitter.com/onmyhonorband"];
if ([[UIApplication sharedApplication]canOpenURL:twitterURL])
{
[[UIApplication sharedApplication]openURL:twitterURL];
}
else
{
[[UIApplication sharedApplication]openURL:safariURL];
}
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [twitterFeed count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
tableView.backgroundColor = [UIColor darkGrayColor];
TweetCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
if (!cell)
{
[tableView registerNib:[UINib nibWithNibName:#"TweetCell" bundle:nil] forCellReuseIdentifier:#"myCell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
}
NSInteger idx = indexPath.row;
NSDictionary *t = twitterFeed[idx];
cell.tweetText.text = t[#"text"];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#end
Here is a screenshot of what the tableview looks like when using the custom cell.

replace the line code
TweetCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
if (!cell)
{
[tableView registerNib:[UINib nibWithNibName:#"TweetCell" bundle:nil] forCellReuseIdentifier:#"myCell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
}
with the below code
TweetCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
if (!cell)
{
NSArray *nibs =[[NSBundle mainBundle] loadNibNamed:#"TweetCell" owner:self options:NULL];
cell = [nibs firstObject];
}

Related

update UITableViewCell from other method

i have search thousand in GG to find solution update data to UITableViewCell but all show me the solution is
UITableViewCell *cell=(UITableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
but the cell is nil for all cells that are visible. I have use NSNotification to send data from one method to ViewController.m , and the Reiever method i want update data to cell by indexPath. but all cell is nil and cannt not update that.
here my code
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property(nonatomic, strong) IBOutlet UITableView *tableView;
#end
ViewController.m
#implementation ViewController
{
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(theReciever:) name:#"theSender" object:nil];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [recipes count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
NSLog(#"cell nil");
}
NSString *idgame=#"Gamexyz";
cell.textLabel.text = idgame;
cell.tag=indexPath.row;
return cell;
}
-(void)theReciever:(NSNotification*)notif{
if([notif.object isKindOfClass:[packeData class]]){
packeData *data=[notif object];
NSString *key=data.key;
NSInteger *index=[key integerValue];
NSIndexPath *indexPath=[NSIndexPath indexPathWithIndex:index];
UITableViewCell *cell=(UITableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
//UITableViewCell *cell=(UITableViewCell*)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:7 inSection:0]];
if(cell==nil)
{
NSLog(#"cell NULL");
}else{
cell.textLabel.text=data.process;
}
}else{
NSLog(#"ERR: object not recognised");
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
any one can help me or solution example for update data in UITableViewCell by indexPath
NOTE: below is just an example u can do it in new project
One thing u need to change the data model packeData, lets say it contains key as NSIntager which holdes the index of the cell and process is NSString which holds the progress as string value for example
in packeData.h
#import <Foundation/Foundation.h>
#interface packeData : NSObject
#property (nonatomic, assign) NSInteger key; //holds index
#property (nonatomic, strong) NSString *process; //holds the progress info
#end
and in packeData.m
#import "packeData.h"
#implementation packeData
- (id)init //simply initialise it
{
self = [super init];
if(self)
{
}
return self;
}
#end
and in view controller where u are tableview,
in ViewController.h
#import <UIKit/UIKit.h>
#import "packeData.h"
#interface ViewController : UIViewController <UI TableViewDataSource,UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *aTableView;
#property (strong,nonatomic) NSMutableArray *recipes; //array acts as datasource
#end
in in ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(theReciever:) name:#"theSender" object:nil];
_recipes = [[NSMutableArray alloc]init]; //initilise your datasource
for(int j = 0 ;j< 20;j++)
{
// for my example i took some values
//initially put some initial values
packeData *data = [[packeData alloc] init];
data.key = j;
data.process = [NSString stringWithFormat:#"game_name_%d",j];
[_recipes addObject:data];
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[NSTimer scheduledTimerWithTimeInterval:0.8 target:self selector:#selector(PostNotification) userInfo:nil repeats:YES]; //just for testing
}
- (void)PostNotification
{
//i am simply posting the notification with some random values
packeData *data = [[packeData alloc]init];
data.key = arc4random()%15;
data.process = [NSString stringWithFormat:#"%ld",( data.key + 20)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"theSender" object:data];
}
- (void)theReciever:(NSNotification *)notif
{
if([notif.object isKindOfClass:[packeData class]]){
packeData *data=[notif object];
NSInteger key=data.key;
NSInteger index= key;
//modify the datasource
packeData *recipes_data = [_recipes objectAtIndex:index]; //get the pocket present in array
recipes_data.process = data.process; //modify the recipes data
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell=(UITableViewCell*)[self.aTableView cellForRowAtIndexPath:indexPath];
if(cell==nil)
{
NSLog(#"cell NULL");
}else
{
[self.aTableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
// cell.textLabel.text=data.process; no need u already mofied the content in the datasource this will call the "cellForRowAtIndexPath" method and displays the process in place of game name
}
}else{
NSLog(#"ERR: object not recognised");
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_recipes count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell* cell = [self.aTableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
NSLog(#"cell nil");
}
packeData *idgame= [_recipes objectAtIndex:indexPath.row];
cell.textLabel.text = idgame.process; //initially contains game name
cell.tag=indexPath.row;
return cell;
}
#end
EDIT
replace the below methods
- (void)PostNotification
{
//i am simply posting the notification with some random values
packeData *data = [[packeData alloc]init];
data.key = arc4random()%15; //15 change the number of rows
data.process = [NSString stringWithFormat:#"%ld",( data.key + arc4random() % 100)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"theSender" object:data];
}
- (void)theReciever:(NSNotification *)notif
{
if([notif.object isKindOfClass:[packeData class]]){
packeData *data=[notif object];
NSInteger key=data.key;
NSInteger index= key;
//modify the datasource
packeData *recipes_data = [_recipes objectAtIndex:index]; //get the pocket present in array
recipes_data.process = data.process; //modify the recipes data
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell=(UITableViewCell*)[self.aTableView cellForRowAtIndexPath:indexPath];
if(cell==nil)
{
NSLog(#"cell NULL");
[self.aTableView reloadData]; //if cell is not visible then reload the whole table
}else
{
[self.aTableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
// cell.textLabel.text=data.process; no need u already mofied the content in the datasource this will call the "cellForRowAtIndexPath" method and displays the process in place of game name
}
}else{
NSLog(#"ERR: object not recognised");
}
}
Edit 2
as for testing just change the below method and as soon as simulator launches the app scroll to down so that only top 5 rows only updates, wait for 5 to 10 seconds and scroll to top and u will see all the calls are updates with same process 5
//scroll down as soon as launches the app and wait for 5 to 10 seconds then scroll to top u will see top 5 cells are updates with progress 5
- (void)PostNotification
{
packeData *data = [[packeData alloc]init];
data.key = arc4random()%5; //only top 5 cells are modify other wont modify
data.process = [NSString stringWithFormat:#"%ld",5];//updates with some same progress lates give it as 5 //( data.key + arc4random() % 100)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"theSender" object:data];
}
form the above test u will see the top 5 cells are updates even when they are not visible
You can't set the value of any of your cell's controller apart from cellForRowAtIndexPath you have to populate the UITableViewCell data with an array, then when you want to update the data in your cell, update your array according to data, then update the single cell of your UITableView like this.
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
Just specify your index path of your row and reload...
NSIndexPath* path = [NSIndexPath indexPathForRow:3 inSection:0];
NSArray* rowsToReload = [NSArray arrayWithObjects:path, nil];
[tableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];

Select one row in each section of UITableView ios?

Scenario:
I have made 2 sections in one UITableView and the user needs to select a row in each section as shown in the screenshot below.
Expected Outcome:
1. User should be able to select a row in each section
Outcome right now:
1. After I have selected row in one section, and then when I select the row in second section, the first selection disappears.
Here is my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Uncheck the previous checked row
long sec=indexPath.section;
if(sec==0){
if(self->checkedIndexPath)
{
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:self->checkedIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
if([self->checkedIndexPath isEqual:indexPath])
{
self->checkedIndexPath = nil;
}
else
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self->checkedIndexPath = indexPath;
}}
if(sec==1){
if(self->checkedIndexPath)
{
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:self->checkedIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
if([self->checkedIndexPath isEqual:indexPath])
{
self->checkedIndexPath = nil;
}
else
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self->checkedIndexPath = indexPath;
}
}
}
Help is appreciated.
This is the simplest way.
Finally i found a solution. It works for me, hope it will work for you.
declare these
#interface ViewController ()
{
int selectedsection;
NSMutableArray *selectedindex;
}
Replace didSelectRowAtIndexPath as follows:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Uncheck the previous checked row
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
if(self.checkedIndexPath)
{
for (int i=0; i<[selectedindex count]; i++) {
NSIndexPath *temp= [selectedindex objectAtIndex:i];
if (temp.section==selectedIndexPath.section) {
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:temp];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
}
NSInteger numb= [tableView numberOfRowsInSection:selectedIndexPath.section];
if (selectedsection==selectedIndexPath.section) {
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:self.checkedIndexPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
}
if([self.checkedIndexPath isEqual:indexPath])
{
for (int i=0; i<[selectedindex count]; i++) {
NSIndexPath *temp= [selectedindex objectAtIndex:i];
if (temp.section==selectedIndexPath.section) {
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:temp];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
}
self.checkedIndexPath = nil;
}
else
{
for (int i=0; i<[selectedindex count]; i++) {
NSIndexPath *temp= [selectedindex objectAtIndex:i];
if (temp.section==selectedIndexPath.section) {
UITableViewCell* uncheckCell = [tableView
cellForRowAtIndexPath:temp];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
}
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.checkedIndexPath = indexPath;
[selectedindex addObject:indexPath];
selectedsection=indexPath.section;
NSLog(#"check");
}
}
You can enable the multiple selection in the tableview:
self.tableView.allowsMultipleSelection = YES;
I wrote a sample code where a compound datasource holds datasource objects for each section. Sounds complicated but actually provides an easy to extend architecture. And keeps your view controller small.
The advantages of this approach:
Small ViewController
ViewController set ups view and handles user interaction — as it should be in MVC
Reusable datasources
by using different datasources per section easy to customize cells for each section
the base datasource architecture
This provides easy extension and is simple to reuse.
#import UIKit;
#interface ComoundTableViewDataSource : NSObject
#property (nonatomic,strong, readonly) NSMutableDictionary *internalDictionary;
-(void) setDataSource:(id<UITableViewDataSource>)dataSource forSection:(NSUInteger)section;
-(instancetype)initWithTableView:(UITableView *)tableView;
#end
#import "ComoundTableViewDataSource.h"
#interface ComoundTableViewDataSource () <UITableViewDataSource>
#property (nonatomic,strong, readwrite) NSMutableDictionary *internalDictionary;
#property (nonatomic, weak) UITableView *tableView;
#end
#implementation ComoundTableViewDataSource
-(instancetype)initWithTableView:(UITableView *)tableView
{
self = [super init];
if (self) {
_tableView = tableView;
tableView.dataSource = self;
_internalDictionary = [#{} mutableCopy];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
return self;
}
-(void)setDataSource:(id<UITableViewDataSource>)dataSource forSection:(NSUInteger)section
{
self.internalDictionary[#(section)] = dataSource;
[self.tableView reloadData];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.internalDictionary allKeys] count];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id<UITableViewDataSource> sectionDataSource = self.internalDictionary[#(section)];
return [sectionDataSource tableView:tableView numberOfRowsInSection:section];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id<UITableViewDataSource> sectionDataSource = self.internalDictionary[#(indexPath.section)];
return [sectionDataSource tableView:tableView cellForRowAtIndexPath:indexPath];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
id<UITableViewDataSource> sectionDataSource = self.internalDictionary[#(section)];
return [sectionDataSource tableView:tableView titleForHeaderInSection:section];
}
#end
#import UIKit;
#interface SingleSectionDataSource : NSObject <UITableViewDataSource>
#property (nonatomic, strong, readonly) NSArray *array;
#property (nonatomic, strong, readonly) UITableView *tableView;
- (instancetype)initWithArray:(NSArray *)array;
#end
#import "SingleSectionDataSource.h"
#interface SingleSectionDataSource ()
#property (nonatomic, strong, readwrite) NSArray *array;
#property (nonatomic, strong, readwrite) UITableView *tableView;
#end
#implementation SingleSectionDataSource
- (instancetype)initWithArray:(NSArray *)array
{
self = [super init];
if (self) {
self.array = array;
}
return self;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
self.tableView = tableView;
return self.array.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.array[indexPath.row];
return cell;
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [#(section) stringValue];
}
#end
the selection datasource architecture
We extend the classes from above to allow one selection per section
#import "ComoundTableViewDataSource.h"
#interface OnSelectionPerSectionComoundTableViewDataSource : ComoundTableViewDataSource
-(void)selectedCellAtIndexPath:(NSIndexPath *)indexPath;
#end
#import "OnSelectionPerSectionComoundTableViewDataSource.h"
#import "SingleSelectionSingleSectionDataSource.h"
#implementation OnSelectionPerSectionComoundTableViewDataSource
-(instancetype)initWithTableView:(UITableView *)tableView
{
self = [super initWithTableView:tableView];
if(self){
[tableView setAllowsMultipleSelection:YES];
}
return self;
}
-(void)selectedCellAtIndexPath:(NSIndexPath *)indexPath
{
SingleSelectionSingleSectionDataSource *sectionDataSource = self.internalDictionary[#(indexPath.section)];
[sectionDataSource selectedCellAtIndexPath:indexPath];
}
#end
View Controller Implementation
As promised, a very slim view controller:
#interface ViewController () <UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (nonatomic, strong) OnSelectionPerSectionComoundTableViewDataSource *tableViewDataSource;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableViewDataSource = [[OnSelectionPerSectionComoundTableViewDataSource alloc] initWithTableView:self.tableView];
self.tableView.delegate = self;
[self.tableViewDataSource setDataSource:[[SingleSelectionSingleSectionDataSource alloc] initWithArray:#[#"Hallo", #"Welt"]] forSection:0];
[self.tableViewDataSource setDataSource:[[SingleSelectionSingleSectionDataSource alloc] initWithArray:#[#"Hello", #"World", #"!"]] forSection:1];
[self.tableViewDataSource setDataSource:[[SingleSelectionSingleSectionDataSource alloc] initWithArray:#[#"Hola", #"Mundo", #"!", #"¿Que tal?"]] forSection:2];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableViewDataSource selectedCellAtIndexPath:indexPath];
}
#end
You will want to add methods to the datasources to get the selected rows.
get the example: https://github.com/vikingosegundo/CompoundDatasourceExample
Note This code has a cell reuse issue. It is fixed on GitHub.

How to delete UITableViewCell from within same cell?

I have a UITableView with custom table view cell.
That cell has a custom delete button. Button action is within the cell class.
I can't reload the table from cell class after deleted row from db. If I try to get table view from cell.superview or cell.superview.superview, the app crashes.
This is cell implementation
#implementation pixSavedTableViewCell
- (IBAction)deleteBusiness:(id)sender {
NSLog(#"DELETING BUSINESS cell");
self.hidden=YES;
pixDBManager *dbConnection = [[pixDBManager alloc]init];
dbConnection.businessName = self.nameLable.text;
[dbConnection deleteBusiness];
//[(UITableView *)self.superview.superview reloadData];
}
#end
This will delete data from
-(void)deleteBusiness
{
NSLog(#"DELETING BUSINESS");
char *error;
if(sqlite3_open([dbPathString UTF8String], &businessDB)==SQLITE_OK){
NSString *deleteStatement = [NSString stringWithFormat:#"DELETE FROM SAVEDBUSINESS WHERE 'NAME'='%#'",_businessName];
const char *delete_stmt = [deleteStatement UTF8String];
NSLog(#"%s",delete_stmt);
if (sqlite3_exec(businessDB, delete_stmt, NULL, NULL, &error)==SQLITE_OK) {
NSLog(#"Business Deleted");
}
else{
NSLog(#"Cant Delete Data");
}
sqlite3_close(businessDB);
}
else{
NSLog(#"Cant Open Data Base");
}
}
This is my view controller implementation
#implementation pixSavedViewController
{
NSMutableArray *businessArray;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
pixDBManager *dbConnection = [[pixDBManager alloc]init];
businessArray = [[NSMutableArray alloc]init];
[dbConnection createOrOpenDB];
businessArray = [dbConnection getSavedBusiness];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return businessArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *customcellidentifier = #"CustomCell3";
pixSavedTableViewCell *cell = (pixSavedTableViewCell *)[tableView dequeueReusableCellWithIdentifier:customcellidentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"Savedcell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
pixBusinessDetails *details = [[pixBusinessDetails alloc]init];
details = [businessArray objectAtIndex:indexPath.row];
cell.nameLable.text = details.name;
cell.addressLable.text = details.address;
cell.descriptionLable.text = details.description;
cell.scoreLable.text = details.score;
cell.votesLable.text = details.votes;
NSLog(#"Business Detail %#\n %#\n %#\n %#\n %#\n %#\n",details.name, details.address,details.description,details.score,details.votes,details.imageId);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 130;
}
Rather than deleting the data from within the UITableViewCell, you should instead call a method in your UIViewController which will delete the data and then reload the table.
On way to do this is to create a protocol for your UIViewController to conform to:
#protocol pixSavedTableViewCellDelegate
- (void)deleteBuisness:(NSString *) businessName
#end
#interface pixSavedTableViewCell : UITableViewCell
#property (nonatomic, weak) id<pixSavedTableViewCellDelegate> delegate;
#end
Then in you UITableViewCell you change your delete method to:
- (IBAction)deleteBusiness:(id)sender {
[self.delegate deleteBuisness:self.nameLabel.text];
}
In your UIViewController you then implement the protocol:
#interface pixSavedViewController <pixSavedTableViewCellDelegate>
#end
#implementation pixSavedViewController
#pragma mark - pixSavedTableViewCellDelegate
- (void)deleteBuisness:(NSString *) businessName
{
pixDBManager *dbConnection = [[pixDBManager alloc]init];
dbConnection.businessName = businessName;
[dbConnection deleteBusiness];
[self.tableView reloadData];
}
You'll also need to set the delegate when creating the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//All your current code
cell.delegate = self;
return cell;
}
This is what the delegate pattern is for, in my opinion:
Make your UITableViewController a delegate of your custom cell.
MyCustomCell *cell = ....
cell.deleteDelegate = self
//in your UITableViewController
- (void)willDeleteCustomCell:(MyCustomCell*)cell
{
}
//on your cell
- (IBAction)deleteBusiness:(id)sender
{
[self.deleteDelegate willDeleteCustomCell:self];
//rest of your delete code
}

how to pass the NSMutableArray to UITableView

data values i have one text filed and two buttons.One button is save,second button is show.
when click save button data will be Stored in Core-Data and then i click Show button data will be displayed on Console. It gone nice.
But now i want when Click show button data will be displayed on UITableView So Please Give me any idea
Thanks in advanced,
this is my code:-
#import "SetViewController.h"
#import "AppDelegate.h"
#interface SetViewController ()
{
NSMutableArray *array;
}
#end
#implementation SetViewController
#synthesize textField;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)save:(id)sender {
AppDelegate *appD=(AppDelegate *) [[UIApplication sharedApplication]delegate];
NSEntityDescription *entit=[NSEntityDescription insertNewObjectForEntityForName:#"Team" inManagedObjectContext:appD.managedObjectContext];
[entit setValue:textField.text forKey:#"fname"];
NSError *error;
BOOL isSaved=[appD.managedObjectContext save:&error];
NSLog(#"succesfully saved flag: %d",isSaved);
}
- (IBAction)show:(id)sender {
AppDelegate *appDelegat=(AppDelegate*)[[UIApplication sharedApplication]delegate];
NSEntityDescription *entity=[NSEntityDescription entityForName:#"Team" inManagedObjectContext:appDelegat.managedObjectContext];
NSFetchRequest *fetchRr = [[NSFetchRequest alloc]init];
[fetchRr setEntity:entity];
array=[[appDelegat.managedObjectContext executeFetchRequest:fetchRr error:nil]mutableCopy];
NSLog(#"array %#",array);
for (NSManagedObject *obj in array) {
NSLog(#"Name :%#\n",[obj valueForKey:#"fname"]);
}
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
#end
i am tried like this :-
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ShowData"])
{
ShowViewController *destViewController = segue.destinationViewController;
destViewController.second = [[array valueForKey:#"fname"] componentsJoinedByString(angry)" , "];
NSLog(#" got data for array %#",destViewController.second);
}
}
ShowViewController.h:-
#import <UIKit/UIKit.h>
#import "SetViewController.h"
#interface ShowViewController : UITableViewController<UITableViewDelegate,UITableViewDataSource>
#property (nonatomic, strong) NSString *second;
#end
ShowViewController.m:-
import "ShowViewController.h"
#interface ShowViewController ()
{
NSMutableArray *acess;
}
#end
#implementation ShowViewController
#synthesize second;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
acess =[second componentsSeparatedByString:#", "];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [acess count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
}
cell.textLabel.text = [acess objectAtIndex:indexPath.row];
return cell;
}
ShowViewController is UITableView.When Click UItableView is open but data will be not Displayed So Please give me any idea
Follow following steps and also read programming guide of UITableview provided by apple:
After getting the array just reload your table view.
Make number of rows = array count.
Customize the cell as per your need.
If you only declared the array as per you shown in code then you must need to initialise it in view did load like this:
array=[NSMutablearray array];
after that get the values from db and stored in array an use UITable view delegate and datasource methods.
First declare your array in your viewDidload() method.
- (void)viewDidLoad
{
yourArray=[[NSArray alloc]initWithObjects:Obj1,obj2,obj3,obj4,obj5, nil];
}
and then use tableview delegate methods. to set array data to tableview.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [yourArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
// Configure the cell...
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
cell.textLabel.text=[yourArray objectAtIndex:indexPath.row];
return cell;
}
If you already have an NSArray that contains your strings, there is no need to go through the whole componentsJoinedByString/componentsSeparatedByString - you can just pass the array to your second view controller.
Accessing the passed data in viewDidLoad won't work though because when you set the property in prepareForSegue the view has already loaded.
Your prepareForSegue should be -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ShowData"])
{
ShowViewController *destViewController = (ShowViewController *)segue.destinationViewController;
destViewController.fnames = [array valueForKey:#"fname"];
}
}
In your .h file for ShowViewController declare a property to receive frames -
#property (strong,nonatomic) NSArray *fnames;
Then your table data source methods will be -
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.fnames count];
}
-(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] ;
}
cell.textLabel.text = [self.fnames objectAtIndex:indexPath.row];
return cell;
}

Passing data from dynamic tableview back to static tableview

Please help i have been struggling passing back the data. I have 2 tableViews. 1st tableview=static table=RootVC. 2nd tableview=dynamic table=FirstVC. in RootVC i have a cell with two labels, "repeatLabel" and "repeatDetail" with a disclosure indicator. When i click on the cell it display the next table which is FirstVC, FistVC is populated with weekdays. after selection of my choice, i want the selected days to be passed back into RootVC in "repeatDetail" and when i go back still be able to see previously selected data.
My RootVC looks like this:
#import "RepeatViewController.h"
#interface SettingsViewController : UITableViewController
#property (strong, nonatomic) IBOutlet UILabel *repeatDetail;
#property (strong, nonatomic) IBOutlet UILabel *repeatLabel;
#property (strong,nonatomic) NSString *getRepeatDetail;
#property (nonatomic, strong) NSMutableArray *selectedDaysArray;
#end
in my RootVC.m
#import "SettingsViewController.h"
#interface SettingsViewController ()
#end
#implementation SettingsViewController
#synthesize repeatLabel,repeatDetail;
#synthesize getRepeatLabel;
#synthesize selectedDaysArray;
- (void)viewDidLoad
{
[super viewDidLoad];
repeatLabel.text = #"Repeat";
repeatDetail.text = getRepeatLabel;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
RepeatViewController *destinationController = segue.destinationViewController;
if( [destinationController isKindOfClass:[RepeatViewController class]] )
{
//You can reuse your selectedDays arrays
destinationController.selectedDays = self.selectedDaysArray;
[(RepeatViewController *)destinationController setCompletionBlock:^(NSArray *retDaysArray) // <- make this change
{
// Save your changes
self.selectedDaysArray = [NSMutableArray arrayWithArray: retDaysArray]; // <- make this change
NSLog(#"retDaysArray: %#", self.selectedDaysArray); //<- Add this debug line
}];
}
}
#end
My 1stVC.h
#import "SettingsViewController.h"
typedef void(^WeekdayCompletionBlock)(NSArray *retDaysArray);
#interface RepeatViewController : UITableViewController <UITableViewDataSource,UITableViewDelegate>
#property (nonatomic,strong) NSMutableArray *selectedDays;
#property (nonatomic, copy) NSArray *completionBlock;
#property (copy) WeekdayCompletionBlock returnBlock;
//#property (strong, nonatomic) IBOutlet UIBarButtonItem *saveButton;
-(IBAction)save:(id)sender;
#end
my 1stVC.m
#import "RepeatViewController.h"
#interface RepeatViewController ()
#end
#implementation RepeatViewController
#synthesize selectedDays= _selectedDays;
#synthesize completionBlock;
#synthesize returnBlock;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
completionBlock = [NSArray arrayWithObjects:#"Sunday", #"Monday", #"Tuesday", #"Wednesday", #"Thursday", #"Friday", #"Saturday", nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 7;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"RepeatCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
NSString *day = completionBlock[indexPath.row];
cell.textLabel.text = day;
if ([self.selectedDays containsObject:day])
cell.accessoryType = UITableViewCellAccessoryCheckmark;
else
cell.accessoryType = UITableViewCellAccessoryNone;
//cell.textLabel.text = [completionBlock objectAtIndex:indexPath.row];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!self.selectedDays)
self.selectedDays = [[NSMutableArray alloc] init];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark)
{
cell.accessoryType = UITableViewCellAccessoryNone;
//remove data from array
[self.selectedDays removeObject:[completionBlock objectAtIndex:indexPath.row]];
}
else
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
//add data to array
[self.selectedDays addObject:[completionBlock objectAtIndex:indexPath.row]];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
-(IBAction)save:(id)sender
{
NSUserDefaults *myNewWeekString = [NSUserDefaults standardUserDefaults];
[myNewWeekString setObject:self.selectedDays forKey:#"MY_KEY_FOR_ACCESING_DAYSOFWEEK"];
[myNewWeekString synchronize];
//NSLog(#"The selected day/s is %#",self.selectedDays);
if (self.returnBlock)
{
self.returnBlock(self.selectedDays);
}
[self.navigationController popViewControllerAnimated:YES];
// NSLog(#"The selected day/s is %#",self.selectedDays);
// if (self.returnBlock)
// {
// self.returnBlock([completionBlock objectAtIndex:indexPath.row]);
//}
}
/*
-(void) setReturnBlock:(WeekdayCompletionBlock)returnBlock
{
[self.selectedDays addObject:(self.returnArray);
}
- (NSArray *)setDats
{
return [NSArray arrayWithArray:[self.selectedDays copy]];
}*/
#end
When you work with static cells you have to bind the control you are using directly, there's no need.
So what I can suggest you is the following:
Bind your controls with some specific identifier, like labelFieldRow{rowid} example: labelFieldRow1.
So on prepare for segue, just check what's the selected row and pass the data you want to the destination controller.
Probably not the best, but it should work.
You have to pass data (selected by user before) from your RootVC to FirstVC. To do that in your RootVC add property to keep the selected data;
#property (nonatomic, strong) NSMutableArray *selectedDaysArray;
In prepareForSegue method you have to pass that array to let the table view know what needs to be selected:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destinationController = segue.destinationViewController;
if( [destinationController isKindOfClass:[RepeatViewController class]] )
{
//You can reuse your selectedDays arrays
((RepeatViewController*)destinationController).selectedDays = self.selectedDaysArray;
[(RepeatViewController *)destinationController setReturnBlock:^(NSArray *retDaysArray) // <- make this change
{
// Save your changes
self.selectedDaysArray = [NSMutableArray arrayWithArray: retDaysArray]; // <- make this change
NSLog(#"DATA: %#", self.selectedDaysArray) //<- Add this debug line
}];
}
}
Remove this line from viewDidLoad you don't want to allocate it every time now you just pass it from rootVC
_selectedDays = [[NSMutableArray alloc] init];
And in cellForRowInIndexPath replace this line:
cell.textLabel.text = [completionBlock objectAtIndex:indexPath.row];
with this code:
NSString *day = completionBlock[indexPath.row];
cell.textLabel.text = day;
if ([self.selectedDays containsObject:day])
cell.accessoryType = UITableViewCellAccessoryCheckmark;
else
cell.accessoryType = UITableViewCellAccessoryNone;
And change didSelectRowAtIndexPath: to
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!self.selectedDays)
self.selectedDays = [[NSMutableArray alloc] init];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark)
{
cell.accessoryType = UITableViewCellAccessoryNone;
//remove data from array
[self.selectedDays removeObject:[completionBlock objectAtIndex:indexPath.row]];
}
else
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
//add data to array
[self.selectedDays addObject:[completionBlock objectAtIndex:indexPath.row]];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Hope this help.

Resources