Pass data from custom UITableViewCell and UIViewController - ios

I am (fairly) familiar with segues and delegates to pass data between UIViewControllers, but my current situation is slightly different and I cannot get it working. Context: XCode 5 and iOS7 with Objective C.
I have a tableview (dynamic prototypes) that loads a custom cell (from a separate nib) that contains a UILabel and a UISwitch. CustomCell.xib loads its data from CustomCell.h/m. The main content is in ViewController.h/m and in that file I need to know whether the switch value changed (or actually the new value of the UISwitch). Obviously I know this within the CustomCell.h/m files but need to pass them to ViewController.h/m.
I tried using a delegate, but I cannot set a delegate for the UINib instance (in contrast to setting a delegate on a viewcontroller's instance). Also, the custom cell is implemented in the viewcontroller, so it is not pushed like another viewcontroller would be in a navigation stack.
CustomCell.h
#import <UIKit/UIKit.h>
#protocol CustomCellDelegate <NSObject>
- (void)switchControlValueChanged:(UISwitch*)switchControl toNewValue:(BOOL)value;
#end
#interface CustomCell : UITableViewCell
#property (nonatomic, weak) IBOutlet UILabel *titleLabel;
#property (nonatomic, weak) IBOutlet UISwitch *switchControl;
#property (nonatomic, assign) id <CustomCellDelegate> delegate;
- (void)setValueForSwitchControlTo:(BOOL)value;
- (IBAction)changeColorForSwitchControl;
#end
CustomCell.m
- (void)changeColorForSwitchControl // value changed method
{
...
[self.delegate switchControlValueChanged:self.switchControl toNewValue:self.switchControl.on];
}
ViewController.h
#import <UIKit/UIKit.h>
#import "CustomCell.h"
#interface ViewController : UITableViewController <CustomCellDelegate>
...
#end
ViewController.m
- (void)viewDidLoad
{
...
// cannot set a delegate on the cellNib
UINib *cellNib = [UINib nibWithNibName:kCustomCell bundle:nil];
[self.tableView registerNib:cellNib forCellReuseIdentifier:kCustomCell];
}
- (void)switchControlValueChanged:(UISwitch *)switchControl toNewValue:(BOOL)value
{
NSLog(#"Switch changed!"); // this is not getting displayed
}

The right time to set your view controller to be the delegate of your cell would be when you set up other attributes of your cell. You do that in tableView:cellForRowAtIndexPath:.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCustomCell forIndexPath:indexPath];
...
cell.delegate = self;
return cell;
}
Side note: registerNib:forCellReuseIdentifier: does exactly what it says, it just registers your nib for reuse. The contents of the nib aren't loaded until the table view decides to do that. It creates copies of the cell contained in the nib as and when required.

