Buttons for each row UITableView - objective-c

I have a custom cell in a UITableView, defined by a custom class (.h and .m files). I am able to display the cell, and change the text for each cell in the list, but my custom cell also has buttons in it (two, actually). When I click the button, I need to know which row's button has been clicked. Is there any way to get this within the custom ui cell class?
I hope what I'm requesting is clear. If not, feel free to comment and I'll try to explain as best as possible.

You don't show any code to comment on, but generally speaking you can:
define a tag for each button which represents the table row where the button appears;
when your button action method is called, you can access then the tag property of the button to know which row it was.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
...
}
...
[button setTag:indexPath.row];
...
}
- (void)buttonPressedAction:(id)sender
{
UIButton *button = (UIButton *)sender;
int row = button.tag;
}
For a more elaborate solution, have a look at this S.O. thread.

You can use this approach:
Set an associated object value with each button. You can support this by adding a category to UIButton
#interface UIButton (AssociatedObject)
#property ( nonatomic, retain ) id associatedObject ;
#end
Implementation:
#implementation UIButton (AssociatedObject)
-(void)setAssociatedObject:(id)object
{
objc_setAssociatedObject( self, #"_associatedObject", object, OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
}
-(id)associatedObject
{
return objc_getAssociatedObject( self, #"_associatedObject" ) ;
}
#end
Use this like this:
myButton.associatedObject = <some object>
Set action/target to your view controller (or maybe table view delegate)
[ myButton addTarget:<view controller> action:#selector( buttonTapped: ) forControlEvents:UIControlEventTouchUpInside ] ;
In your action, look at the sender's associated object. Sender will be your UIButton
-(void)buttonTapped:(UIButton*)sender
{
// retrieve object associated with the tapped button:
id associatedObject = sender.associatedObject ;
}

I have done this by creating a protocol for the custom cell class and then making the UIViewController that is handling the UITableView the delegate for each of the custom cells.
Then I attached the UIButton to an IBAction in the custom cell class that made a call to it's delegate with information about which cell it is or which information I needed to act on.
So I would set up a protocol with something like:
#protocol CustomCellDelegate <NSObject>
- (void) cellButtonPressed:(NSDictionary *)stuffForDelegate;
#end
Then when I implemented cellButtonPressed: in the ViewController I would use stuffForDelegate to determine which cell it was or what information I needed to act on.
The tag method is okay, but I find it tedious to deal with all of the tags flying around and I prefer using objects and protocols and delegates instead.

Related

didSelectRowAtIndexPath, pushViewController and a little lable

I have a table controller in which I use didSelectRowAtIndexPath to navigate from the pushed cell to another view. In it I initialize new view controller and push some data in it. After that I do pushViewController.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
ServicesModel *service = [services objectAtIndex:indexPath.row];
ServiceViewController *serviceViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"ServiceView"];
serviceViewController.serviceModel = service;
NSLog(#"Set model %#", service.title);
// Pass the selected object to the new view controller.
[self.serviceController pushViewController:serviceViewController animated:YES];
}
In my ServiceViewController I have a label serviceTitle and ServiceModel property for selected service
#property (weak, nonatomic) IBOutlet UILabel *serviceTitle;
#property (strong, nonatomic) ServiceModel *serviceModel;
Using viewDidLoad I'm trying to change text of the label
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"viewDidLoad %#", self.serviceModel.title);
self.serviceTitle.text = self.serviceModel.title;
}
Also I'm trying to access model in viewDidAppear
- (void) viewDidAppear:(BOOL)animated
{
NSLog(#"viewDidAppear %#", self.serviceModel.title);
}
but when view opens, label is empty. Why? What am I doing wrong? The most strange is the log:
(-[ServiceViewController viewDidLoad]) (ServiceViewController.m:43) viewDidLoad (null)
(-[ServicesTableViewController tableView:didSelectRowAtIndexPath:]) (ServicesTableViewController.m:127) Set model Google.com
(-[ServiceViewController viewDidAppear:]) (ServiceViewController.m:36) viewDidAppear (null)
It shows that viewDidLoad fires before I assign the model property. And in viewDidAppear model property is still null. How it can be?
You have two problems. The first one, as 0x7fffffff mentioned, is that you're instantiating your controller incorrectly (it should be initWithNibName:bundle: if made in a xib, and like 0x7fffffff said if in a storyboard).
Second, you can't access the label in serviceViewController from didSelectRowAtIndexPath, because its view has not been loaded yet. So, instead of setting the label in didSelectRowAtIndexPath, you should have a string property in serviceViewController, and give it the value service.text. Then in viewDidLoad, you can populate your label with that string.
Is the label missing altogether, or do you see it and it just didn't receive updated text? If the label is missing, then it's probably a problem in how you're creating the view controller. If for example, you're using storyboards, you should be accessing the view controller like this:
ServiceViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"SomeStoryBoardID"];
Instead of this:
ServiceViewController *serviceViewController = [[ServiceViewController alloc] init];
If however, you can see the label, but it just hasn't updated it's text, the first thing you should to is examine the connections inspector in Interface Builder, and verify that the IBOutlet for the label is properly linked.

