I have a custom UITableViewCell, called EventCell.
EventCell.h
#import <UIKit/UIKit.h>
#interface EventCell : UITableViewCell
#property (nonatomic, strong) IBOutlet UILabel *titleLabel;
#property (nonatomic, strong) IBOutlet UILabel *locationLabel;
#property (nonatomic, strong) IBOutlet UILabel *dateLabel;
#property (nonatomic, strong) IBOutlet UILabel *typeLabel;
#end
EventCell.m
#import "EventCell.h"
#implementation EventCell
#synthesize titleLabel, locationLabel, dateLabel, typeLabel;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
Here is how I'm setting up my cell.
EventsMasterViewController.m
- (EventCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Event *myEvent;
NSString *CellIdentifier = #"EventCell";
EventCell *cell = (EventCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"EventCell" owner:nil options:nil];
for (id currentObject in topLevelObjects)
{
if ([currentObject isKindOfClass:[EventCell class]])
{
cell = (EventCell *)currentObject;
break;
}
}
myEvent = [self.myEventsDataController objectInListAtIndex:indexPath.row];
cell.titleLabel.text = myEvent.name;
cell.locationLabel.text = myEvent.location;
cell.typeLabel.text = #"Social";
cell.layer.borderColor = [UIColor blackColor].CGColor;
cell.layer.borderWidth = 1.0;
return cell;
}
The cell is being formatted great, it looks exactly as I need it to. But when I click on it, the cell highlights blue and doesn't segue to the next view. I put a breakpoint in my prepareForSegue method and it's not even getting called.
Is there some way to call prepareForSegue manually? If so, where should I do that.
You need to implement
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"YourSegueIdentifier" sender:nil];
}
You shouldn't have to implement didSelectRow for this to work - control-dragging from a cell to another view controller should create a segue that works when tapping the cell.
In my case, the problem was that the storyboard editor was creating the segue incorrectly. I had two very similar storyboards, one worked and the other didn't, and looking at the source code (right click on the storyboard file and choose Open As -> Source Code), I saw this:
<segue destination="UWX-rF-KOc" kind="replace" identifier="showStory" trigger="accessoryAction" splitViewControllerTargetIndex="1" id="Gly-La-KIe"/>
Note the trigger attribute. This suggests to me that the segue will be fired from the accessory action of the cell. You can even see this from the connections inspector of the cell:
Removing this and replacing the segue by dragging from the "selection" handle above instead of control-dragging from the cell gave me the correct segue.
I'm not sure what it is about this particular cell that makes control-dragging connect to the accessory action instead of the selection action, but there you go.
As stated above, you need to use didSelectRowAtIndexPath unless you configure a segue in your storyboard to a new UIViewController. This allows you to use the prepareForSegue function instead of the programmatic call of performSegueWithIdentifier.
Hope that helps clear it up!
My problem was the cell identifier.
So, in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath I declared static NSString *cellIdentifier = #"cell"; and then in storyBoard I added this identifier in here:
And here you go!
Related
I have two UITableViews using Storyboards in Xcode 7. I've set the delegate and dataSource using the Connections Inspector for both table views.
Let the first table view be the main table view and let the table views within each cell of the main table view be the detail table views with cell identifiers named appropriately and respectively.
When [tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath] executes, it immediately calls its dataSource method -cellForRowAtIndexPath: for DetailCell preventing me from setting a custom instance variable in time to add the appropriate data to each cell.
The following is a simplified example marked using comments.
MainTableViewController:
#implementation MainTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Keep in mind the following two (2) lines are set using the Connections Inspector
//cell.detailTableView.dataSource = cell;
//cell.detailTableView.delegate = cell;
// Stepping over the following line will jump to the
// other `-cellForRowAtIndexPath:` (below) used to set
// the detail info.
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
CustomObj *obj = self.mainData[indexPath.row];
cell.nameLabel.text = obj.name;
cell.additionalInfo = obj.additionalInfo; // This line is not set before instantiation begins for the detail table view...
return cell;
}
...
#end
DetailTableViewCell (contains a UITableView and implements appropriate protocols):
#interface DetailTableViewCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, weak) IBOutlet UILabel *nameLabel;
#property (nonatomic, weak) IBOutlet UITableView *detailTableView;
#property (nonatomic, strong) CustomObj *additionalInfo;
#end
#implementation DetailTableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell = (DetailTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"DetailCell" forIndexPath:indexPath];
// Instantiate detail ...
cell.detailLabel.text = self.additionalInfo.text;
// Problem!
// self.additionalInfo == nil thus we cannot set a value to the label.
return cell;
}
...
#end
The problem is when the detail -cellForRowAtIndexPath: method is called, I haven't had a chance to set a value for its dataSource, in this case, additionalInfo.
There are many possible ways to fix your problem, but first I would say that, your design seems not a good one, A UItableViewCell has another UITableView, and another UItableViewCell inside this UITableView? Why you do this? Just use one UITableView and put all of your views into one UItableViewCell as subViews should be enough.
Now get to your problem:
I would suggest not to use IBOutlet for setting up your delegate and dataSource, use code. This can give you a chance to delay setting the dataSource and delgate when you are ready. Once you think it's the proper time, just call [cell.detailTableView reloadData] will trigger your DetailTableViewCell to invoke cellForRowAtIndexPath
#implementation MainTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Keep in mind the following two (2) lines are set using the Connections Inspector
//cell.detailTableView.dataSource = cell;
//cell.detailTableView.delegate = cell;
// Stepping over the following line will jump to the
// other `-cellForRowAtIndexPath:` (below) used to set
// the detail info.
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
CustomObj *obj = self.mainData[indexPath.row];
cell.nameLabel.text = obj.name;
cell.additionalInfo = obj.additionalInfo; // This line is not set before instantiation begins for the detail table view...
// setup dataSource and delegate now
cell.detailTableView.dataSource = cell;
cell.detailTableView.delegate = cell;
// call reloadData whenever you think is proper
[cell.detailTableView reloadData];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = nil;
//Check this call is for which table view.
if(tableView == detailTableView) {
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
// Do any additional setup you want with MainCell
} else {
cell = (DetailTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"DetailCell" forIndexPath:indexPath];
// Do any additional setup you want with DetailCell
}
return cell;
}
I have a TabBarController with 4 tabs, 3 of which are table views. I am trying to put a detail for every table view cell, and I don't think storyboard is efficient since I have over 50 detail pages. I'm very new to all of this, and I've tried to find out how to link a detail to every tab for a couple hours. My table views start with the Second View Controller.
Here is SecondViewController.m:
#import "SecondViewController.h"
#implementation SecondViewController
{
NSArray *tableData;
}
#synthesize tableData;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
tableData = [NSArray arrayWithObjects:#"Carter", #"Greene", #"Hancock", #"Hawkins", #"Johnson", #"Sullivan", #"Unicoi", #"Washington", nil];
[super viewDidLoad];
}
#pragma mark - TableView Data Source methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
return [tableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyCell"];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
#end
Here is SecondViewController.h:
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController <UITableViewDelegate,
UITableViewDataSource>
#property(nonatomic, retain) NSArray *tableData;
#end
If this helps, here is my storyboard.
If anyone can help me individually add details to each table view cell in the most painless way possible, I would appreciate it. Thanks!
If using storyboards, the process is fairly simple.
First, I'd suggest dragging a prototype "table view cell" on to your table views. You can then control-drag from that prototype cell to your destination scene to add a segue between the cell and the next scene:
Make sure to select that prototype cell and set its storyboard identifier (I used "Cell"). You will need to reference that storyboard identifier, as shown in my code sample below. I also configured appearance related things (like the disclosure indicator) right in that cell prototype in IB so I don't have to worry about doing that in code and I can see what the UI will look like right in IB.
Now you can go to the table view controller and (a) simplify cellForRowAtIndexPath (because you don't need that logic about if (cell == nil) ... when using cell prototypes); but also implement a prepareForSegue to pass the data to the destination scene:
// SecondViewController.m
#import "SecondViewController.h"
#import "DetailsViewController.h"
#interface SecondViewController ()
#property (nonatomic, strong) NSArray *tableData;
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableData = #[#"Carter", #"Greene", #"Hancock", #"Hawkins", #"Johnson", #"Sullivan", #"Unicoi", #"Washington"];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[DetailsViewController class]]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSString *name = self.tableData[indexPath.row];
[(DetailsViewController *)segue.destinationViewController setName:name];
}
}
- (IBAction)unwindToTableView:(UIStoryboardSegue *)segue {
// this is intentionally blank; but needed if we want to unwind back here
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.tableData[indexPath.row];
return cell;
}
#end
Obviously, this assumes that you created a DetailsViewController and specified it as the destination scene's base class, and then create properties for any values you want to pass to this destination scene:
// DetailsViewController.h
#import <UIKit/UIKit.h>
#interface DetailsViewController : UIViewController
#property (nonatomic, copy) NSString *name;
#end
And this destination scene would then take the name value passed to it and fill in the UILabel:
// DetailsViewController.m
#import "DetailsViewController.h"
#interface DetailsViewController ()
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#end
#implementation DetailsViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.nameLabel.text = self.name;
}
#end
Frankly, this process will undoubtedly be described more clearly in any UITableView tutorial includes a discussion of "cell prototypes" (your code sample suggests you were using an older tutorial that predates cell prototypes).
I think the relationship between the code and the storyboard is as following:
Code implement the function of the application.
Storyboard contains many scenes, these scenes implement the User interface, including data presentation, data input, data output.
Code read data from these scenes and output the result to the scenes.
Code is internal logic function entities and the storyboard the the User Interface presentation.
Is there anyway to control UIButton state (enable/disable button) in UITableViewCell. My problem is my UIButton in cell is made in storyboard using viewWithTag. I've been spending quite a lot of time to sort it out but no luck. People mostly sort out the problem by programmatically assigning the tag for the button with cell indexPath.
I'm aware of that the table will reuse the cell, but I just want to ask if there is another hacky way to sort out my issue. If it is impossible, I might have to create the button programmatically.
You could loop through all the subviews of the cell and check if they are a UIButton using isMemberOfClass to get your button. If you have multiple buttons you could then check the text of the button or some other property that uniquely identifies it. That would be a hacky way to do it.
You have to make a custom cell like that:
CustomCell.h
#protocol CustomCellDelegate <NSObject>
- (void)buttonPressed:(UIButton *)sender;
#end
#import <UIKit/UIKit.h>
#interface CustomCell : UITableViewCell
#property (weak, nonatomic) id<CustomCellDelegate> delegate;
#property (weak, nonatomic) IBOutlet UIButton *button;
- (IBAction)buttonPressed:(UIButton *)sender;
#end
CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)prepareForReuse{
self.button.enable = YES;
}
- (IBAction)buttonPressed:(UIButton *)sender{
[self.delegate buttonPressed:sender];
}
#end
after in IB you add a new UITableViewCell at your UITableView and the class of it with you new custom cell set the Identify ID like "CustomCell" add your Button to your custom cell and connect the Outlet, then you modify you tableView:cellForRowAtIndexPath: like that:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier=#"CustomCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.delegate = self;
return cell;
}
- (void)buttonPressed:(UIButton *)sender{
sender.enable = NO;
}
Also you have to add the CustomCellDelegate in your controller's heater file
One simple way would be to keep a NSMutableArray variable in your viewcontroller and with that keep track of what cells button is disabled/enabled. And use the UITableViewDataDelegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
to set the buttons state each time it gets displayed. And UITableViewDelegate method:
– tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)tableViewCell forRowAtIndexPath:(NSIndexPath *)indexPath
to write to the array. Indexing with indexPath.
I've got a button in a custom Cell for a TableView which is supposed to open the camera for taking pictures.
I thought of two ways but can't get them working.
First one is to open an instance of UIImagePickerController from within the cell. Well, it seems like I can't call
[self presentViewController...];
from within the cell. Is this right?
Because of this "result" I thought of putting the method which opens up the UIImagePickerController inside the TableViewController and then call this method from within the cell (where the button is located) by something like
[super openCamera];
Or making the TableViewController the delegate of the cell to enable it to call the method.
Are these ideas going in the right direction? What would you recommend? Thank you very much!
Ok, I figured something out but I'm still wondering if it can be done easier.
Here is the solution I found:
In the custom cell I added
#property (nonatomic, assign) id adminController;
Then in the tableViewController I customized the following method to use the custom cell I created and set the tableViewController als "admin"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
CreateCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.adminController = self;
return cell;
}
So I could finally call
[self.adminController performSelector:#selector(openCamera)];
This is an old question but I'd like to have my old questions answered too so... Yes, there is an easier way using blocks:
First, declare a public method in your UITableViewCell interface:
#interface YourCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UIButton *button;
- (void)setDidTapButtonBlock:(void (^)(id sender))didTapButtonBlock;
#end
In the UITableViewCell subclass implementation file declare a private property with a copy attribute.
#import "YourCell.h"
#interface YourCell ()
#property (copy, nonatomic) void (^buttonTappedBlock)(id sender);
#end
Add the target and action of the UIControl in the UITableViewCell constructor and implement the selector method
- (void)awakeFromNib {
[super awakeFromNib];
[self.button addTarget:self
action:#selector(didTapButton:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)didTapButton:(id)sender {
if (buttonTappedBlock) {
buttonTappedBlock(sender);
}
}
Finally implement the block code in the tableView:cellForRowAtIndexPath: method in the controller
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YourCell *cell = (YourCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
[cell buttonTappedBlock:^(id sender) {
NSLog(#"%#", item[#"title"]);
}];
return cell;
}
For further information in blocks, you can read Working With Blocks
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.