One option would be to use NSNotification, not as elegant but would work for your purpose. Every time the switch value changes, you can send a notification in CustomCell.m, something like:
NSDictionary *cellInfo = #{}; // add useful information to identify the cell to this dictionary
[[NSNotificationCenter defaultCenter] postNotificationName:#"SwitchValueChanged" object:nil userInfo:cellInfo];
Then you catch the notification in your ViewController by registering it as an observer with:
-(void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(yourMethod) name:#"SwitchValueChanged" object:nil];
}

Related

XCode TableViewController to detail in Objective-C

I have a TabBarController with 4 tabs, 3 of which are table views. I am trying to put a detail for every table view cell, and I don't think storyboard is efficient since I have over 50 detail pages. I'm very new to all of this, and I've tried to find out how to link a detail to every tab for a couple hours. My table views start with the Second View Controller.
Here is SecondViewController.m:
#import "SecondViewController.h"
#implementation SecondViewController
{
NSArray *tableData;
}
#synthesize tableData;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
tableData = [NSArray arrayWithObjects:#"Carter", #"Greene", #"Hancock", #"Hawkins", #"Johnson", #"Sullivan", #"Unicoi", #"Washington", nil];
[super viewDidLoad];
}
#pragma mark - TableView Data Source methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
return [tableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyCell"];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
#end
Here is SecondViewController.h:
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController <UITableViewDelegate,
UITableViewDataSource>
#property(nonatomic, retain) NSArray *tableData;
#end
If this helps, here is my storyboard.
If anyone can help me individually add details to each table view cell in the most painless way possible, I would appreciate it. Thanks!
If using storyboards, the process is fairly simple.
First, I'd suggest dragging a prototype "table view cell" on to your table views. You can then control-drag from that prototype cell to your destination scene to add a segue between the cell and the next scene:
Make sure to select that prototype cell and set its storyboard identifier (I used "Cell"). You will need to reference that storyboard identifier, as shown in my code sample below. I also configured appearance related things (like the disclosure indicator) right in that cell prototype in IB so I don't have to worry about doing that in code and I can see what the UI will look like right in IB.
Now you can go to the table view controller and (a) simplify cellForRowAtIndexPath (because you don't need that logic about if (cell == nil) ... when using cell prototypes); but also implement a prepareForSegue to pass the data to the destination scene:
// SecondViewController.m
#import "SecondViewController.h"
#import "DetailsViewController.h"
#interface SecondViewController ()
#property (nonatomic, strong) NSArray *tableData;
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableData = #[#"Carter", #"Greene", #"Hancock", #"Hawkins", #"Johnson", #"Sullivan", #"Unicoi", #"Washington"];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[DetailsViewController class]]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSString *name = self.tableData[indexPath.row];
[(DetailsViewController *)segue.destinationViewController setName:name];
}
}
- (IBAction)unwindToTableView:(UIStoryboardSegue *)segue {
// this is intentionally blank; but needed if we want to unwind back here
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.tableData[indexPath.row];
return cell;
}
#end
Obviously, this assumes that you created a DetailsViewController and specified it as the destination scene's base class, and then create properties for any values you want to pass to this destination scene:
// DetailsViewController.h
#import <UIKit/UIKit.h>
#interface DetailsViewController : UIViewController
#property (nonatomic, copy) NSString *name;
#end
And this destination scene would then take the name value passed to it and fill in the UILabel:
// DetailsViewController.m
#import "DetailsViewController.h"
#interface DetailsViewController ()
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#end
#implementation DetailsViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.nameLabel.text = self.name;
}
#end
Frankly, this process will undoubtedly be described more clearly in any UITableView tutorial includes a discussion of "cell prototypes" (your code sample suggests you were using an older tutorial that predates cell prototypes).
I think the relationship between the code and the storyboard is as following:
Code implement the function of the application.
Storyboard contains many scenes, these scenes implement the User interface, including data presentation, data input, data output.
Code read data from these scenes and output the result to the scenes.
Code is internal logic function entities and the storyboard the the User Interface presentation.

Go to another view by clicking a button in Custom Cell

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....
}

Making Main View Controller Delegate of UITableViewDataSource

I'm new to iOS development. My Main View Controller doesn't display any cells from its table view. I was trying to set it up to display just one cell for now. The main view controller is a subclass of the UIViewController, and has a table view with the prototype cell as well. So my MainViewController.h file looks like below:
#import <UIKit/UIKit.h>
#interface MainViewController : UIViewController <UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UIBarButtonItem *sidebarButton;
#end
I made the MainVewController a delegate of the UITableViewDataSource, is that the right idea here? My MainViewController.m looks like below:
#import "MainViewController.h"
#import "SWRevealViewController.h"
#interface MainViewController ()
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Home";
SWRevealViewController *revealViewController = self.revealViewController;
if(revealViewController) {
[self.sidebarButton setTarget: self.revealViewController];
[self.sidebarButton setAction: #selector(revealToggle:)];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1; //change to number of post objects in array (array.count)
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"basicCell" forIndexPath:indexPath];
return cell;
}
#end
I don't understand what I'm doing wrong here. Shouldn't my MainViewController's Table View be properly displaying the cell? Thoughts?
You should use in viewDidLoad:
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
I don't see the Table View outlet. Did you forget to connect the Table View from interface builder to your view controller header file? After doing that you should also assign the delegate and data source properties of the table view to "self".
Your class just conforms to <UITableViewDataSource>
you should also conform UITableViewDelegate do it this way.
#interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
You missed setting the delegate and dataSource
It can be done in 2 ways:
using code:
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
put this code in viewDidLoad:
using storyboard: ctrl drag from tableView to your ViewController and set it as delegate and dataSource. see the Image below.
EDIT:
Why don't we need to connect the table's cell as well?
Ans: Table cell is returned from dataSource method tableView:cellForRowAtIndexPath:. This cell is displayed in the tableView. So we don't connect it in the storyboard. However we can configure it in the storyboard.
What's the difference between data source and delegate?
Ans: Delegate: The delegate is an object that is delegated control of the user interface for that event.
Datasource: A data source is like a delegate except that, instead of being delegated control of the user interface, it is delegated control of data.
For more information see Delegates and Data Sources and this answer.