Buttons on a UICollectionViewCell

One of the new ios6 classes is uicollectionview which allows one to display items in a grid format similar to the Homescreen / iBooks / Pages layout.
Is there was a way to receive a touch event on a button that is on the UICollectionViewCell? Currently, my cell is created through an XIB file and added to the UICollectionView programatically. I'm looking for something similar to the Detail Diclosure Indicator on a UITableView.
After looking through Apple's documentation here, I don't see any methods that allow for something like that, but I am positive there's a way to do it.
How can one add a button to a UICollectionViewCell and get the indexPath of the cell when the button is tapped?
Are there any tutorials or links out there that could be helpful? iOS 6 is fairly new so I haven't found much. Thanks in advance.
One way is to add an NSIndexPath property to your cell and set it when the cell is dequeued. Then, your cell's action handler would have access to the current index.
Snippet for your UICollectionViewCell:
#interface MyCustomCell : UICollectionViewCell
#property (weak, nonatomic) NSIndexPath *indexPath;
- (IBAction)testClicked:(id)sender; // TODO: wire up your button to this handler
#end
Snippet for your view controller implementing UICollectionViewDataSource:
-(UICollectionViewCell*) collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:#"myCustomCell"
forIndexPath:indexPath];
cell.indexPath = indexPath;
// TODO: other initialization for the cell
return cell;
}

Adding Text Field Data to Table Cell

How I can store text field data in a view controller into a tableView cell in a TableViewController With Xcode using data source?
That means when the user taps "+" it will show another view that has the text field. When the user enters the text and presses save, the entered data will be stored as a table cell.
First make sure you understand data sources. Then you have to implement
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return self.data.count;
}
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
NSTableCellView * result = [tableView makeViewWithIdentifier:#"identifier" owner:self];
result.textField = #"Your special string";
return result;
}
After having setup it in IB like this and having connected the outlet of the Table View data source to your custom objects class (the class the above code is in) it should look like this
Please notice I used the same identifier as in the code so that I can get the created table cell view back easily.
self.data could be an array for example in which you store all your underlying objects (for the cell creation).
Of course you could also add any kind of UI elements to the cell view as well. In this case I use a custom subclass for the cell view. You would have to do something like this then (and set your class as the class of the cell view within IB of course. This is the part in the screenshot that has an NSTableCellView currently in it. It had to be MyGreatCellView from now on):
#interface MyGreatCellView : NSTableCellView {
IBOutlet NSTextField *files;
}
#property (assign) NSTextField *files;
Then you could also refer to result.files in the tableView:vieForTableColumn:row for example.
If something is unclear, just ask.
Getting the UITextField data onto a TableView Cell.
Here is a query for some one for the same issue.I think it will help you.

How to get a reference to the view controller of a superview?

