So I have a TableViewController, when certain cell pressed it goes to my DetailViewController to display more details about the pressed cell.
On this DetailViewController, I would like to add a button and when this button gets pressed a "CHECKMARK" will add to its cell from TableViewController and goes back to TableViewController. How would you do that?
I already added an IBAction for my button but I don't know how I would implement that yet..
Please speak in layman's term as much as possible because I'm not that yet very familiar with iOS Programming. Thank you so much.
You can use protocol for message transfer.
In your detailViewController create protocol,
#protocol yourDetailViewControllerDelegate <NSObject>
- (void)yourDetailViewController:(YourDetailViewController *)controller didSelectObject:(YourObject *)object;
#end
and create delegate object,
#property (nonatomic, weak) id<yourDetailViewControllerDelegate> delegate;
Then in your tableViewController implement the delegate method,
- (void)yourDetailViewController:(YourDetailViewController *)controller didSelectObject:(YourObject *)object {
// code for add check mark
}
In your button action,
- (void)buttonAction {
// Call the delegate method
[delegate yourDetailViewController:self didSelectObject:changedObject];
}
Related
I created the custom cell (XIB) as the subclass of UICollectionViewCell and the cell has a button in it. When I click a button, I want to go to another view with some data on that, and could go back to the original view by clicking a button as well. I've search for that and found something like "segue" or "modal" but I can't do it initially from my custom cell.
Is there any way to do this? Any help would be very thankful.
So what you want to do, since it seems like UICollectionView works the same as UITableView, is make a subclass of UICollectionViewCell that contains a protocol to send actions, like pressing a button, to a view controller from a different view. In this case, a different view being the UICollectionViewCell.
Adding a Protocol to a UICollectionViewCell
Add a new Cocoa Touch Class called UICustomCollectionViewCell with subclass of UICollectionViewCell. And include the interface builder file
header file UICustomCollectionViewCell.h
#protocol UICustomCollectionViewCellDelegate;
#interface UICustomCollectionViewCell : UICollectionViewCell
#property ( nonatomic, retain) IBOutlet UIButton *button;
- (IBAction)pressButton:(id)sender;
#property ( assign) id< UICustomCollectionViewCellDelegate> delegate;
#end
#protocol UICustomCollectionViewCellDelegate <NSObject>
#optional
- (void)customCollectionViewCell:(UICustomCollectionViewCell *)cell pressedButton:(UIButton *)button;
#end
implementation file UICustomCollectionViewCell.m
#implementation UICustomCollectionViewCell
#synthesize delegate;
- (IBAction)pressButton:(id)sender {
if ([delegate respondsToSelector: #selector( customCollectionViewCell:pressedButton:)])
[delegate customCollectionViewCell: self pressedButton: sender];
}
#end
xib file UICustomCollectionViewCell.xib
make sure the connections from the UICustomCollectionViewCell are connected to the button from the Connections Inspector:
button
-pressButton:
Finally, using this class in your project
Import the class as well as the delegate:
#import "UICustomCollectionViewCell.h"
#interface ViewController () < UICustomCollectionViewCellDelegate>
#end
In this following code, you will use the UICustomCollectionViewCell class instead of UICollectionViewCell:
UICustomCollectionViewCell *cell;
...
[cell setDelegate: self];
...
return cell;
And now the action, or method, that is called when the button is pressed:
- (void)customCollectionViewCell:(UICustomCollectionViewCell *)cell pressedButton:(UIButton *)button {
//action will be here when the button is pressed
}
If you want to find out what indexPath this cell was from:
[collectionView indexPathForCell: cell];
You can't/shouldn't perform navigation jobs in the cell, navigation is not in the cells domain.
What you can try is
1) Use a delegate, setup a delegate and wire it up to the button action, the controller hosting the tableview/collection view can set itself up as the delegate and listen to any events. This controller should be in charge of pushing a new view to the stack using any method you desire.
2) If you hate delegates but love blocks, you can setup a callback block on the cell, its actions could be setup in the cellForRowAtIndex: method in the controller.
Noticed a pattern here? both the above methods are delegating the task from the cell to the controller.
If all fails, just implement didSelectItemAtIndexPath: and stick with it.
Did you try with didSelect method?
- (void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
YourNewViewControllerClass *someViewController = [storyboard instantiateViewControllerWithIdentifier:#"YourNewVCID"];
[self presentViewController:someViewController
animated:YES
completion:nil];
}
Easiest way would be to implement cellForRow.. method, set a tag for your cell/button and react basing on that tag (eg. indexPath.row).
1.custom your button
NouMapButton.h
#import <Foundation/Foundation.h>
#interface NouMapButton : UIButton
#property (nonatomic, readwrite, retain) NSObject *dataObj;
#end
NouMapButton.m
#import "NouMapButton.h"
#implementation NouMapButton
#end
set your button data and target in
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
btn01.dataObj = YOUR_DATA;
[btn01 addTarget:self action:#selector(map:) forControlEvents:UIControlEventTouchUpInside];
then you can get button custom dataObj in sender.dataObj
-(void)map:(NouMapButton *)sender{
MapViewController *nextView = [[MapViewController alloc] init];
nextView.dataObj = sender.dataObj;
//TODO....
}
I have a popover view that I added a PFQueryTableViewController too, as a childviewController. I have a button on the popover that, when pressed, should reload the tableview in the PFQueryTableViewController.
Here is the code in the buttonPressed method that should do this:
NotificationsTableViewController *note = (NotificationsTableViewController *)self.childViewControllers[0];
[note loadObjects];
[note.tableView reloadData];
The table is not reloaded, though, and the only way I can get it to reload is by instantiating a whole new controller and adding it to the popover, which is not something I want to end up doing.
Here is some more code:
//NotificationsPopoverController
#class NotificationsTableViewController;
#protocol PopDelegate <NSObject>
-(void) changedQue;
#end
#interface NotificationsPopoverController : PDPopoverController
#property (nonatomic, assign) id<PopDelegate> myDelegate;
#property NotificationsTableViewController *noti;
When my button is pressed I call [self.myDelegate changedQue];
//NotificationsTableViewController.h
#interface NotificationsTableViewController : PFQueryTableViewController <PopDelegate>
//NotificationsTableViewController.m
-(void)changedQue
{
NSLog(#"Did it work?");
[self.tableView reloadData];
[self loadObjects];
}
The log statement is not printed for some reason, not sure why...
I haven't understand the flow completely yet. I think somewhere you will have to add self.note.myDelegate = self;. Hm... I think your have implemented delegate wrongly. Can you paste more complete code on both the view controller.
As per Jacob, add self.myDelegate = self.noti; right after the child view noti solves 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.
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.
Here is my question: I have two UITableViewController that we're going to call. OriginalTableViewController and SecondTableViewController.
The SecondTableViewController is populated by an NSMutableArray and a UISegmentedControl where the users can navigate through a bunch of data and select multiple rows.
What I want to do is to enable the users to select multiple row, click on save button in the navigation bar and then on OK to dismiss the view and go back to OriginalTableViewController which has to be populated by the selected rows of the SecondTableViewController.
I don't know exactly how to proceed since I started to learn how to code like 4 months ago. Should I use delegation? Or anything else? I would appreciate any help.
For your problem, delegation would be the best choice. You define a protocol in SecondTableViewController and implement that protocol in OriginalTableViewController. When the Save button is pressed, the second table notifies the original table with selected data, and the original can pop/dismiss the second and reloads its table.
In SecondTableViewController.h, define the protocol:
#protocol SecondDelegate <NSObject>
#required
- (void) didSelectRows:(NSArray *)rows;
#end
#interface SecondTableViewController : UITableViewController
#property (retain) id<SecondDelegate> delegate;
#end
In OriginalTableViewController, implement the protocol:
.h:
#interface OriginalTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, SecondDelegate>
.m:
- (void) didSelectRows:(NSArray *)rows {
// Update the model with selected data and reload. Also pops/dismisses second table.
}
And set the delegate property just before pushing/presenting SecondTableViewController:
SecondTableViewController *second = nil; // instantiate the vc some how
second.delegate = self;
In SecondTableViewController.m, implement the save method:
- (void) save {
NSMutableArray *array = [NSMutableArray array];
for (NSIndexPath *indexPath in [self.tableView indexPathsForSelectedRows]) {
// Populate array with selected objects.
}
[self.delegate didSelectRows:array];
}
Hope this helps.