REUSE a UITableView with its UI/delegate methods

I have 2 UIViewControllers, the 2 ones are containing EXACTLY the SAME UITableView(with its custom cells and delegate methods).
My question is their any way to "centralize" the UITableView UI and code(datasource and delegates), so that I just have to modify in one file instead of 2 .
following up on my comment, the table view in the xib in your father vc and the delegate methods in your father vc are just in the same place because you chose it to be like that, the table view and the delegate methods are actually quite detached.
so create a new object, say FatherTableController which implements UITableViewDatasource and UITabelViewDelegate and copy those methods out of your FatherViewController into this FatherTableController
now in your FatherViewController, go like
FatherTableController tableController = [FatherTableController new]; //should be a property or a singleton
self.tableview.delegate = tableController;
self.tableview.datasource = tableController;
now you can do that in both your separate vc's that use the same table, and even use the exact same table contoller between the two views if you share it in some way (possibly via a singleton pattern, which can be useful for sharing state between the two view controllers)
Solution:
#interface FatherViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
#property (strong, nonatomic) IBOutlet UITableView *parentTableView;
#implementation FatherViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.parentTableView.delegate=self;
self.parentTableView.dataSource=self;
}
//declare the delegate / datasource methods
--------------------- CHILD VIEW CONTROLLER ---------------------
#interface ViewController : FatherViewController
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate=self;
self.tableView.dataSource=self;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [super numberOfSectionsInTableView:tableView];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [super tableView:tableView numberOfRowsInSection:section];
}

IOS drop down with UITextField and UITableView

I have created a drop down using UITextField and UITableView. When the user selects a textfield then a tableview is displayed as a drop down. I had set the tableview delegate and datasource in another class.
Now my issue is i wanted to get the text of the selected row in tableview on to the textfield i.e I want to send the tableview row text back to view controller(which consists of textfield) when user selects a row in tableview.
Thanks in advance.
add below code in In view did load of class in which you have added UITextField
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(addValueToTextField:) name:#"addValueToTextFiel" object:nil];
-(void)addValueToTextField:(NSNotification *) notification{
NSString* text = [notification text];
yourTextField.text = text;
}
And in Didselect delegate of UITable view of other class you have to add following code
UITableViewCell *selectedCell =[tableView cellForRowAtIndexPath:indexPath];
[[NSNotificationCenter defaultCenter] postNotificationName:#"addValueToTextField" object:nil userInfo:selectedCell.Text];
Or alternate of its is you can use custom delegates
Create custom delegate for controller with UITableView
ItemsList .h file
#protocol ItemsListDelegate : NSObject
#optional
- (void)itemSelected:(int)num withTitle:(NSString *)title;
#end
#interface ItemsList : UITableViewController{
id <ItemsListDelegate> delegate;
...
}
ItemsList .m file
#import "ItemsList.h"
#implementation ItemsList<UITableViewDataSource, UITableViewDelegate>
#synthesize delegate;
.....
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[delegate itemsSelected:[indexPath row] withTitle:[items objectAtIndex:[indexPath row]]];
}
.....
And in ViewController with your field set in .h
#import "ItemsList.h"
#interface ViewWithField<ItemsListDelegate>{
ItemsList *itemsList;
}
....
And in .m file
.....
- (void)viewDidLoad
{
itemList.delegate = self;
}
- (void)itemSelected:(int)num withTitle:(NSString *)title{
self.textField.text = title;
}
.....
Something like this. I don't check errors in this code. But look at this way.
Or use NotificationCenter, but this way is more correct.
Sorry for my ugly English.

Resources