I am having some trouble trying to share a NSMutableArray of Strings to another class. I have a tableView that is populated with Strings that I would like to add to a NSMutableArray. Then use that SAME NSMutableArray in another ViewController
I have created a class with the subclass of NSMutableArray
.h
#import <Foundation/Foundation.h>
#interface HYServicesMArray : NSMutableArray
#property (nonatomic, weak)NSMutableArray * arrServicesUserChoice;
#end
.m
#import "HYServicesMArray.h"
#implementation HYServicesMArray
#dynamic arrServicesUserChoice;
#end
I am trying to add elements to this NSMutableArray from a tableView didSelectRowAtIndexPath:
.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"You have selected: %#",cell.textLabel.text);
// add cell.textLabel.text to arrServicesUserChoice
// Tried the code below but causes my app to crash
arrServicesUserChoice = [[NSMutableArray alloc]init];
[arrServicesUserChoice addObject:cell.textLabel.text];
}
How ever I am unable to add elements to the arrServicesUserChoice. I am stuck please help! Thank you in advance!
YOu declared a mutable array, and the code you used to add returns an array, but it dont add anyting. you have to add the objects like:
[arrAvaialbleServicesList addObject:cell.textlabel.text];
After this, your mutable array will have the added data
or you can declare another array, and do it your way"
NSArray *test=[arrAvaialbleServicesList arrayByAddingObject:cell.textLabel.text];
after this, the new array will have the array with added data.
I think you should change
#dynamic arrServicesUserChoice;
To
#synthesize arrServicesUserChoice;
The original initialization to MutableArray does nothing really and does not specify arrAvalabelServiceList as Mutable - arrayByAddingObject creates a brand new instance which is NSArray, NOT NSMutableArray
arrAvaialbleServicesList = [[NSMutableArray alloc]init];
[arrAvaialbleServicesList arrayByAddingObject:cell.textLabel.text];
You are however not assigning this new instance to anything, if you did:
arrAvaialbleServicesList = [arrAvaialbleServicesList arrayByAddingObject:cell.textLabel.text];
You would at least get a handle to the new NSArray instance.
What you should probably to is to create your list when you setup the table:
arrAvaialbleServicesList = [[NSMutableArray alloc]init];
The on your didSelect you would just add the text to the list:
NSString *selected = [NSString stringWithString:cell.textLabel.text];
[arrAvaialbleServicesList addObject:selected];
or something close to this then you would add something to your list whenever you select a row in the table.
What you try to create is a kind of public static var correct? i think the better way to do that is creating an Class that can handle your MutableArray object. Doing her as a singleton class is a good way to guarantee that you will not create another instance of your object and lost your MutableArray. Here is a sample:
HYServicesMArray.h
#import <Foundation/Foundation.h>
#interface HYServicesMArray : NSObject
#property (nonatomic, strong) NSMutableArray *arrServicesUserChoice;
+ (HYServicesMArray *) instance;
#end
HYServicesMArray.m
#import "HYServicesMArray.h"
#implementation HYServicesMArray
+ (HYServicesMArray *)instance
{
static HYServicesMArray *_ServicesMArray = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_ServicesMArray = [[self alloc] init];
});
return _ServicesMArray;
}
-(id)init {
self.arrServicesUserChoice = [[NSMutableArray alloc] init];
return self;
}
#end
And the application on your didSelectRowAtIndexPath method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"You have selected: %#",cell.textLabel.text);
[[HYServicesMArray instance].arrServicesUserChoice addObject:cell.textLabel.text];
}
You should make it #synthesized and dereference the property as self.arrServicesUserChoice. (You can leave the self away only in Swift.)
This should compile:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"You have selected: %#",cell.textLabel.text);
self.arrServicesUserChoice = [[NSMutableArray alloc] init];
[self.arrServicesUserChoice addObject:cell.textLabel.text];
}
You do not need to declare it #dynamic, You need to #synthesize it. If you don't want to #synthesize it then use self. You can check the difference between here between these two here.
#synthesize vs #dynamic, what are the differences?
If you want to use this same array in another class then you should declare a property in that class and pass this class's array to another class using segue.
Related
I try to search this problem in this site and I found this link How to insert items to a UITableView when a UIButton is clicked in iOS. But my problem is, I already copy the code on that link and It doesn't reload the data when I insert a value to my array.
here's the code "ViewController.m"
#import "ViewController.h"
#interface ViewController ()
#property(nonatomic,strong) NSMutableArray * array;
#property(nonatomic,weak) IBOutlet UITableView * tableView;
#end
#implementation ViewController
-(NSMutableArray *) array{
if(_array==nil){
_array=[[NSMutableArray alloc] init];
}
return _array;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (IBAction)addInfo:(UIBarButtonItem *)sender {
[self.array addObject:#"sample"];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"TodoListItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [self.array objectAtIndex:indexPath.row];
return cell;
}
#end
here's the code "ViewController.h"
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
#end
Is there something wrong with my code or there is something that I need to setup to my tableview? I'm very confuse of this and try to figure out the missing part of my code. I'm still studying the code of object c and I'm still noob for this. Please help me and thanks in advance.
How did you insert the UITableView?, on nib or programatically?, either way you need to flag the table's delegate and datasource, if on nib, right click on table and see that delegate and data source are connected to the files owner, if programatically check
self.MyTable.datasource = self
self.MyTable.delegate = self
please see how to check if connected [dataSource and delegate should be with a dot, if not click on circle and drag line to file's owner for both]
table delegate and datasource on interface builder "NIB"
Also please note that on this image outlet is not connected, you have to connected also to call the reload as you are doing now
edit, check if the button is connected?, put a log or a break point on ibaction for your button to know if is called
edit 2, you have to init the array, are you calling it? do this in view will appear or when you want to use it
edit 3, try this
- (IBAction)addInfo:(UIBarButtonItem *)sender {
if(self.array==nil){
self.array=[NSMutableArray array];
}
[self.array addObject:#"sample"];
[self.tableView reloadData];
}
I'm building an iPad app that will have a table view in one sector of the window with other labels, images, buttons, etc elsewhere. I know how to create a tableView when using a tableViewController as the class of the scene, but can't figure out how to populate the table cells when the table view is embedded in a scene with a UIViewController class.
I have updated my .h file as follows:
#interface SKMainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
I'm returning data from an asynchronous API call into the viewController, but can't figure out how to initiate the required tableView methods (tableView:numberOfRowsAtIndexPath and tableView:cellForRowAtIndexPath). How can I trigger them to run once I've got my data back? Is there a simple method call I don't know about?
I've dissected a functioning UITableViewController class and don't see what triggers the population of the table cells.
EDITED: Adding large sections of code for your review. I've trimmed out the other imports and property declarations to simplify.
This is SKMainViewController.h:
#import "SKWelcomeViewController.h"
#import "SKAgenda.h"
#import "SKAgendaManager.h"
#import "SKAgendaCommunicator.h"
#import "SKAgendaManagerDelegate.h"
#import "SKAgendaTableViewCell.h" // I'm using a custom cell
#interface SKMainViewController : UIViewController <SKFlipsideViewControllerDelegate, UIPopoverControllerDelegate, NSURLConnectionDelegate, SKGreetingManagerDelegate, SKWeatherManagerDelegate, UITableViewDataSource, UITableViewDelegate>
{
NSArray *_agenda;
SKAgendaManager *_aManager;
}
#pragma mark Agenda Detail
#property (strong, nonatomic) NSArray *agendaItems;
#property (strong, nonatomic) IBOutlet UITableView *agendaTableView;
#end
This is SKMainViewController.m:
#import "SKMainViewController.h"
#interface SKMainViewController ()
#end
#implementation SKMainViewController
- (void)viewDidLoad
{
[self startFetchingAgenda:_agendaItems];
}
-(void)startFetchingAgenda:(NSNotification *)notification
{
NSInteger deviceID = [[NSUserDefaults standardUserDefaults] integerForKey:#"deviceID"];
if(deviceID == 0)
{
// todo: add error handling here
NSLog(#"Bad stuff happened");
}
[_aManager fetchAgendaForDeviceID:deviceID];
}
-(void)didReceiveAgenda:(NSArray *)agendaItems
{
NSLog(#"Received these agenda items: %#", agendaItems);
_agendaItems = agendaItems;
if(agendaItems.count == 0)
{
// set defaults to populate a single cell
NSLog(#"No Items!");
}
else
{
// populate data and present
NSLog(#"Some Items!");
agendaTableView.delegate = self; // Error Here: use of undeclared identifier 'agendaTableView'
agendaTableView.dataSource = self; // Error Here: use of undeclared identifier 'agendaTableView'
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [_agendaItems count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.agendaSpinner startAnimating];
self.agendaSpinner.hidden = NO;
static NSString *tableIdentifier = #"agendaTableCell";
SKAgendaTableViewCell *cell = (SKAgendaTableViewCell *)[tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if(cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"agendaTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// set the data related to the agenda item
SKAgenda *agendaDetails = [_agendaItems objectAtIndex:indexPath.row];
cell.agendaTitle.text = agendaDetails.title;
cell.agendaDescription.text = agendaDetails.description;
NSString *eventTimes = [NSString stringWithFormat:#"%# - %#", agendaDetails.start, agendaDetails.end];
cell.agendaTimes.text = eventTimes;
//todo: add functionality for background and text colors. Will need to use RGB colors instead of HEX. Change in webiste.
// todo: accommodate for no events in the agenda
[self.agendaSpinner stopAnimating];
self.agendaSpinner.hidden = YES;
return cell;
}
Thanks.
Just set the data source and delegate:
myTableView.delegate = self;
myTableView.dataSource = self;
Where self is your SKMainViewController that adopts the UITableViewDataSource and UITableViewDelegate protocols.
If at any time you'd like to reload your table data:
[myTableView reloadData];
Yes, the simple method is reloadData. This causes the table view to call its data source methods. You need to put it in a completion block (or delegate that's called after the data is received) if you're using an asynchronous api.
I am trying to implement the concept shown in this example project. My goal is to separate my view controller class and the datasource protocol. Instead of implementing the table view datasource methods in my table view controller class, I try to put it in its own class, and in my view controller, I only call this method to set up my table view:
- (void)setupTableView
{
void (^configureCell)(JVRTodoItemCell *, JVRTodoItem *) = ^(JVRTodoItemCell *cell, JVRTodoItem *todoItem)
{
[cell configureForTodoItem:todoItem];
};
NSArray *todoItems = currentUser.todoItems;
self.todoArrayDataSource = [[JVRArrayDataSource alloc] initWithItems:todoItems withCellIdentifier:TodoCellIdentifier withConfigureCellBlock:configureCell];
self.tableView.dataSource = self.todoArrayDataSource;
[self.tableView registerClass:[JVRTodoItemCell class] forCellReuseIdentifier:TodoCellIdentifier];
}
The data source is separated into its own class:
#interface JVRArrayDataSource ()
#property (copy,nonatomic) NSArray *items;
#property (copy,nonatomic) NSString *cellIdentifier;
#property (copy,nonatomic) void (^configureCellBlock)(id item, id cell);
#end
#implementation JVRArrayDataSource
...
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
self.configureCellBlock(cell,item);
return cell;
}
The interesting part is that creating the cell based on the identifier (using dequeueReusableCellWithIdentifier:forIndexPath:) seems to be successful, because the correct cell gets allocated, but its labels remain nil. I try setting up my cell using the following method, but the values remain nil (aTodoItem has valid properties):
- (void)configureForTodoItem:(JVRTodoItem *)aTodoItem
{
self.todoItemTitle.text = aTodoItem.title;
self.todoItemPriority.text = [NSString stringWithFormat:#"%d", aTodoItem.priority];
}
I am trying to figure out what could possibly be missing here, but so far, I haven't managed to fix the issue yet, and I'm starting to lose hope. Any help would be appreciated.
UPDATE:
To make it clear, the issue is shown on this picture.
It seems that the cells get created, but its labels don't.
If all you want to do is separate your tableview datasource delegate from the view controller you can create a separate class called TableViewDataSource. Within that class you can manage the datasources and their table view cells; configuring the them in your view controller, but letting the TableViewDataSource manage them.
TDSTableViewDataSource.h
#import <Foundation/Foundation.h>
#protocol TDSTableViewDataSourceDelegate <NSObject>
- (NSString *)fetchCellIdentifierForObject:(id)object;
- (UITableViewCell *)configureCell:(UITableViewCell *)cell usingObject:(id)item;
#end
#interface TDSTableViewDataSource : NSObject <UITableViewDataSource>
#property (strong, nonatomic) NSArray *items;
#property (strong, nonatomic) id<TDSTableViewDataSourceDelegate> delegate;
#end
TableViewDataSource.m
#import "TDSTableViewDataSource.h"
#implementation TDSTableViewDataSource
- (NSArray *)items {
if (!_items) _items = [[NSArray alloc] init];
return _items;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if ([self.items count]) {
return [self.items count];
} else {
NSLog(#"numberOfSectionsInTableView could not be determined. self.items is nil or empty.");
return 0;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if ([self.items count]) {
return [self.items count];
} else {
NSLog(#"numberOfRowsInSection could not be determined. self.items contains fewer section requested does not contain any items.");
return 0;
}
}
/*
Single dimension Array of items belonging to a UITableView section
The method checks if the cell implements the HZConfigureTableViewCellDelegate, which is required.
The delegate should be the View Controller.
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
id obj = [self.items objectAtIndex:indexPath.row];
UITableViewCell *cell = nil;
if ([self.delegate conformsToProtocol:#protocol(TDSTableViewDataSourceDelegate)]) {
NSString *cellIdentifier = [self.delegate fetchCellIdentifierForObject:obj];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (obj)
cell = [self.delegate configureCell:cell usingObject:obj];
}
return cell;
}
#end
This class and the protocol will essentially allow you to fetch and configure UITableViewCell's and not have to implement the protocols into your View Controller.
Inside your view controller, you create a datasource property using the protocol above.
#import "TDSViewController.h"
#import "TDSTableViewDataSource.h"
#interface TDSViewController () <UITableViewDelegate, TDSTableViewDataSourceDelegate>
#property (strong, nonatomic) TDSTableViewDataSource *dataSource; // UITableView data source.
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation TDSViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.dataSource = self.dataSource;
self.dataSource.delegate = self;
}
#pragma mark - UITableView methods
-(NSString *)fetchCellIdentifierForObject:(id)object {
// Check if this is an event or a Reminder class.
if ([object isKindOfClass:[UITableViewCell class]]) {
// Return the cell identifier for this particular cell.
return #"com.myapp.defaultcell";
}
return #"blankcell";
}
- (UITableViewCell *)configureCell:(UITableViewCell *)cell usingObject:(id)item {
UITableViewCell *configuredCell = cell;
// Check if this is an event or a reminder.
if ([item isKindOfClass:[UITableViewCell class]]) {
// Configure the cell to present what data we want here...
}
return configuredCell;
}
#end
This is a complete example project. You can use this to configure any kind of cell you want, without having to add the datasource methods to your view controllers.
The view controller is used by the ConfigureTableViewCellDelegate protocol to configure the UITableViewCell's and use them in the Table View. Since the code is segregated now, the TableViewDataSource class now handles presenting the data to the table view. The View Controller is simply used to configure the cell's. This allows you to use custom UITableViewCells' on each ViewController if you want, and not have to deal with implementing the data sources each time.
UPDATED
Provided a better example, a complete project template.
In the ViewDidLoad register the nib, it fix the problem :)
-(void)viewDidLoad
{
[self.leftTableView registerNib:[UINib nibWithNibName:NIB_FILE bundle:nil] forCellReuseIdentifier:CELL_IDENTIFIER];
}
After hours of digging, I've managed to solve the issue (for now), by changing my custom cell's outlets to strong properties, and initializing them in the cell's init method:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.todoItemTitle = [[UILabel alloc] init];
self.todoItemPriority = [[UILabel alloc] init];
}
return self;
}
This is very strange, since I thought that creating my views in storyboard, this should be automatically taken care of, and I've never had to do this manually before.
I have a NSMutableArray of seven bools in managerViewController, these represent days of the week when a shop is open. I am running out of space in this view and most of the time the user will be happy with the default setting of open all hours.
the user needs to be able to change them to suit their business needs, my current approach to this is to have a uitableview of seven rows all of which have switches in them. where I am stuck is how to the actions of in uitableview modify the original nsmuntable array in the manageViewController class.
I am new to iOS, but I have built the UITableView and all the other bits, it is just accessing the NSMutableArray I am stuck on.
Use replaceObjectAtIndex:withObject: or, with the new objective-c literals it's much easier:
NSMutableArray *myArray = [NSMutableArray array];
myArray[0] = #(YES); // the same as: [myArray addObject:[NSNumber numberWithBool:YES]];
myArray[0] = #(NO); // the same as: [myArray replaceObjectAtIndex:0 withObject:[NSNumber numberWithBool:YES]];
Define a delegate to your table view controller, sayTableViewControllerDelegate. Add a delegate property to your table view controller
#property (weak, nonatomic) id delegate;
Make your ManagerViewController conform to the delegate protocol, and upon showing TableViewController, set its delegate to be your ManagerViewController instance.
Add a method in the delegate to inform the action on a day, for example:
- (void) didModifyDay:(NSInteger)dayNumber;
or
- (void) didChangeDay:(NSInteger)dayNumber toState:(BOOL)selected;
When table view is loading in the
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
make sure to check initial values for all of the switch and set values from your data source (the mutable array). For easily handle the situation use a custom and put its object in the array.
#interface Days : NSObject
#property (nonatomic, retain) NSString *day;
#property (nonatomic, assign) BOOL isOpen;
#end
i.e.
for(int ii = 0; ii < 7; ii++ ){
Days *days = [[Days alloc] initWithDay:ii isOpen:YES];
[array addObject:days];
[days release];
}
Assume that you have a custom UITableViewCell which have a property set to the switch.
i am using cell.switch for example.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"reusableCells";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(cell == nil)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
Days *days = [array objectAtIndex:indexPath.row];
cell.textLabel.text = days.day;
[cell.switch setOn:days.isOpen animated:YES];
return cell;
}
and this will handle your switch.
now assume you toggle the switch on/off when press the table cell so handle the didSelectRowAtIndexPath method of the tableview delegate
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
Days *days = [array objectAtIndex:indexPath.row];
days.isOpen != days.isOpen;
[cell.switch setOn:days.isOpen animated:YES];
}
That's all. you will get the switch toggle on/off and the will be manipulated from the array.
You can use the app delegate class of your application to access the NSMutableArray.
In the app delegate class create the property for the class in which that NSMutableArray is present for e.g let say class name is TestController.
//then in .h file of Appdelegate
#import TestController.h
//in the interface
TestController *objTest;
//declare the property
#property (nonatomic, strong) TestController *objTest;
//then in the .m file of app delegate synthesize the object
#synthesize objTest;
Now in the similar way declare the property for NSMutableArray in the TestController
Create the share instance of the app delegate class where you want to access the NSMutable array object(say arr1) as below
AppDelegate *objAppDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
You can access that array as "objAppDelegate.objTest.arr1"
Or
You can create the share instance in the view controller class
//in the .h of view controller
+ (id)sharedInstance;
//in the .m of view controller
+ (id)sharedInstance
{
// structure used to test whether the block has completed or not
static dispatch_once_t p = 0;
// initialize sharedObject as nil (first call only)
__strong static id _sharedObject = nil;
// executes a block object once and only once for the lifetime of an application
dispatch_once(&p, ^{
_sharedObject = [[self alloc] init];
});
// returns the same object each time
return _sharedObject;
}
Then in the application where you fill the array use,
TestViewController *objTest=[TestViewController sharedInstance];
objTest.arr1=[[NSMutableArray alloc]initWithObjects:#"1",#"1",#"1",#"1", nil];
And in the table view controller you can access it as below
TestViewController *objTest=[TestViewController sharedInstance];
objTest.arr1 will be the resulted array.
I'm figuring out the right mechanism to pass data from UITableViewCells to a UIableViewController (or UIViewController).
Searching within stackoverflow I found different ways to do this and finally I found a mechanism that works well but I don't know if it could be a valid approach.
This is the scenario. First, I created a custom cell (associated with a xib interface), named DataTableViewCell, that extends UITableViewCell. This cell has some outlet to display (and modify) data and an addictional property called index like the following:
#property (nonatomic, copy) NSIndexPath* index;
This property is refreshed inside the method cellForRowAtIndexPath method within the UITableViewController:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
DataTableViewCell *cell = (DataTableViewCell*)[tv dequeueReusableCellWithIdentifier:kCellTableIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"DataTableViewCell" owner:self options:nil];
cell = (DataTableViewCell*)self.dataTableViewCellOutlet;
}
// configure the cell with data
// do stuff here...
// configure the cell with the current indexPath
cell.index = indexPath;
return cell;
}
Since it is possible to change values within a cell, I had the need to pass data to the UITableViewController for updating the model. To do that I decided to use a delegate mechanism. So, I created a protocol with a method like the following:
- (void)updateData:(DataItem*)dataItem atIndexPath:(NSIndexPath*)index;
The UITableViewController implements that protocol. In this way, within the cell (against to some events), I can call that method and update the model in the correct way.
Having said this, I have some questions to ask:
Is this a right mechanism to pass data from a cell to a controller?
Is it correct to using an index property like the one use in the cell?
Is it the same using retain policy instead of copy policy. What could be the difference?
Since the solution I found could be very scheming, is it possible to use block insteads?
About blocks, I thought this way:
Within the cell create a property block like the following:
#property (nonatomic, copy) void (^updateModelOnEvent)(DataItem* dataItem);
Then inside the method cellForRowAtIndexPath method within the UITableViewController assign that property to a block code like this (this code is at the same level of cell.index = indexPath;):
// configure the cell with the current indexPath
cell.updateModelOnEvent = ^(DataItem* dataItem) {
[self.model insertObject:dataItem atIndex:indexPath.row];
};
Could be a valid alternative? In this case, do I have to use copy or retain policy?
Thank you in advance.
Why not just use [UITableView indexPathForCell:] with a delegate?
MyViewController.h
#interface MyViewController : UITableViewController <MyTableViewCellDelegate>
#end
MyViewController.m
#implementation MyViewController
// methods...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier = #"MyCell";
MyTableViewCell *cell = (id)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil)
cell = [[[MyTableViewCell alloc] initWithMyArgument:someArgument reuseIdentifier:reuseIdentifier] autorelease];
[cell setDelegate:self];
// update the cell with your data
return cell;
}
- (void)myDelegateMethodWithCell:(MyTableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// update model
}
#end
MyTableViewCell.h
#protocol MyTableViewCellDelegate;
#interface MyTableViewCell : UITableViewCell
#property (assign, nonatomic) id <MyTableViewCellDelegate> delegate;
- (id)initWithMyArgument:(id)someArgument reuseIdentifier:(NSString *)reuseIdentifier;
#end
#protocol MyTableViewCellDelegate <NSObject>
#optional
- (void)myDelegateMethodWithCell:(MyTableViewCell *)cell;
#end
MyTableViewCell.m
#implementation MyTableViewCell
#synthesize delegate = _delegate;
- (id)initWithMyArgument:(id)someArgument reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
if (self) {
// custom setup
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.delegate = nil;
}
- (void)someActionHappened {
if ([self.delegate respondsToSelector:#selector(myDelegateMethodWithCell:)])
[self.delegate myDelegateMethodWithCell:self];
}
#end
To modify cells you should modify data model and reload table data. Nothing else.
Not necessary to have a indexPath for cell
In your case it is the same using retain or copy policy. Copy makes new objects with same state.