Is there a way to get a reference to the view controller of my superview?
There were several instances that I needed this on the past couple of months, but didn't know how to do it. I mean, if I have a custom button on a custom cell, and I wish to get a reference of the table view controller that controls the cell I`m currently in, is there a code snippet for that? Or is it something that I should just solve it by using better design patterns?
Thanks!
Your button should preferably not know about its superviews view controller.
However, if your button really needs to message objects that it shouldn't know the details about, you can use delegation to send the messages you want to the buttons delegate.
Create a MyButtonDelegate protocol and define the methods that everyone that conforms to that protocol need to implement (the callback). You can have optional methods as well.
Then add a property on the button #property (weak) id<MyButtonDelegate> so that any class of any kind can be set as the delegate as long as it conforms to your protocol.
Now the view controller can implement the MyButtonDelegate protocol and set itself as the delegate. The parts of the code that require knowledge about the view controller should be implemented in the delegate method (or methods).
The view can now send the protocol messages to its delegate (without knowing who or what it is) and the delegate can to the appropriate thing for that button. This way the same button could be reused because it doesn't depend on where it is used.
When I asked this question I was thinking of, in a situation where I have custom cells with buttons on them, how can the TableViewController know which cell's button was tapped.
More recently, reading the book "iOS Recipes", I got the solution:
-(IBAction)cellButtonTapped:(id)sender
{
NSLog(#"%s", __FUNCTION__);
UIButton *button = sender;
//Convert the tapped point to the tableView coordinate system
CGPoint correctedPoint = [button convertPoint:button.bounds.origin toView:self.tableView];
//Get the cell at that point
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:correctedPoint];
NSLog(#"Button tapped in row %d", indexPath.row);
}
Another solution, a bit more fragile (though simpler) would be:
- (IBAction)cellButtonTapped:(id)sender
{
// Go get the enclosing cell manually
UITableViewCell *parentCell = [[sender superview] superview];
NSIndexPath *pathForButton = [self.tableView indexPathForCell:parentCell];
}
And the most reusable one would be to add this method to a category of UITableView
- (NSIndexPath *)prp_indexPathForRowContainingView:(UIView *)view
{
CGPoint correctedPoint = [view convertPoint:view.bounds.origin toView:self];
return [self indexPathForRowAtPoint:correctedPoint];
}
And then, on your UITableViewController class, just use this:
- (IBAction)cellButtonTapped:(id)sender
{
NSIndexPath *pathForButton = [self.tableView indexPathForRowContainingView:sender];
}
If you know which class is the superview of your view controller, you can just iterate through the subviews array and typecheck for your superclass.
eg.
UIView *view;
for(tempView in self.subviews) {
if([tempView isKindOfClass:[SuperViewController class] ])
{
// you got the reference, do waht you want
}
}

NSTextField not calling delegate when inside an NSTableCellView

I have a fairly vanilla Source List (dragged out from the Object Library) in my app, with an NSTreeController as its data source. I set the NSTextField inside the DataCell to be editable, but I want to be able to turn that off for some cells. The way I figured you would do this, is with a delegate for the NSTextField, but none of the delegate methods I've tried get called. Is there something I'm missing? I have the delegate set with an outlet in my XIB, and it happens to be the delegate to the owner NSOutlineView, as well, implementing both the NSOutlineViewDelegate and NSTextFieldDelegate protocols.
Also, I can't use the old –outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate method either, since that only works with cell-based Outline Views (I'm assuming this is the case - the Outline View documentation doesn't appear to have been updated for Lion, though the analogous NSTableView documentation has, and those methods don't get called either).
Update
I reproduced this in a brand new test project, so it's definitely not related to any of my custom classes. Follow the steps below to create my sample project, and reproduce this problem.
In Xcode 4.1, create a new project, of type Mac OS X Cocoa Application, with no special options selected
Create two new files, SourceListDataSource.m and SourceListDelegate.m, with the contents specified below
In MainMenu.xib, drag a Source List onto the Window
Drag two Objects onto the dock (left side of the window), specifying the SourceListDataSource class for one, and the SourceListDelegate for the other
Connect the Outline View's dataSource and delegate outlets to those two objects
Select the Static Text NSTextField for the DataCell view inside the outline view's column
Turn on its Value binding, keeping the default settings
Connect its delegate outlet to the Source List Delegate object
Set its Behavior property to Editable
Build and Run, then click twice on either cell in the outline view.
Expected: The field is not editable, and there is a "well, should I?" message in the log
Actual: The field is editable, and no messages are logged
Is this a bug in the framework, or am I supposed to achieve this a different way?
SourceListDataSource.m
#import <Cocoa/Cocoa.h>
#interface SourceListDataSource : NSObject <NSOutlineViewDataSource>
#property (retain) NSArray *items;
#end
#implementation SourceListDataSource
#synthesize items;
- (id)init
{
self = [super init];
if (self) {
items = [[NSArray arrayWithObjects:#"Alo", #"Homora", nil] retain];
}
return self;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
if (!item) {
return [self.items objectAtIndex:index];
}
return nil;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
return !item ? self.items.count : 0;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
return NO;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
return item;
}
#end
SourceListDelegate.m
#import <Foundation/Foundation.h>
#interface SourceListDelegate : NSObject <NSOutlineViewDelegate, NSTextFieldDelegate> #end
#implementation SourceListDelegate
- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
return [outlineView makeViewWithIdentifier:#"DataCell" owner:self];
}
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
NSLog(#"well, should I?");
return NO;
}
#end
Subclass NSTableCellView, with an outlet for the text field, and set the text field delegate in awakeFromNib. After doing that, control:textShouldBeginEditing: gets called. I'm not sure why, but (edit:) if you set the delegate in the xib, the delegate methods aren't called – I had the same experience as you.
Alternatively, you can forego the delegate and conditionally set Editable using a binding, either to a boolean property of the model, or using a value transformer which acts on a model instance and returns a boolean. Use the Editable binding of the text field.
I've encountered this problem, too. Because I didn't want to lose the bindings, I did the following:
Binding editable of the TextField to the objectValue and set up a custom NSValueTransformer subclass.
The other proposed solutions above are not performant and will not work on modern versions of macOS. NSTableView calls acceptsFirstResponder on EVERY textField in the entire table when one is about to be edited. And first responder methods get called while you just scroll around the table. If you put some logging in those calls, you'll see them in action.
Additionally, assigning the textField's delegate anywhere other than IB is not needed and won't actually work because NSTableView (and therefore NSOutlineView) basically "take over" for the views they contain.
The Correct, Modern Approach:
Subclass NSTableView (or NSOutlineView) and do this:
final class MyTableView: NSTableView
{
override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool
{
// NSTableView calls -validateProposedResponder on cellViews' textFields A METRIC TON, even while just scrolling around, therefore
// do not interfere unless we're evaluating a CLICK on a textField.
if let textField: NSTextField = responder as? NSTextField,
(event?.type == .leftMouseDown || event?.type == .rightMouseDown)
{
// Don't just automatically clobber what the TableView returns; it'll return false here when delays are needed for double-actions, etc.
let result: Bool = super.validateProposedFirstResponder(responder, for: event)
// IF the tableView thinks this textField should edit, now we can ask the textField's delegate to confirm that.
if result == true
{
print("Validate first responder called: \(responder).")
return textField.delegate?.control?(textField, textShouldBeginEditing: textField.window?.fieldEditor(true, for: nil) ?? NSText()) ?? result
}
return result
}
else
{
return super.validateProposedFirstResponder(responder, for: event)
}
}
}
Notes:
This was written against macOS 11.3.1 and Xcode 12.5 for an application targeting macOS 11.
The isEditable property of the NSTextFields in your NSTableCellViews must be set to true. NSTableView's implementation of -validateFirstResponder will check that property first, so you need not do so in your delegate method.