While playing around with the iOS 7.1 SDK (on XCode 5.1.1), I am trying to intercept the calls to the delegate methods of UITableViewController by creating a class that implements the UITableViewDelegate and UITableViewDataSource protocol. I am expecting the tableView to delegate the calls to MyTableViewDelegate which does some customization and delegate back to HomeViewController.
Through debugging, I found tableView:cellForRowAtIndexPath: method and tableView:willDisplayCell:forRowAtIndexPath: of the HomeViewController, the drawRect: method of my custom UITableViewCell class was called, and the cells have right content. **So it seems to me that the cells are drawn to somewhere. But just not displaying on the screen (The table row divider lines were displayed on the screen though).**I wonder if anyone knows why it doesn't work. Below is the code snippet. Thanks in advance for your insights.
HomeViewController.h
// HomeViewController.h
#interface HomeViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
UITableViewController *tableViewController;
UITableView *tableView;
}
#property(nonatomic, strong) MyTableViewDelegate *myDelegate;
#end
HomeViewController.m
// HomeViewController.m
- (void)loadView {
[super loadView];
tableViewController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
[self addChildViewController:tableViewController];
[tableViewController didMoveToParentViewController:self];
myDelegate = [[MyTableViewDelegate alloc] init];
[myDelegate setDelegate:self];
tableView = [[tableViewController tableView] retain];
[tableView setFrame:[[self view] bounds]];
[tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
[tableView setDelegate:myDelegate];
[tableView setDataSource:myDelegate];
[tableView setClipsToBounds:NO];
[[self view] addSubview:tableView];
}
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyEntry *entry = [self entryAtIndexPath:indexPath];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellClass"];
if (cell == nil) cell = [[[cellClass alloc] initWithReuseIdentifier:#"cellClass"] autorelease];
[self configureCell:cell forEntry:entry];
return cell;
}
MyTableViewDelegate.h
// MyTableViewDelegate.h
#interface MyTableViewDelegate : NSObject <UITableViewDelegate, UITableViewDataSource> {
}
#property (nonatomic, assign) id delegate;
#end
MyTableViewDelegate.m
// MyTableViewDelegate.m
#implementation MyTableViewDelegate
#synthesize delegate;
// Display customization
#pragma mark - Delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([delegate respondsToSelector:#selector(tableView:willDisplayCell:forRowAtIndexPath:)]) {
<...some customization ....>
[delegate tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath];
}
}
...
// and all other required and optional methods declared by the UITableViewDelegate and UITableViewDataSource protocol.
EDITED:
if I set the delegate and dataSource of tableView to the HomeViewController instance rather than MyTableViewDelegate instance, it works just fine.
// HomeViewController.m
- (void)loadView {
[super loadView];
...
[tableView setDelegate:self];
[tableView setDataSource:self];
...
EDITED:
Normally we can just do all the work in HomeViewController. However, in my case, I am trying to see if it's possible to insert a layer between HomeViewController and tableView. I have a special use case where I would expect HomeViewController to not be able to override the customization implemented in MyTableViewDelegate (intended to be a library). Hence, it's not a good idea to implement MyTableViewDelegate as a base class and make HomeViewController derive from it.
As far as I can see, the only connection between the HomeViewController and tableView is that tableView is managed as a subView by HomeViewController and tableViewController is also a childViewController of HomeViewController. Would the additional layer (ie. MyTableViewDelegate) break this connection since MyTableViewDelegate delegates every method call back to HomeViewController? If so, how does it break? Again, without the MyTableViewDelegate layer, the above code works just fine.
Related
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.
I added this code to my ViewController class under (#pragma mark - UITableViewDelegate Methods)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"selected cell = %#", cell.textLabel.text);
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
NSLog(#"row = %ld", indexPath.row);
}
I just want to display the text that I select but it's not working. Any ideas what I'm doing wrong?
Full code is here: IOS 8 Objective-C Search bar and Table view Using Google Autocomplete
Thanks for the help!
EDIT:
I also added the following code:
viewDidLoad method (ViewController.m)
_tableView.allowsSelectionDuringEditing = YES;
_tableView.delegate = self;
_tableView.dataSource = self;
changed ViewController.h code from
#interface ViewController : UIViewController
to
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
in viewDidLoad set the delegates
_table.delegate = self;
_table.dataSource = self;
in the interface then declare the protocols
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
[tableView reloadRowsAtIndexPaths:]... only works when wrapped inbetween [tableView beginUpdates] and [tableView endUpdates];
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
check if the delegate is set to self
_tableView.delegate = self;
Use the debugger and break points.
I would put a break point in:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
and see if the program stops there or not.
Have you set this properties in Interface Builder?
Adding the method is not enough. You should set your 'delegate' property of table view to the 'self' where 'self' means your view controller.
For example in your .m file:
#interface MyViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, weak) IBOutlet UITableView *tableView;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
}
...
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...
}
EDIT: Also please make sure you have connected the tableView property with a table view object inside of Interface Builder.
Method name changed or wrong. You can drag-drop to set the ViewController as the delegate. But this method needed to process when row touched:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("section: \(indexPath.section)")
print("row: \(indexPath.row)")
// caller is set to the calling ViewController, when it "prepare" to launch this one
caller.payFromSelection( selectedIndex: indexPath.row )
}
Back in the main controller, it gets the value and closes the child vc having the tableview...
func payFromSelection( selectedIndex:Int ) -> Void {
print( selectedIndex )
self.navigationController?.popViewController(animated: true)
//self.navigationController?.popToRootViewController(animated: true)
}
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.
I have two view controllers (Both Embedded in Navigation Controllers) in tab bar controller. ViewControllerA is has two buttons (MybuttonA and MybuttonB with enabled box unchecked in storyboard). ViewControllerB is a TableViewController. I would like to enable a buttons in ViewControllerA upon selecting specific rows in ViewControllerB table. Each button is expected to push different view controllers when enabled.
My present code works only when ViewControllersA and ViewControllersB are not embedded in Navigation Controllers. But without Navigation controller embedding, the enabled buttons does not push ViewControllers.
ViewControllerA.h
#import <UIKit/UIKit.h>
#interface ViewControllerA : UIViewController {
IBOutlet UIButton * MybuttonA;
IBOutlet UIButton * MybuttonB;
}
-(IBAction)mybuttonaction:(id)sender;
#property(strong,nonatomic)UIButton *MybuttonA;
#property(strong,nonatomic)UIButton *MybuttonB;
#end
ViewControllerA.m
#import "ViewControllerA.h"
#interface ViewControllerA ()
#end
#implementation ViewControllerA
#synthesize MybuttonA;
#synthesize MybuttonB;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(IBAction)mybuttonaction:(id)sender{
//write code to push view controller
}
ViewControllerB.h
#import <UIKit/UIKit.h>
#import "ViewControllerA.h"
#interface ViewControllerB : UITableViewController{
ViewControllerA *viewcontrollerA;
}
#property (nonatomic, retain) ViewControllerA *viewcontrollerA;
#end
ViewControllerB.m
#import "ViewControllerB.h"
#import "ViewControllerA.h"
#interface ViewControllerB () {
}
#end
#implementation ViewControllerB
#synthesize viewcontrollerA;
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"CONTENTS";
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(reload) forControlEvents:UIControlEventValueChanged];
[self reload];
[self.refreshControl beginRefreshing];
viewcontrollerA = [self.tabBarController.viewControllers objectAtIndex:0];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{return 1;}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
{return 5;}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat: #"Cell #%d", indexPath.row];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSString* value = [tableView cellForRowAtIndexPath:indexPath].textLabel.text;
if ([value isEqual:#"1"]){
viewcontrollerA.MybuttonA.enabled=YES;
}
else if ([value isEqual:#"2"])
{
viewcontrollerA.MybuttonB.enabled=YES;
}
}
else {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
}
#end
Use this:
viewcontrollerA = ((UINavigationController*)[self.tabBarController.viewControllers objectAtIndex:0] ).topViewController
Instead of this:
viewcontrollerA = [self.tabBarController.viewControllers objectAtIndex:0];
Use Delegation or 'NSNotification' to pass messages in between objects. Create a protocol for class B and send a message to its delegate when selecting particular row in it and make view controller A conform to this protocol. Make changes in view controller A when it receives the delegate message from view controller B.
Or for a loose broadcasting, you can fire/post a notification on row selection in view controller B and make view controller A a listener to it and change views on the posting of notification.
Although Delegation is the way to go in your case.
I have a UITableView in a popover. When a user selects a row in the popover I am wanting it to close the popover and save some data in the cell to a variable in the parent view controller. What is the most efficient way to do this?
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
In this method you handle the user selection. For passing variables between view controllers from a UITableView to another ViewController read this great tutorial from ray wenderlich : http://www.raywenderlich.com/1797/how-to-create-a-simple-iphone-app-tutorial-part-1
Create a delegate in popover's table view controller and pass the variable to it as cell data
In .h of the popover's table view controller
#protocol PopoverTableViewControllerDelegate <NSObject>
- (void)didSelectRow:(NSString *)cellDataString;
#end
#interface PopoverTableViewController : UITableViewController
#property (strong, nonatomic) id<PopoverTableViewControllerDelegate> delegate;
#end
In the .m's didSelectRowAtIndexPath call the delegate and pass the cell data variable as
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.delegate didSelectRow:cellDataString];
}
- (void)dealloc
{
[super dealloc];
[_delegate release];
}
Implement it in parent view controller in .h implement the PopoverTableViewControllerDelegate as
#interface ParentViewController : UIViewController <PopoverTableViewControllerDelegate>
#property (strong, nonatomic) NSString *cellDataString;
#end
and in .m implement the delegate as
- (void)dealloc
{
[super dealloc];
[_cellDataString release];
}
PopoverTableViewController *popoverTableViewController = [[[PopoverTableViewController alloc] init] autorelease];
popoverTableViewController.delegate = self;
- (void)didSelectRow:(NSString *)cellDataString
{
self.cellDataString = cellDataString;
[popOverController dismissPopoverAnimated:YES];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ParentViewController *parent = [[ParentViewController alloc] initWithNibName:#"ParentViewController" bundle:nil];
parent.variable = //do something you want
[self.navigationcontroller pushViewController:parent animated:YES];
}
Something like this... Hope this helps...