I have a view based table with a custom cell view. Inside this custom cell view, I have a custom NSButton that acts like a check box (it toggles a custom image on and off). This part works well. The images toggle on and off perfectly.
What I want to do is associate the custom button in the row with the actual row in the table. When I check/click the button it will highlight the the corresponding row and then perform an action on the row in which the check box/ button is situated. For example, removing the row from the table when the NSButton is clicked.
My custom NSButton is implemented as follows:
Header file:
#import <Foundation/Foundation.h>
#interface CustomCheckButton : NSButton {
BOOL _checked;
}
#property (nonatomic, setter=setChecked:) BOOL checked;
-(void) setChecked:(BOOL) check;
#end
Implementation:
#import "CustomCheckButton.h"
#implementation CustomCheckButton
#synthesize checked = _checked;
-(id) init
{
if( self=[super init] )
{
self.checked = NO;
[self setTarget:self];
[self setAction:#selector(onCheck:)];
}
return self;
}
-(void) awakeFromNib
{
self.checked = NO;
[self setTarget:self];
[self setAction:#selector(onCheck:)];
}
-(void) setChecked:(BOOL) check
{
_checked = check;
if( _checked )
{
NSImage* img = [NSImage imageNamed:#"check_on.png"];
[self setImage:img];
[self setState:NSOnState];
}
else
{
NSImage* img = [NSImage imageNamed:#"check_off.png"];
[self setImage:img];
[self setState:NSOffState];
}
}
-(void) onCheck:(id) sender
{
self.checked = !_checked;
NSLog(#"A check box was pressed");
}
#end
The current solution does not associate the button with the row at all. When I sort the rows, for example, the selected image is not linked to the row and often stays in the same place within the table, even though a different row was selected.
Any help on this would be greatly appreciated.
How about adding a delegate field and setting it with a custom initializer? Or you could add it as a property and set it after calling alloc init. This can be done in 2 ways: via IB or programmatically.
If you want to do it programmatically, you'll need to do a couple of things:
So let's look at the class that has a UITableView element in it. Assuming this class is also the UITableViewDelegate and UITableViewDataSource, it will have to have an implementation of the data source method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
which returns a cell at a given indexPath. Here, you could instantiate your custom cell and in that, add a custom button and set it's delegate to the cell itself, by adding this to your code:
in CustomCheckButton.h:
UITableViewCell * delegate;
//...
#property (nonatomic, assign) UITableViewCell * delegate;
in CustomCheckButton.m
#synthesize delegate;
in the table view data source method:
// initialize my custom cell element, assume it has a property for a CustomCheckButton with name checkButton
// ... some code
myCell.checkButton.delegate = mycell; // link the button's delegate to the cell
// ... some other code, probably based on indexPath
return myCell;
This might seem like a big task, but you can do it in interface builder too, assuming you have a xib for your custom cell class. If you do, just open the xib. You'll probably have a button on it already; change it's class to your own CustomCheckButton and you'll be able to set its delegate property like you would normally, with ctrl-drag from the button to the cell.
Hope this helps!
Related
In the viewDidLoad in my UITableView I am calling:
[self registerNib:[UINib nibWithNibName:#"MyCell" bundle:nil] forCellReuseIdentifier:#"MyCellRI"];
Then in the cellForRowAtIndexPath I have the following:
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCellRI"];
[cell setup:[items objectAtIndex:indexPath.row]];
Inside the setup method I have a look on [self subviews] to go through the subviews and assign content based on key/value but in this loop I find UITableViewCellContentView's but I do not see the my custom view defined in the NIB.
In the NIB I have assigned Files Owner & The custom class of my cell. The cell is showing I just can't find the NIB view in the subviews.
Please advise.
James
Iterating through subviews is hard work) Try to avoid it. it is not very good practice because it is not reliable. For instance if you decided to change your custom cell UI you will have to change method where you're iterating through subviews.
Try to do following:
Let's say that there is custom cell with UIImage and 3 strings (e.g. person's name, age and cell phone number)
Implement 3 functions in your custom cell to set this values as follows:
MyCustomCell.h
... .h file standart code and then at the end ...
#propert(nonatomic, strong) IBOutlet UIImageView *myImage;
#propert(nonatomic, strong) IBOutlet UILabel *myFirstLabel;
#propert(nonatomic, strong) IBOutlet UILabel *mySecondLabel;
#propert(nonatomic, strong) IBOutlet UILabel *myThirdLabel;
- (void)setUIimage:(UIImage *)_image;
- (void)setFirstString:(NSString *)_string;
- (void)setSecondString:(NSString *)_string;
- (void)setThirdString:(NSString *)_string;
MyCustomCell.m:
#synthesize myImageView, myFirstLabel, mySecondLabel, myThirdLabel;
- (void)setUIimage:(UIImage *)_image {
self.myImageView.image = _image;
}
- (void)setFirstString:(NSString *)_string {
self.myFirstLabel.text = _string;
}
- (void)setSecondString:(NSString *)_string {
self.mySecondLabel.text = _string;
}
- (void)setThirdString:(NSString *)_string {
self.myThirdLabel.text = _string;
}
and finaly in your tableView controller cellForRowAtIndexPath
[myCustomCell setUiImage:someImage];
[myCustomCell setFirstString:oneString];
[myCustomCell setSecondString:twoString];
[myCustomCell setThirdString:threeString];
Hope this approach will help you. Feel free to ask if you have some questions
I am creating an app where you press a button and it opens up your contacts list. You can then select the contact you want to add and it imports their name and email into the app. I currently have that information going into labels but I want to add it to a table view cell. How would I do this?
My Code:
.h:
#import <UIKit/UIKit.h>
#import <AddressBookUI/AddressBookUI.h>
#interface FirstViewController : UIViewController <ABPeoplePickerNavigationControllerDelegate>
- (IBAction)showPicker:(id)sender;
#property (weak, nonatomic) IBOutlet UILabel *firstName;
#property (weak, nonatomic) IBOutlet UILabel *email;
#end
.m:
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
#synthesize firstName;
#synthesize email;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)showPicker:(id)sender {
ABPeoplePickerNavigationController *picker =
[[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
}
- (void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
[self displayPerson:person];
[self dismissModalViewControllerAnimated:YES];
return NO;
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
return NO;
}
- (void)displayPerson:(ABRecordRef)person
{
NSString* name = (__bridge_transfer NSString*)ABRecordCopyValue(person,
kABPersonFirstNameProperty);
self.firstName.text = name;
ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty);
NSString *emailId = (__bridge NSString *)ABMultiValueCopyValueAtIndex(emails, 0);//0 for "Home Email" and 1 for "Work Email".
self.email.text = emailId;
}
#end
OK, I am going to explain how you programmatically implement a very basic table view controller. It will be up to you, though, to figure out how to integrate this into your application.
Let's start with the header file, let's call it MyTableViewController.h:
#interface MyTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
}
#end
As you can see, your controller class adopts the protocols UITableViewDelegate and UITableViewDataSource.
Now let's look at a first snippet from the implementation file MyTableViewController.m. Your first job, obviously, is to create the controller's view. You do this in your controller's loadView method. If you want to learn more about the view life cycle and how to program a UIViewController I suggest you read the UIViewController class reference and the accompanying View Controller Programming Guide.
- (void) loadView
{
// Give the view some more or less arbitrary initial size. It will be
// resized later when it is actually displayed
CGRect tableViewFrame = CGRectMake(0, 0, 320, 200);
UITableView* tableView = [[[UITableView alloc] initWithFrame:tableViewFrame style:UITableViewStyleGrouped] autorelease];
self.view = tableView;
// Here we make sure that the table view will take as much horizontal
// and vertical space as it can get when it is resized.
UIViewAutoresizing autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
tableView.autoresizingMask = autoresizingMask;
// We need to tell the table view that we are both its delegate and
// its data source.
tableView.delegate = self;
tableView.dataSource = self;
}
Just to let you know: You can omit loadView entirely if your controller is a subclass of UITableViewController, but I deliberately do not take that shortcut so that I can show you how a table view needs a delegate and a data source. Most important ist the data source.
In the next snippet in MyTableViewController.m we are going to implement some basic UITableViewDataSource methods. For this you need to understand how a table view is structured: A table view is divided into sections, and each section has a number of cells. The point of having sections is to visually separate groups of cells, with an optional section header or footer. I am not going into details here, though, to keep this simple.
- (NSInteger) numberOfSectionsInTableView:(UITableView*)tableView
{
// Let's keep it simple: We want just one section
return 1;
}
- (NSInteger) tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
// Let's keep it simple: We want just one row, or table view cell.
// Since we only have one section (see above) we don't have to look
// at the section parameter.
return 1;
}
And now, finally, the centerpiece where you create your table view cell. Again, this is a UITableViewDataSource method that we implement. Note that we do not need to inspect the indexPath parameter only because we know that we only have one section and one row. In a real world application you will probably have to write switch-case or if-else statements that examine indexPath.section and indexPath.row so that you can distinguish between the different cells you need to create.
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
// This is very important for your future table view implementations:
// Always ask the table view first if it already has a cell in its
// cache. If you don't do this your table view will become slow when
// it has many cells.
NSString* identifier = #"MyTableViewCell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
{
// Aha, the table view didn't have a cell in its cache, so we must
// create a new one. We use UITableViewCellStyleValue1 so that the
// cell can display two pieces of information.
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier] autorelease];
}
// Regardless of whether we got the cell from the table view's cache
// or create a new cell, we must now fill it with content.
// First, obtain the information about the person from somewhere...
NSString* personName = ...;
NSString* personEmail = ...;
// ... then add the information to the table cell
cell.textLabel.text = personName;
cell.detailTextLabel.text = personEmail;
return cell;
}
As a final nicety, we implement a UITableViewDelegate method:
- (void) tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
// Here you can react to the user tapping on the cell. If you
// don't want the user to be able to select a cell you can
// add the following line to tableView:cellForRowAtIndexPath:
// cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
It is difficult to tell how you should integrate this into your application. It all depends where you want to display the table view. Since you say you want to replace the two labels you already have, one possible approach could be this:
In Interface Builder, add the table view as a subview to the main view of your FirstViewController
Add an outlet to FirstViewController that you connect to the table view
Let FirstViewController adopt the protocols UITableViewDelegate and UITableViewDataSource
Connect FirstViewController to the delegate and data source outlets of the table view
Don't implement loadView from my example, you don't need it, you already have made all the connections etc. in Interface Builder
If you need further help with integration, I suggest that you ask a new question and possibly refer to this answer. Good luck.
I have two view controller. In first view controller I have list of names. When I click on that, I want the same name to be displayed in second view controller.
I have below code.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// PropDetailViewController is second view controller
PropDetailViewController *prop = [self.storyboard instantiateViewControllerWithIdentifier:#"prop"];
ListOfProperty *propList = [propListFinal objectAtIndex:indexPath.row];
NSString *myText = [NSString stringWithFormat:#"%#", propList.addressOfFlat];
prop.detailLabel.text = myText;
prop.title = myText;
[self.navigationController pushViewController:prop animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
and in PropDetailViewController, I have #property (retain, nonatomic) IBOutlet UILabel *detailLabel;.
What I was expecting is when I click say Name 1, I will see Name 1 as text in UILabel and on the UINavigationBar too. However I only see Name 1 on navigation bar and not on UILabel.
It is not advisable to access an UIView item at that point in the program flow. When setting the value of prop.detailLabel.text the view may not have been loaded. When the view is loaded later then the UIView is updated with the default settings given in the XIB file in IB.
You should rather set an NSString property, lets say
#property (retain, nonatomic) NSString *propName;
assign it before pushing the view controller as you do. But use this property and not the UILable. And in PropDetailViewController in viewDidLoad do the following:
(void) viewDidLoad {
// call super viewDidLoad and all the works ...
self.detailLabel.text = propName;
}
Instead of viewDidLoad you could use viewWillAppear. Because viewDidLoad COULD be executed already when you assign the property's value.
If you want to be on the save side then invent a new init method where you hand over all the values that you want to be set upfront.
But I never did that in combination with storyboard (where you may use instantiate... rather than init...) and therefore I cannot give any advise out of the top of my head.
Another clean approach would be to stick with the propName property but to implement a custom setter -(void)setPropName:(NSString)propName; where you set the property (probably _propName if you autosynthesize) AND set the UILable text plus setting the UILable text within viewDidLoad.
Try this:
in .h
#property (retain, nonatomic) NSString *detailText;
in PropDetailViewController.m
Change line of code with
NSString *myText = [NSString stringWithFormat:#"%#", propList.addressOfFlat];
prop.detailText = myText;
prop.title = myText;
in ViewDidLoad:
[self.detailLabel setText:self.detailText];
The Problem:
The built-in editButtonItem that Xcode automatically comments out when a new UITableViewController class is created does not work when I delete the comment slashes (//). By does not work I mean that the edit button does not appear at all. Swiping a cell does not work either.
Attempted Solutions:
I have tried to follow the various workarounds that have been posted on other stackoverflow threads to no avail. Most of the posts that I have found talk about various aspects of the edit button not working (e.g., no minus signs showing up, etc…) but very few that I have found in which the edit button does not show up at all.
Hunch:
I have a hunch that it might have something to do with the UITableViewController not being properly implemented. I am very new to object-oriented programming as well as objective-c, so I apologize if the answer is something very basic—but hey, it's part of the learning process. Any help is much appreciated.
Code:
____.h
#import <UIKit/UIKit.h>
#import "IndividualRecipeViewController.h"
#class BrowsePrivateRecipeViewController;
#protocol BrowsePrivateRecipeViewControllerDelegate
- (void)browsePrivateRecipeViewControllerDidFinish:(BrowsePrivateRecipeViewController *)controller;
#end
#interface BrowsePrivateRecipeViewController : UITableViewController
#property (weak, nonatomic) id <BrowsePrivateRecipeViewControllerDelegate> delegate;
#property (assign, nonatomic) NSUInteger listLength;
#property (strong, nonatomic) NSDictionary *dictionaryOfRecipes;
#property (strong, nonatomic) NSMutableArray *arrayOfRecipeNames;
// ... methods
#end
____.m
#interface BrowsePrivateRecipeViewController ()
#end
#implementation BrowsePrivateRecipeViewController
#synthesize delegate = _delegate;
#synthesize listLength = _listLength;
#synthesize dictionaryOfRecipes = _dictionaryOfRecipes;
#synthesize arrayOfRecipeNames = _arrayOfRecipeNames;
- (void)viewDidLoad
{
// ... code here
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
// ... other methods
UPDATE:
LINK TO SOURCE CODE
So I have decided to post the source code to my whole project. I am having this problem with multiple files, but if I get it fixed in one, I am pretty sure that the rest will fall into place.
Please focus on the files BrowsePrivateRecipeViewController.m/.h. This is the most straightforward instance of the problem.
Once again thank you for your patience and help.
Sincerely,
Jason
First of all, I would definately not use a custom button for editing the table. It's unnecessary simply because there's already one built in.
Just use UIViewControllers editButtonItem.
If you have to perform additional stuff on button press, override -setEditing:animated: and call super first.
The error you mentioned above is caused because you're trying to access the navigationBars navigationItem, which does not exist. You should access your view controller's navigationItem.
self.navigationItem.rightBarButtonItem = self.editButtonItem;
You need to make a button first. This will make an Edit button then add it to the rightBarButtonItem spot.
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editTable)];
self.navigationItem.rightBarButtonItem = editButton;
You then need to set up a method to turn on the table's edit mode.
- (void)editTable
{
[self.tableView setEditing:YES animated:YES];
}
Update:
Just read your question again and noticed you want swipe to delete as well. You need to added these methods in order to add that to your tableview.
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
Update 2
__.h
#property (weak, nonatomic) IBOutlet UINavigationBar *navigationBar;
__.m
#synthesize navigationBar;
- (void)viewDidLoad {
//...
UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editTable)];
self.navigationBar.navigationItem.rightBarButtonItem = editButton;
}
You aren't alloc'ing or init'ing the editButtonItem object, so how can you expect to retain it (equals sign), let alone have it show up? You're basically sending a message to nil.
I have a UITableViewController that has custom cells that I have customized with my own subclass.
In this subclass, I've added a button and I want to push a view into the stack of the navigation controller. I have no idea on how to do that since I don't know how I can access to the navigation controller from my custom cell.
Any idea?
Need more infos here. What class holds the table, what class is the tableview delegate?
In the most simple case you're working in one single class. Than it would be [self.navigationController pushViewController: xyz].
But if you have your own subclassed UITableViewCells, than you need to communicate between the cell class and the viewcontroller. You could do this via setting a property in the cell class or your own customCell delegate.
You could also just send a Notification ([[NSNotificationCenter defaultCenter] postNotification: #"cellButtonTouchedNotification"]) on which your viewController is listening ([[NSNotificationCenter defaultCenter] addListener: self target: #selector(...) name: #"cellButtonTouchedNotification"]). In this case you could use the userInfo property to remember which cell was touched.
Anotherway is to make the button accessible from outside (a property eg). Then you could add the target in your tableViewDelegate's method cellForRowAtIndexPath:. Smth. like [myCustomCell.button addTarget: self selector: #selector(...)]; You could use tags to identify the row myCustomCell.button.tag = indexPath.row.
Use delegation. Here a simple example.
//.h
#protocol MyTableViewCellDelegate;
#interface MyTableViewCell : UITableViewCell
#property (assign, nonatomic) id <MyTableViewCellDelegate> delegate;
//your code here
#end
#protocol MyTableViewCellDelegate <NSObject>
#optional
- (void)delegateForCell:(MyTableViewCell *)cell;
#end
//.m
#implementation MyTableViewCell
#synthesize delegate = _delegate;
- (void)prepareForReuse {
[super prepareForReuse];
self.delegate = nil;
}
- (void)buttonAction {
if ([self.delegate respondsToSelector:#selector(delegateForCell:)])
[self.delegate delegateForCell:self];
}
#end
When you click the button you send a message to the delegate for your cell (e.g. the table view controller that is inserted into the navigation controller).
Here the controller
#implementation YourController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier = #"MyCustomCell";
MyTableViewCell *cell = (id)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil)
cell = [[[MyTableViewCell alloc] initWithMyArgument:someArgument reuseIdentifier:reuseIdentifier] autorelease];
[cell setDelegate:self];
// update your cell
return cell;
}
- (void)delegateForCell:(MyTableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// do your stuff
[self.navigationController pushViewController:...];
}
#end
Hold a pointer to your UITableViewController in the cell. You can pass it in the cell's constructor or set it later. Then you can call the pushViewController on the table view controller.
Even more beautiful solution would be to define a delegate for your cell, say ButtonCellDelegate that has a buttonClicked callback. You implement the delegate in your UITableViewController (or any other place where you have access to the view controller). Then you pass the delegate to the cell as described above and call the callback function from the cell when the button is clicked.