The tableviewcells are put in a view controller. It is okay for me to set different properties of the UITextField. However, I cannot find a way to implement textField:shouldChangeCharactersInRange:replacementString: successfully.
How should I do? Thanks.
myTextField - a subclass of UITextField, conformed to <UITextFieldDelegate>
myTableViewCell - a subclass of UITableViewCell
In this subclass, a myTextField property is declared and is connected to the nib layout.
myViewController - a subclass of UIViewController, conformed to <UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource>. A nib file is created with a Table View, the Table View is connected to a table view property. An ivar of myTableViewCell is also declared, named myCell.
In viewDidLoad of myViewController, followings are invoked:
[[myCell myTextField] setDelegate:self];
[myTableView setDelegate:self];
[myTableView setDataSource:self];
textField:shouldChangeCharactersInRange:replacementString: is also implemented in myViewController. But by breakpoint, it is found not implemented at all.
I hope the textField delegate is pointing to the viewcontroller class, rit ?
I think your problem is in identifying which textField is calling that delegate method.
In that case 1 solution will be to point the textField.delegate to the UITableViewCell
or else if u need to handle that in the controller,
set the tag for each of the textField in the method cellForRowAtIndexpath: (based on indexPath.row)
and in the textField:shouldChangeCharactersInRange:replacementString: compare with [textField Tag]
Related
I have a small problem with cells in uitableview.
I create properties in one cell and i want to grab values from them and sent to my webservice:
#property (weak, nonatomic) IBOutlet UILabel *profileValueLabel;
#property (weak, nonatomic) IBOutlet UITextField *profileTextfield;
profileValueLabel is just text, it is ok but I need to take profileTextField value and send it with my service out.
Any help?
Your best bet would be to have your UITableViewCell subclass conform to the UITextFieldDelegate, and then create a protocol (delegate) for your table view cell subclass that fires when the UITextField didEndEditing method is called and pass the table view cell itself (perhaps along with the text data you want to send to your web service) into the delegate method so that in your view controller you can determine the index path based on the cell in the table. Then, in your view controller, you can send the data on each cell to the web service whenever you delegate method is fired. This is probably the cleanest approach, but it's best to encapsulate this logic in the table view class itself so as to keep your view controller thinner and make the communication between the two components more explicit.
I hope you have implemented the delegates of UITableView , onE Approach
NSString *tempString1 ;
NSString *tempString2
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell1 = [tableView cellForRowAtIndexPath:indexPath];
tempString1= cell1.textLabel.text; //<-- your label
*tempString2= cell1.textfeild.text ; // u can access the value ,
}
second part of question //
You need to embed tempString and tempString2 variable with the service request .
Your profileTextfield property needs to be in the header file for your UITableViewCell subclass. Then in your UITableViewDataSource controller you would use [myTableView cellForRowAtIndexPath:yourCellsIndexPath].profileTextfield.text once you've determined the index path for the cell whose textfield text you want to grab.
If you want to grab the profileTextfield property as soon as your user is done entering data, you will need to create a protocol in your UITableViewCell subclass and a corresponding delegate property. Then you make a protocol method like -(void)tableViewCell:(myUITableViewCellSubclass *)cell didEndEditingProfileTextFieldWithText:(NSString *)text; Then in your UITableViewCell subclass, set the profileTextField's delegate to your UITableViewCell subclass and use the textFieldDidEndEditing: or textFieldWillReturn: delegate method. In whichever method you use, call [self.delegate tableViewCell:self didEndEditingProfileTextFieldWithText:self.profileTextfield.text] protocol method. Your tableViewController should handle this method, and you can do whatever you need to there.
I have a XIB that has a bunch of views in it (iPad). One of the views is a UITableView. The delegate was set via IB. When you run the app cellForRowAtIndex is not being called. As a matter of fact, in this file, there is also no numberOfRowsInSection or numberOfSections methods. There is only a didSelectRowAtIndexPath method.
So I wrote a cellForRowAtIndex and manually set self.table.delegate = self.
If I click a cell the didSelectRowAtIndexPath is executed.
I honestly am at a loss? How can this even happen?
Add:
self.table.dataSource = self;
cellForRowAtIndexis a datasource protocol method, so you must set the datasource to self as well in order for the controller to respond to the datasource protocol.
If this controller is anything other than a UITableViewController, do not forget to add the following to your .h file:
#interface myViewController: UIViewController <UITableViewDelegate, UITableViewDataSource> {}
In your .h you need to add the Datasource and Delegate declarations
#interface HDMainViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
and in you .m file where you create your tableview you need to set
tableview.delegate = self;
tableview.dataSource = self;
Now your dataSource and Delegate methods should work
I'm not sure what is causing the problem, however an easy fix is that in your -(void)viewDidLoad method you could call [self.table reloadData] which forces it to manually call all of the functions
Somethign else that occurred to me is that you said "numberOfRowsInSection or numberOfSections is not implemented"... Those methods are required for a table view and oyu must make sure to implement them. That could very well be the problem
For a while now I've had this dilemma on my mind. A cell in UITableView is essentially a view, thus the class for UITableViewCell should take care of view related things (i.e. presentation methods, layout and so on.) and have no business logic inside of it (usually taken care of the controller). But since we don't have a controller for each cell and only a controller for the whole table, I have trouble figuring out where to put my cell-wise logic. Putting it in the cell itself breaks MVC, but putting it in the table controller makes it hard to determine what cell the method is being called from (I prefer writing subclasses for my senders if the view is action based so I can add properties to help me determine what view this is).
For instance I have a cell, that cell has a UIButton inside of it, when the button is pushed a UIPopover appears. Now where do I put the popover presentation code (The presentation appears from one specific cell, therefore I must know which cell it's being called from.)
I'd like to know what other people do in this case and what are their best practices.
If you put the presentation of the popover inside the cell, then it's the best option. Why ?, because this is not logic, this is view related things and because the button who makes this action is inside your cell, then the code should be inside your cell (or you can send message(delegate) to your viewController to show that).
Then what is the logic ? The logic is for example: calculating, date operations, sending things to server. All these should be inside another object that we can call it module or manager.
The controller can exchange messages between all these objects (view - model), but the view and the module should be separated from each other.
Update:
You may want to take a look at Single Responsibility principle
Normally, it's to your View Controller to handle the "filling" logic for your cells. Cells are recipient that you fill each time.
It is even said in prepareForReuse: of UITableViewCell :
The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell.
So indeed, your cells shouldn't hold any logic other than displaying.
If you need logic like button in your cell, you should set a delegate (you create one protocol) to your subclass of UITableViewCell and then hold in your UIViewController the cell logic.
If you cell is unique, I recommend you to define your cell as a static cell (no reuse identifier). And make a strong link to it.
You could subclass UITableView and UITableViewCell. Then, add delegate methods for the button. e.g. tableView:buttonWasPressedForCell: & buttonWasPressedForCell:. The tableView would conform to the cell's delegate and receive the message buttonWasPressedForCell:. Then, the tableView would send the message tableView:buttonWasPressedForCell: to it's delegate, in this case, your controller. This way you know which UITableView and which UITableViewCell the message was sent from.
Example:
ABCTableView.h
#protocol ABCTableViewDelegate <NSObject, UITableViewDelegate>
// You may not need this delegate method in a different UIViewController.
// So, lets set it to optional.
#optional
// Instead of passing the cell you could pass the index path.
- (void)tableView:(ABCTableView *)tableView buttonWasPressedForCell:(ABCTableViewCell *)cell;
#end
#interface ABCTableView : UITableView
// Declare the delegate as an IBOutlet to enable use with IB.
#property (weak, nonatomic) IBOutlet id<ABCTableViewDelegate> delegate;
#end
ABCTableView.m
#implementation ABCTableView
#dynamic delegate;
- (void)buttonWasPressedForCell:(ABCTableViewCell *)cell
{
// Check if the delegate responds to the selector since
// the method is optional.
if ([self.delegate respondsToSelector:#selector(tableView:buttonWasPressedForCell:)])
{
[self.delegate tableView:self buttonWasPressedForCell:cell];
}
}
#end
ABCTableViewCell.h
#protocol ABCTableViewCellDelegate;
#interface ABCTableViewCell : UITableViewCell
// Declare the delegate as an IBOutlet to enable use with IB.
#property (weak, nonatomic) IBOutlet id<ABCTableViewCellDelegate> delegate;
#end
#protocol ABCTableViewCellDelegate <NSObject>
// You may not need this delegate method in a different custom UITableView.
// So, lets set it to optional.
#optional
- (void)buttonWasPressedForCell:(ABCTableViewCell *)cell;
#end
ABCTableViewCell.m
#implementation ABCTableViewCell
- (IBAction)action:(id)sender
{
// Check if the delegate responds to the selector since
// the method is optional.
if ([self.delegate respondsToSelector:#selector(buttonWasPressedForCell:)])
{
[self.delegate buttonWasPressedForCell:self];
}
}
#end
Note:
When you dequeue the cell in tableView:cellForRowAtIndexPath: or add the cell using Interface Builder be sure to set the cell's delegate to the tableView.
E.g.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ABCTableViewCell *cell = (ABCTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"Cell"];
cell.delegate = tableView;
return cell;
}
Usually for tasks like this I assign to cell my viewController as delegate (and define some protocol for it). Also, i keep weak reference to object from which I populate my cell, so on button's action I will forward to delegate (viewController) method like this:
- (void)actionOnCell:(UITableViewCell *)cell fromView:(UIView *)sender withItem:(id)sourceItem;
so in this way, I know where from show my popover, and what information (appropriate to sourceItem) show in it.
EDIT Also, if there multiple controls on cell to avoid duplication of pretty similar methods you can just add one parameter to function mentioned above, and define enum of all possible actions
Create an action handler and a data source for the cell. Have your data source conform to the data source protocol (View Model). Then there is no need for the cell to even know about the data model.
In the interface: TableViewCell
#property (nonatomic, weak) id <SomeTableViewCellActionHandler> actionHandler;
#protocol SomeTableViewCellActionHandler <NSObject>
- (void)cell:(SomeTableViewCell *)cell didReceiveStartButtonAction:(UIButton *)button;
- (void)cell:(SomeTableViewCell *)cell didReceivePauseButtonAction:(UIButton *)button;
- (void)cell:(SomeTableViewCell *)cell didReceiveClearButtonAction:(UIButton *)button;
#end
Implementation
- (void)prepareActionsForControls
{
[self.startButton addTarget:self action:#selector(handleStartButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.pauseButton addTarget:self action:#selector(handlePauseButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.clearButton addTarget:self action:#selector(handleClearButtonAction:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)handleStartButtonAction:(id)sender
{
[self.actionHandler cell:self didReceiveStartButtonAction:sender];
}
- (void)handlePauseButtonAction:(id)sender
{
[self.actionHandler cell:self didReceivePauseButtonAction:sender];
}
- (void)handleClearButtonAction:(id)sender
{
[self.actionHandler cell:self didReceiveClearButtonAction:sender];
}
When you create your cell in the View Controller
create an action handler that conforms to the MyTableViewCellActionHandler protocol, pass the action handler the View Controller if it needs to do presentation.
cell.actionHandler = self.tableViewCellActionHandler;
You may also provide a datasource for your cell and pass in a View Model. (MVVM) This will allow you to keep only presentation code in the cell and keep all of your business logic where it belongs. Separation of concerns.
I don't know if this is considered to be an accepted Objective-C practice or not, so I'm open to other ideas. Here is the idea. I have a table that gets is cells from a custom UITableViewCell. Each of these cells presents an event that the user can attend. As such, I'd like for the user to be able to add them to their calendar directly from the table view.
To accomplish this, I put a button on each table cell - an "add to calendar button". What I am stuck on is how to wire an action from this button back to the UIViewController where the UITableView is a subview. The button is part of a UITableView class and doesn't have visibility to the UIViewController.
I've been trying to implement the delegate pattern suggested by Aaron below. I'm almost there, but something is still disconnected. Here is what I have:
New protocol EventDelegate.h
#protocol EventDelegate <NSObject>
- (void) addToCalendar : (NSString *) strDate;
#end
In MyTableCell.h, I have added this property:
#property (nonatomic, strong) id<EventDelegate> eventDelegate;
In MyTableCell.m, I have added this method:
- (IBAction)addToCalendar:(UIButton *)sender
{
NSLog(#"calling addToCalendar delegate %#", _dayAndTime.text);
[self.eventDelegate addToCalendar:_dayAndTime.text];
}
All is well to this point. When I click on the button that I added to the table cell, I get the output calling addToCalendar delegate Monday, January 13
Over in MyViewController.h, I changed it look like this:
#interface TrainingScheduleViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, EventDelegate>
- (void) addToCalendar:(NSString *)strDate;
#end
And finally, in MyViewController.m I added the method body:
- (void) addToCalendar:(NSString *)strDate
{
NSLog(#"inside delegate");
NSLog(#"%#", strDate);
}
The part that I think might be the problem is where Aaron suggests adding this line of code:
[tableViewController setEventDelegate:self];
First, I'm not sure where to add this line. I put it in viewDidLoad. The compiler wouldn't let me type it verbatim, so the closest thing that I could find was this line:
[self.tableView setDelegate:self];
Maybe I need an additional outlet?
I have to be almost there, but I just don't see what I am still missing. Can anyone help me? Thanks!
Here is some example code that I pulled from my "cellForRowAtIndexPath" method. This is from a table that was built in IB and uses a prototype cell, but the idea is the same no matter how you implement. In this case, I use a tag to identify the button, then reference it when the cell is created:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
UIButtonRed *actionButton = (UIButtonRed *)[cell.contentView viewWithTag:4];
[actionButton addTarget:self action:#selector(initializeReorder:) forControlEvents:UIControlEventTouchDown];
return cell;
}
The initializeReorder: method automatically receives (id)sender as a parameter. You can cast that to a table cell and inspect it to get the rest of your info:
- (void)initializeReorder:(id)sender
{
UIButtonRed *actionButton = (UIButtonRed*)sender;
UITableViewCell *cell = (UITableViewCell*)actionButton.superview.superview;
NSIndexPath* cellPath = [self.tableView indexPathForCell:cell];
...
}
Create a protocol for this.
I would define a protocol like this:
#protocol MyEventDelegate <NSObject>
- (void)addToCalendar:(Event*)event;
#end
And add a delegate property to both your UITableViewController class and your MyTableCell class:
#property (nonatomic, strong) id<MyEventDelegate> eventDelegate;
MyViewController should conform to this protocol and implement addToCalendar:
#interface MyViewController : UIViewController <MyEventDelegate>
When your MyViewController object sets up the UITableViewController, pass in a reference to itself:
[tableViewController setEventDelegate:self];
and when your UITableViewController creates each cell, pass it on:
[cell setEventDelegate:self.eventDelegate];
Now, when the IBAction is called in your cell, the cell can call the delegate method on MyViewController like so:
[self.eventDelegate addToCalendar:event];
Xcode 4.6.1 iOS 6 using storyboards
My problem is this
I have a UITableView with dynamic prototype cells on a UIView in a UIViewController (that is itself embedded in a navigation controller) and I want to segue from one specific cell to another view
(Before anyone suggests I should just be using a UITableViewController , I do have other things on the UIView, so i'm set up this way for a reason.)
Now i'm not sure how to go about creating the segue
If I drag from the prototype UITableViewCell to create a segue , all the generated cells automatically call the the segue - when i need only one to do so. This is normal behaviour and I would get around this if i was using a UITableViewController by creating the segue by dragging from UITableViewController and calling [self performSegueWithIdentifier:.... From my didSelectRowAtIndexPathMethod so only the specific cell I want to perform this segue triggers it.
I don't have a UITableViewController in this case - just my UITableView on a UIView that is part of a UIViewController subclass
I've been playing around and I have just discovered that i cannot drag from the UITableView - doesn't let you do that, so that was a deadend.
My only choice that seemed left to me was to drag from the UIViewController
So i tried that and of course XCode throws up an error on the perform segue line telling me i have ... No visible interface for 'LocationTV' declares the selector performSegueWithIdentifier. LocationTv being my tableview subclass.
What is the correct way to attempt to call the new view in this situation
Thank
Simon
First of all segues can be use only between UIViewControllers. So in case you want to perform a segue between two views that are on the same view controller, that's impossible.
But if you want to perform a segue between two view controllers and the segue should be trigger by an action from one view (inside first view controller) well that's possible.
So in your case, if I understand the question, you want to perform a segue when the first cell of a UITableView that's inside of a custom UIView is tapped. The easiest approach would be to create a delegate on your custom UIView that will be implemented by your UIViewController that contains the custom UIView when the delegate method is called you should perform the segue, here is a short example:
YourCustomView.h
#protocol YourCustomViewDelegate <NSObject>
-(void)pleasePerformSegueRightNow;
#end
#interface YourCustomView : UIView {
UITableView *theTableView; //Maybe this is a IBOutlet
}
#property(weak, nonatomic) id<YourCustomViewDelegate>delegate;
YourCustomview.m
#implementation YourCustomview
# synthesise delegate;
//make sure that your table view delegate/data source are set properly
//other methods here maybe
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row == 0) { //or any other row if you want
if([self.delegate respondsToSelector:#selector(pleasePerformSegueRightNow)]) {
[self.delegate pleasePerformSegueRightNow];
}
}
}
YourTableViewController.h
#interface YourTableViewController : UIViewController <YourCustomViewDelegate> {
//instance variables, outlets and other stuff here
}
YourTableViewController.m
#implementation YourTableViewController
-(void)viewDidLoad {
[super viewDidLoad];
YourCustomView *customView = alloc init....
customView.delegate = self;
}
-(void)pleasePerformSegue {
[self performSegueWithIdentifier:#"YourSegueIdentifier"];
}
You can create any methods to your delegate or you can customise the behaviour, this is just a simple example of how you can do it.
My Solution
I ended up using a delegation pattern
I made a segue dragging from the my UIViewController - specifically dragging from the viewController icon (the orange circle with a white square in it - from the name bar thats under the view in the storyboard - although you could also drag from the sidebar ) to the view that i wanted to segue to.
I needed to trigger this segue from a table view cell on a table view.
TableView Bit
So i declared a protocol in my tableview header file - which is called LocationTV.h - as follows
#protocol LocationTVSegueProtocol <NSObject>
-(void) makeItSegue:(id)sender;
#end
Below that I declare a property to hold my delegate
#property (nonatomic, strong) id<LocationTVSegueProtocol> makeSegueDelegate;
To actually trigger the segue i called the makeItSegueMethod on my makeSequeDelegate in my didSelectRowAtIndexPath method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {
DLog(#"selected row %d",indexPath.row);
case dLocation:
{
if(indexPath.row == 2){
[_makeSegueDelegate makeItSegue:self];
} else if (indexPath.row == 7){
UIViewController Bit
and set up my UIViewController (named MultiTableHoldingVC) as implementing that protocol
#interface MultiTableHoldingView : UIViewController
<EnviroTVProtocol,LocationTVSegueProtocol> {
}
Below that i declared the protocol method in the list of my classes methods (although i'm not sure that is necessary as the compiler should know about the method as the decalration of implementing a protocol is essentially a promise to implement this method)
-(void) makeItSegue:(id)sender;
And then over in the implementation file of my UIViewController i wrote the method which essentially just calls preformSegueWithIdentifier
-(void) makeItSegue:(id)sender{
[self performSegueWithIdentifier:#"ChooseCountryNow"
sender:sender];
}
And to link it all together,as in the header file I had declared my instance of the tableView as follows
#property (strong, nonatomic) IBOutlet LocationTV *dsLocationTV;
I had to set that tables views delegate property to be self - which I did in my UIViewControllers -(void)ViewDidLoad method
_dsLocationTV.makeSegueDelegate = self;
It all seems a bit of a kludge calling a method to call a method and allprog suggestion is simpler (I cant for the life of me work out why it threw up errors for me) but this works just fine . Thanks to both allprog and danypata for their suggestions.
Hope this is helpful to someone out there
performSegueWithIdentifier: is a method of the UIViewController class. You cannot call it on a UITableView instance. Make your view controller implement the UITableViewDelegate protocol and set it as the delegate for the UITableView.
Another option is that you don't use segues. In the same delegate method do:
OtherViewController ov = [[OtherViewController alloc] init<<some initializer>>];
// Or in case of storyboard:
OtherViewController ov = [self.storyboard instantiateViewControllerWithIdentifier:#"ovidentifier"];
// push view controller
[self.navigationController pushViewController:ov animated:YES];
If the delegate object is different from the view controller, then the easiest solution is to add a weak property to the delegate's class that keeps a reference to the viewController, like this:
#property (weak) UIViewController *viewController;
and set it up in the viewDidLoad of the viewController
- (void) viewDidLoad {
self.tableView1.viewController = self;
}
Make sure that the tableView1 property is declared like this:
#property (IBACTION) (weak) SpecialTableView *tableView1;
Sometimes using the storyboard is more painful than writing the code yourself.