1) I started a new project in xcode using the single view application.
2) I deleted the default view controller and added a new UITableViewController
3) In storyboard, I dragged out a UITableViewController and set it to the one I just created
4) Set the reuse identifier
In my code I tried to override the init method to do some setup. Why is my custom init method not being called? When you are using storyboard, and you drag out a UITableViewController and set it to a custom class, can you not override the initWithStyle: method? When I put the setup in viewDidLoad then it worked.
Here is the code for the view controller:
#import "ItemsViewController.h"
#import "BNRItem.h"
#import "BNRItemStore.h"
#implementation ItemsViewController
- (id)init
{
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
for (int i = 0; i < 10; i++) {
[[BNRItemStore defaultStore] createItem];
NSLog(#"Test init");
}
}
return self;
}
- (id)initWithStyle:(UITableViewStyle)style
{
NSLog(#"test init style");
return [self init];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
NSLog(#"test tableview rowsinsection");
return [[[BNRItemStore defaultStore] allItems] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"test tableview cellforrow");
// Create an instance of UITableViewCell, with default appearance
// Check for a reusable cell first, use that if it exists
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"itemsCell"];
// If there is no reusable cell of this type, create a new one
if (!cell) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"itemsCell"];
}
// Set the text on the cell with the description of the item
// that is at the nth index of items, where n = row this cell
// will appear in on the tableview
[[cell textLabel] setText:#"Hello"];
return cell;
}
#end
init is only called when you using [[class alloc] init]
you can override
- (void)awakeFromNib
awakeFromNib
Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
- (void)awakeFromNib
Discussion
An awakeFromNib message is sent to each object loaded from the archive, but only if it can respond to the message, and only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet instance variables set.
Related
This one is driving me crazy - I don't know what am I missing.
here is my ViewController.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.tableView registerClass:[CurrentMatchCell class] forCellReuseIdentifier:#"CurrentMatchCell"];
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"1");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"2");
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"3");
CurrentMatchCell *cell = (CurrentMatchCell *)[tableView dequeueReusableCellWithIdentifier:#"CurrentMatchCell"];
if (cell == nil) {
NSLog(#"XXX");
}
[cell.matchDescription setText: #"Home Team vs Away Team"];
return cell;
}
Here is screenshots from the app.
delegate and datasource are set programmatically.
cell attributes :
And the .h file :
#interface CurrentMatchesViewController : UIViewController <NSFetchedResultsControllerDelegate,UITableViewDelegate, UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
So, I can see logs 1,2,3 being printed out, cell is not nill but I do not see my content. Why is that?
I only see a number of empty white cells (even if I return 0 or whatever it does show the same every time).
Thanks
If you create your table view and your cell prototypes in a storyboard, the storyboard loader takes care of registering the cell prototypes that you defined in the storyboard. So:
You don't need to call registerClass:forCellReuseIdentifier: again in the code. This will actually mess up your storyboard settings.
You can also use dequeueReusableCellWithIdentifier:forIndexPath: instead of dequeueReusableCellWithIdentifier:. That method always returns a cell, so you don't have to have a nil check.
Edit: If that doesn't do the trick, try calling [self.tableView reloadData] after setting the delegate / data source, or set the delegate and data source in the storyboard.
It is because you're not loading your custom nib. Try this. (Make sure CurrentMatchCell is the name of your xib file).
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UINib *nib = [UINib nibWithNibName:#"CurrentMatchCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"CurrentMatchCell"];
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
Edit: based on your comment: Don't register the custom cell class when using a storyboard.... it does that for you and it also sets the delegate. So try removing those lines from the viewdidload.... and second I would try actually making CurrentMatchesViewController a subclass of UITableViewController
I have a table view that has 3 rows in it. When one of the rows is tapped, the row height expands to show a UICollectionView with 3 cells in it.
I want the row to reduce height again when one of the collection view cells is tapped. This is what I've attempted:
//customCell.m
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
ProductViewController *productView = [[ProductViewController alloc]init];
[productView collapseRow];
}
//ProductViewController.m
#property NSIndexPath *selectedRow;
- (void)collapseRow {
menuCell *cell = (menuCell *)[self.tableView cellForRowAtIndexPath:self.selectedRow];
[self.tableView beginUpdates];
self.selectedRow = nil;
[self.tableView endUpdates];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView beginUpdates];
if ([indexPath compare:self.selectedRow] == NSOrderedSame) {
self.selectedRow = nil;
} else {
self.selectedRow = indexPath;
}
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; {
if ([indexPath compare:self.selectedRow] == NSOrderedSame)
return 105;
else
return 50;
}
Am i doing anything wrong here? I know that the table view is updating when I tap a collection view cell, but it's not checking against the currently open row and setting the height using that.
The things we have a ViewController(ProductViewController), containing an instance of UITableView, the cells of table view are created in CustomCell class.
1) Since ProductViewController has the instance of UITableView, so using that instance ProductViewController can call methods on UITableView and it's cells.
2) Also, while creating table view we set the delegate and dataSource of tableview as
tableView.delegate = self;
tableView.dataSource = self;
self is nothing but the reference of the class in which table view is being created(in your case it's ProductViewController)
you implement the tableView's delegate and dataSource method in the ProductViewController class
and what the table view does, if it needs to call a method on your class ProductViewController' instance it uses the reference contained in it's delegate and dataSource properties, which is nothing but the reference of ProductViewController. So calling the method using delegate/dataSource by table view will execute the methods in ProductViewController(if the method exists there).
3) Now as you have created a class called CustomCell, using which you create the custom cells and add on the table view, the table can call methods on the cell's instance but if you need the other way round(cell calling the method of table view or instance of ProductViewController, you can't do so since the cell does not know the reference/address of the ProductViewController), so to call a method on the instance of ProductViewController which contains the table view and cell, the cell should know the address of the ProductViewController. And to provide the cell with the reference/ address of ProductViewController we use the concept of delegation as used by UITableView in 2 point point above.
You can use the below delegation pattern implementation to meet the requirement of point 3-
In CustomCell.h class declare a delegate as
#protocol CustomCellDelegate;
#interface CustomCell : UITableViewCell {
//declare member variables
...
id<CustomCellDelegate> cellDelegate;
}
...
#property (nonatomic, assign) id<CustomCellDelegate> cellDelegate;
...
// method declarations
#end
#protocol CustomCellDelegate <NSObject>
- (void)collapseRow;
#end
in CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
#synthesize cellDelegate = _ cellDelegate;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
// methods to create cells
// methods to create collection view and it's items
// collection view item selection handler
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// using delegate will call the method collapseRow in ProductViewController
[self. cellDelegate collapseRow];
}
#end
Also, adopt the delegate in ProductViewController.h as
#import "CustomCell.h"
#interface ProductViewController:UIViewController < CustomCellDelegate >
and in in ProductViewController.m
when you are creating the cell as
CustomCell *cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
pass the reference of ProductViewController instance as
cell.cellDelegate = self;
You can also use
- (void)collapseRow
{
NSArray* indexArray = [NSArray arrayWithObjects:self.selectedRow, nil];
self.selectedRow = nil;
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationNone];
}
I think the actual problem for you lies in this method
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
ProductViewController *productView = [[ProductViewController alloc]init];
[productView collapseRow];
}
In the above method you are creating a new instance of ProductViewController using ProductViewController *productView = [[ProductViewController alloc]init]; and this will call the collapseRow, but as it's not on the instance of the previous ProductViewController class so the row of the table contained in the previous instance will not get collapsed.
In above method you should use delegation pattern, when you are creating the cells for the collection view than during cell creation just pass the reference of ProductViewController instance(using delegation) and when any collection view cell is selected, just use the same delegate(which contains the reference of ProductViewController instance) to call the collapseRow method on the correct class rather than allocating the new one.
This should work:
- (void)collapseRow {
NSIndexPath *selectedRow = self.selectedRow;
[self.tableView beginUpdates];
self.selectedRow = nil;
[self.tableView reloadRowsAtIndexPaths:#[selectedRow] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
Or you could simply:
- (void)collapseRow {
self.selectedRow = nil;
[self.tableview reloadData];
}
but that would cause all the cells to be reloaded, so it's not as efficient.
I am trying to make multiple view controllers with a common table view but i am not able to this i have made a view file and added table view in and importing that file to all view controllers but not able to get the same table view every
here is code :
//
// LeftTableViewClass.h
//
//
// Created by on 30/07/14.
// Copyright (c) 2014 All rights reserved.
//
#import <UIKit/UIKit.h>
#interface LeftTableViewClass : UITableView <UITableViewDataSource,UITableViewDelegate>
#end
//
// LeftTableViewClass.m
//
//
// Created by on 30/07/14.
// Copyright (c) 2014 All rights reserved.
//
#import "LeftTableViewClass.h"
#implementation LeftTableViewClass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 4;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//cell.contentView.layer.borderWidth = 1.0f;
//cell.contentView.layer.cornerRadius = 4.0f;
cell.textLabel.text = #"test";
return cell;
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%d",indexPath.row);
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
LeftTableViewClass *leftTableViewClass = [[LeftTableViewClass alloc] init];
[self.view addSubview:leftTableViewClass];
You can a make a table view class and initialise(alloc) it once and keep the reference for all the controllers.
I think the mistake you are making here is you think you need to subclass UITableView to define it's behavior. But instead Apple uses a delegate design pattern here. Any UITableView has two properties we should worry about here. delegate and dataSource.
The delegate pattern is when the object delegates certain actions to other objects. In this case you set the UITableView's delegate and datasource properties to the object that will define it's behaviors. delegate tells the table view what to do when an action is taken (like selecting a cell). dataSource is used when to tell how many cells and what kind there are.
In your case, you can subclass (although this isn't strictly necessary) but you never set your dataSource and delegate for your table view. You can do this in your init method if you like.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.dataSource = self;
self.delegate = self;
}
return self;
}
This now tells the table view to use itself as the delegate and dataSource. See this article for more information on UITableView here.
Create an UIView subclass with a costructor method which collects the datasource data [array] and frame value then inside the class include the code for table with reference to the frame size of view and display it whereever you want it by creating an instance and adding as a subview.
Why your current option doesn't work
Didn't provide the frame of the LeftTableViewClass instance you created.
The frame is the key to add the subviews.Make the instance allocation using initWithFrame method
Use
LeftTableViewClass *leftTableViewClass = [[LeftTableViewClass alloc] initWithFrame:CGRectMake(0, 0, 150, 200)];
I have created .h and .m files for UITableView called mainTableViewgm.h and mainTableViewgm.m resp. and I am calling -initWithFrame: method from my main view controller to this mainTableViewgm.m implementation file
[[mainTableViewgm alloc]initWithFrame:tableViewOne.frame]
Note that this tableview is in my main view controller. But I have created separate files for the tableView and have also set the custom class to mainTableViewgm in storyboard.
the -initWithFrame: methods appears as follows
- (id)initWithFrame:(CGRect)frame
{
//NSLog(#"kource data");
self = [super initWithFrame:frame];
if (self)
{
[self setDelegate:self];
[self setDataSource:self];
[self tableView:self cellForRowAtIndexPath:0];
[self tableView:self numberOfRowsInSection:1];
// Initialization code
}
return self;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"kource data");
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"kource data2");
UITableViewCell*cellOne =[[UITableViewCell alloc]init];
cellOne.detailTextLabel.text=#"text did appear";
return cellOne;
}
the -initWithFrame: is being called fine along with the 'if (self)' block in this method. But the problem is numberOfRowsInSection: and cellForRowAtIndexPath: are not being automatically called here . kource data/kource data2 never appear in log. What do I do to load the table? Are the delegate/datasource being set incorrectly?
I must mention that I have also set the UITableViewDelegate and UITableviewDataSource protocols:
#interface mainTableViewgm : UITableView <UITableViewDelegate,UITableViewDataSource>
#end
Help will be much appreciated. Thank you.
Your tableview is not loaded when the controller is initializing, so you cannot do that in the init methods. You have to move your code to the viewDidLoad method.
Also you are not setting the delegate and datasource on the tableview object (probably a type, you are setting them on the view controller). It should look like this:
- (void)viewDidLoad:(BOOL)animated {
[super viewDidLoad:animated];
[self.tableView setDelegate:self];
[self.tableView setDataSource:self]; // <- This will trigger the tableview to (re)load it's data
}
Next thing is to implement the UITableViewDataSource methods correctly. UITableViewCell *cellOne =[[UITableViewCell alloc] init]; is not returning a valid cell object. You should use at least initWithStyle:. And take a look how to use dequeueReusableCellWithIdentifier:forIndexPath:. A typical implementation would look like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
// Reuse/create cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Update cell contents
cell.textLabel.text = #"Your text here";
cell.detailTextLabel.text=#"text did appear";
return cell;
}
I can't believe I've been doing XCode programming for two years, and still hit this issue.
I had the same problem with XCode 6.1 - I was setting my UITableView's delegate & dataSource in the viewWillAppear function, but none of the delegate functions were kicking in.
However, if I right-clicked on the UITableView on the Storyboard, the circles for delegate and dataSource were empty.
The solution, then, is to hold down the CTRL key, and drag from each of these circles up to the name of your UIView which contains your UITableView:
After doing this, my UITableView happily populated itself.
(So, we're upto v6.1 of XCode now are we ? Do you think Apple ever going to make this thing, you know, friendly...? I would quite like to add a Bookmark in my code... that'd be a nice feature.)
I am calling a method in my TableViewController class from another class.
To call the method of displaying the tableview, I do this:
TableViewController *tableVC = [[TableViewController alloc]init];
[tableVC setTableViewContent];
then in TableViewController.h
#interface TableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *nameArray;
}
-(void)setTableViewContent;
#property (nonatomic, strong) IBOutlet UITableView *tableView;
#end
TableViewController.m
#implementation TableViewController
#synthesize tableView;
- (void)viewDidLoad
{
nameArray = [[NSMutableArray alloc]init];
[super viewDidLoad];
}
-(void)setTableViewContent{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
for(int i=0;i< [appDelegate.businessArray count];i++)
{
NSDictionary *businessDict = [[appDelegate.businessArray objectAtIndex:i] valueForKey:#"location"];
nameArray = [appDelegate.businessArray valueForKey:#"name"];
}
NSLog(#"%#", nameArray);
NSLog(#"tableview: %#", tableView);
// here tableview returns null
[tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [nameArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"updating tableview...");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = [nameArray objectAtIndex:indexPath.row];
return cell;
}
For some reason when I try to log the tableview, it returns null, so the ReloadData doesn't work. The delegate and datasource is connected properly in IB, and there is a referencing outlet for tableView.
Any idea what is going on here? Thanks in advance
If you added the table view controller to a container view, then you can get a reference to that controller in prepareForSegue. For a controller in a container view, prepareForSegue will be called right before the parent controller's viewDidLoad, so you don't need to do anything to invoke it. In my example below, I've called the segue "TableEmbed" -- you need to give the segue that identifier in IB.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"TableEmbed"]) {
TableViewController *tableVC = (TableViewController *)segue.destinationViewController;
[tableVC setTableViewContent];
}
}
Be aware that prepareForSegue:sender: is called before either controller's viewDidLoad is called, so you should move the initialization of your array to setTableViewContent, and your reloadTable should go into viewDidLoad.
BTW, it's not clear to me why you want to call setTableContent from your other class anyway. Why not move all the code in that method to the viewDidLoad method of the table view controller?
This is happening because you are calling a method on tableView before it actually exists. Simply initializing that class doesn't draw the table itself, so using reloadData before the table has actually been created doesn't really make any sense.
What you want to do in this situation is create your nameArray in whatever class is calling setTableViewContent, and then pass it in either via a custom init method, or by setting tableVC.nameArray before loading that table view controller.
What I would do is make custom init method like - (id)initWithArray:(NSMutableArray *)nameArr
Which should look something like this:
if (self = [super init]) {
nameArray = [nameArr copy];
}
return self;
Then where you have TableViewController *tableVC = [[TableViewController alloc]init]; put TableViewController *tableVC = [[TableViewController alloc]initWithArray:theNameArray]; where theNameArray is the content in setTableViewContent (which you are now generating in the same class that calls the table view instead of in the table view itself).
Make sense?
I solved a similar situation by creating a "safe" reload method on the UITableViewController:
- (void)reloadTableViewData
{
if ([self isViewLoaded])
[self.tableView reloadData];
}
According to the docs for isViewLoaded:
Calling this method reports whether the view is loaded. Unlike the view property, it does not attempt to load the view if it is not already in memory.
Therefore it is safe to call reloadTableViewData on the table view controller at any time.