I can't figure out how to get this UITableView to display.
The code creates the table fully programmatically except for the nib.
The nib is a simple cell that works in other UITableViews in the same app.
When the table view is created and added as a subview to the view and reloadData is called then numberOfSectionsInTableView: and tableView:numberOfRowsInSection: are both called. They both return 1. (I checked in the debugger.)
However tableView:cellForRowAtIndexPath: is never called.
I add the tableView to it's parent view with a NSLayoutConstraint that forces it's height, but nothing is displayed in that spot.
The view hierarchy debugger shows nothing in that spot and that there isn't a view on top of the table.
The app runs without any error or crashes.
I would appreciate your help.
#interface MyViewCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UIButton *button;
#end
#implementation MyViewCell
#end
#interface MyDataTableEntry : NSObject
#property (nonatomic, strong) NSString* title;
#end
#implementation MyDataTableEntry
#end
#interface MyDataTable : NSObject <UITableViewDataSource, UITableViewDelegate>
#property (strong, nonatomic) UITableView *tableView;
#property (strong, nonatomic) NSArray *rows;
#property (strong, nonatomic) UINib* nib;
#end
#implementation MyDataTable
- (UINib*)getAndRegisterNib:(NSString*)reuseIdentifier {
UINib* nib = [UINib nibWithNibName:reuseIdentifier bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:reuseIdentifier];
return nib;
}
- (id)initWithRows:(NSArray*) rows andView:(UIView*)view {
if (self = [super init]) {
self.rows = rows;
self.tableView = [[UITableView alloc] initWithFrame:view.bounds style:UITableViewStylePlain];
self.nib = [self getAndRegisterNib:#"PrototypeCell"];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.backgroundColor = [UIColor grayColor];
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.rows.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PrototypeCell" forIndexPath:indexPath];
MyDataTableEntry* entry = self.rows[indexPath.row];
[cell.button setTitle:entry.name forState:UIControlStateNormal];
cell.button.backgroundColor = [UIColor blueColor];
return cell;
}
#end
Here is a snippet that creates the table:
MyDataTableEntry* entry = [[MyDataTableEntry alloc] init];
entry.title = #"hello";
self.dataTable = [[MyDataTable alloc] initWithRows:#[entry] andView:self.view];
[self.view addSubview:self.dataTable.tableView];
[self.dataTable.tableView setNeedsLayout];
[self.dataTable.tableView layoutIfNeeded];
[self.dataTable.tableView reloadData];
Without confirming in a debugger:
The problem is here:
self.dataTable = [[MyDataTable alloc] initWithRows:#[entry] andView:self.view];
[self.view addSubview:self.dataTable];
self.dataTable is an NSObject. The second line should read something like this:
[self.view addSubview:self.dataTable.tableView];
I would also suggest that you engage in some refactoring. MyDataTable is not a view. It's a controller. So rename to MyDataTableController. Then the bug would have been more obvious.
I would also ask why you are doing this in code? you are already using nibs. So why not go to storyboards. Doing that and using UITableViewController as a base class should enable you to remove a lot of the code and potential bugs.
Related
I have a viewController that contains a programmatically created tableView.
#property (strong, nonatomic) UITableView *numbersTableView;
//...
self.numbersTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, -100, self.view.frame.size.width-20, 50)];
self.numbersTableView.delegate = self;
self.numbersTableView.dataSource = self;
self.numbersTableView.tag = 1;
self.numbersTableView.layer.cornerRadius = 5;
[self.numbersTableView setBounces:false];
[self.numbersTableView setHidden:true];
[self.numbersTableView registerClass:[AZGCountryCell class] forCellReuseIdentifier:#"NumbersTableViewCell"];
[self.view addSubview:self.numbersTableView];
For the prototype cell I want to use a prototype that I created somewhere else in another viewController and I designed It in storyboard.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if (tableView.tag == 1) { //tableView == self.numbersTableView
NSString *cellID = #"NumbersTableViewCell";
AZGCountryCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[AZGCountryCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
cell.countryName.text = self.numbersArray[indexPath.row].number;
cell.flagImageView.image = [UIImage imageNamed:self.numbersArray[indexPath.row].country];
return cell;
}
}
And my custom cell contains one UIImageView and one UILabel but when I run the code the cells are empty and I can see the UIImageView and UILabel are nil!
#import <UIKit/UIKit.h>
#interface AZGCountryCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIImageView *flagImageView;
#property (strong, nonatomic) IBOutlet UILabel *countryName;
#property (strong, nonatomic) IBOutlet UILabel *countryCode;
#end
#import "AZGCountryCell.h"
#implementation AZGCountryCell
#synthesize flagImageView;
#synthesize countryName;
- (void)awakeFromNib {
[super awakeFromNib];
self.flagImageView.layer.cornerRadius = self.flagImageView.frame.size.width/2;
self.flagImageView.layer.masksToBounds = YES;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
}
#end
Then what should I do to have properly filled cells in my numbersTableView?
Unfortunately you can't reuse a table cell you've defined in a table view in a storyboard in your separate class.
In order for you to be able to share your cell between the different views, you'll have to extract the view to a separate nib and register that nib go be used on both tables.
Alternatively you can implement the cell's UI in code in the table view cell subclass.
You getting empty cell because custom tableview cell not registered, use
[self.numbersTableView registerNib:[UINib nibWithNibName:#"AZGCountryCell" bundle:nil] forCellReuseIdentifier:#"NumbersTableViewCell"];
instead of
[self.numbersTableView registerClass:[AZGCountryCell class] forCellReuseIdentifier:#"NumbersTableViewCell"];
I have two controllers: the main one is LocationItemTableViewController and contains all the graphic elements and the unfiltered table; the other one is SearchResultsTableViewController, which is used to process the results of the search. I want the search results to be displayed on LocationItemTableViewController, in a new table.
When the search starts updateSearchResultsForSearchController is called on the main controller and this assigns the results of the search to resultsController (an instance of SearchResultsTableViewController) which displays them on the main view controller.
Issues:
When updateSearchResultsForSearchController is called my NavigationBar unexpectedly disappears! Why?
When updateSearchResultsForSearchController is called the results are displayed correctly but if I scroll them up they pass underneath the SearchBar and then they show up again where there should be the NavigationBar..
When I click on a button that I have implemented in cellForRowAtIndexPath it gives me this warning:
Warning: Attempt to present UIAlertController: xxxxxxx on LocationItemTableViewController: xxxxxx whose view is not in the window hierarchy!
The button is supposed to call a kind of action sheet (UIAlertController) on LocationItemTableViewController.
Clearly there is something conceptually wrong in what I am doing but I cannot understand what...
Any hint please? Thanks!
LocationItemTableViewController.h
#interface LocationItemTableViewController : UIViewController <UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating, UIAlertViewDelegate, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate, NSCoding, UIActionSheetDelegate>
#property (strong,nonatomic) NSMutableArray *filteredLocationItemArray; //Filtered results array
#property (nonatomic, retain) IBOutlet UINavigationBar *NavigationBar;
LocationItemTableViewController.m
#interface LocationItemTableViewController ()
#property (nonatomic, strong) UISearchController *searchController;
#property (nonatomic, strong) NSMutableArray *searchResults;
#property (nonatomic, strong) SearchResultsTableViewController *resultsController;
- (void)viewDidLoad
{
[super viewDidLoad];
self.searchResults = [NSMutableArray arrayWithCapacity:[self.LocationItemArray count]];
resultsController = [[SearchResultsTableViewController alloc] init];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:resultsController];
self.searchController.searchResultsUpdater = self;
self.tableView.tableHeaderView = self.searchController.searchBar;
self.definesPresentationContext = YES;
self.searchController.searchBar.placeholder = nil;
[self.searchController.searchBar sizeToFit];
self.searchController.dimsBackgroundDuringPresentation = YES;
self.searchController.searchBar.delegate = self;
self.searchController.delegate = self;
self.tableView.frame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y + NavigationBar.bounds.size.height +20, self.tableView.frame.size.width, self.tableView.contentSize.height);
}
-(void)updateSearchResultsForSearchController:(UISearchController *)searchController {
NSString *searchString = [self.searchController.searchBar text];
NSString *scope = nil;
[self updateFilteredContentForProductName:searchString type:scope];
if (self.searchController.searchResultsController) {
resultsController.filteredEvents = self.searchResults;
resultsController.tableView.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height-ToolBar.bounds.size.height);
//Now reload the table but this time containing the search results
[resultsController.tableView reloadData];
}
}
SearchResultsTableViewController.h
#interface SearchResultsTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *filteredEvents;
SearchResultsTableViewController.m
#interface SearchResultsTableViewController ()
#property (nonatomic, strong) LocationItemTableViewController *instanceOfLocationItemTableViewController;
#end
#implementation SearchResultsTableViewController
#synthesize instanceOfLocationItemTableViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
//Create instance of LocationItemTableViewController.h
instanceOfLocationItemTableViewController = [[LocationItemTableViewController alloc] init];
[self.tableView setAllowsSelection:NO];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.filteredEvents ? 1 : 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"CALLED numberOfRowsInSection --: %lu", (unsigned long)[self.filteredEvents count]);
return [self.filteredEvents count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Create a new LocationItem Object
LocationItem *locationItem = nil;
locationItem = [self.filteredEvents objectAtIndex:[indexPath row]];
static NSString *CellIdentifier = #"SearchResultCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
....
[[cell textLabel] setText:[locationItem name]];
return cell;
}
Having an issue where the array values do not display in my tableview cells, but can be printed out correctly with NSLog. Thanks in advance for your help!
TableViewCell .h
#import <UIKit/UIKit.h>
#interface TableViewCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIImageView *image;
#property (strong, nonatomic) IBOutlet UILabel *label;
#end
TableViewController.h
#import <UIKit/UIKit.h>
#interface TableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
#property(nonatomic, strong) NSMutableArray *imagesArray;
#property(nonatomic, strong) NSMutableArray *namesArray;
#end
TableViewController.m
#import "TableViewController.h"
#import "TableViewCell.h"
#interface TableViewController ()
#end
#implementation TableViewController
#synthesize primaryWeaponNames = _primaryWeaponNames;
- (void)viewDidLoad {
[super viewDidLoad];
[self setupArrays];
}
- (void)setupArrays {
_namesArray = [[NSMutableArray alloc] initWithObjects:
#"NAME1", #"NAME2", #"NAME3"
nil];
self.imagesArray = [[NSMutableArray alloc] initWithObjects:
#"IMG1", #"IMG2", #"IMG3"
nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (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 _namesArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TableViewCell";
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.nameLabel.text = [NSString stringWithFormat:#"%#", [_namesArray objectAtIndex:indexPath.row]];
NSLog(#"%#", _namesArray[indexPath.row]);
cell.image.image = [self.imagesArray objectAtIndex:indexPath.row];
[cell setAccessoryType: UITableViewCellAccessoryDisclosureIndicator];
return cell;
}
When you make the cell in a xib file, you should register the nib in viewDidLoad. Since you didn't do that, the dequeue method will return nil, and you're just creating a default cell (in the if (cell == nil) clause) instead of your custom cell. The default cell doesn't have a nameLabel or image property, so the lines to set the values of those outlets won't do anything.
[self.tableView registerNib:[UINib nibWithNibName:#"Your nib name here" bundle:nil] forCellReuseIdentifier:#"TableViewCell];
In your TableViewController class, you need to include
self.tableView.delegate = self;
self.tableView.datasource = self;
in your viewDidLoad method. Or you can do this in interface builder by right click dragging the table view in your tableview controller to file's owner and setting delegate, then repeat for datasource
I have a UITableViewController class that I call "MasterViewController".
From within MasterViewController I want to display a tableview that uses a different UITableViewController (not MasterViewController). I am doing this as another tableview is already using MasterViewController as its delegate and datasource.
I have the following logic in a method of MasterViewController;
ToTableViewController *toController = [[ToTableViewController alloc] init];
UIView *toView = [[UIView alloc] initWithFrame:CGRectMake(10,10,250,200)];
//toView.backgroundColor = [UIColor redColor];
UITableView *toTableView = [[UITableView alloc] initWithFrame:CGRectMake(10,10,220,180) style:UITableViewStylePlain];
[toTableView setDelegate:toController];
[toTableView setDataSource:toController];
[toView addSubview:toTableView];
[self.view addSubview:toView];
[toTableView reloadData];
I want the ToTableViewController to be the delegate and datasource for this new tableview (toTableView).
The problem is that my ToTableViewController cellForRowAtIndexPath method is not being called. In fact, none of the delegate methods are being called.
Any feedback would be appreciated.
Tim
I am pretty sure your issue is that the toController is being released too early. Using ARC (I assume you are too), I played around with it following what you are trying todo. And I got the same result where the delegate methods appeared NOT to get called. What solved it was to NOT use a local variable for the toController. Instead declare it as a member to the MasterViewController class like
#property (strong, nonatomic) ToTableViewController *toController;
then use the variable name _toController to refer to it in the code.
EDIT: just to be clear in my first test I had ToTableViewController inherit from a UITableViewController. Since as such I really didn't need that added UITableView you are creating and attaching the delegates too (you could just use the _toController.view directly) SO on this second test I created a ToTableViewController from scratch inheriting from the delegate protocols where the separate UITableView becomes necessary. Just for completeness here is the code that works:
ToTableViewController.h
#import <Foundation/Foundation.h>
#interface ToTableViewController : NSObject <UITableViewDelegate, UITableViewDataSource>
#end
ToTableViewController.m
#import "ToTableViewController.h"
#implementation ToTableViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 13;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *str1 = #"Row #";
cell.textLabel.text = [str1 stringByAppendingFormat:#"%d", indexPath.row+1];
return cell;
}
#end
MasterViewController.m (I declare the toController in the .m file as shown but could do so the .h file instead...)
#import "MasterViewController.h"
#import "ToTableViewController.h"
#interface MasterViewController ()
#property (strong, nonatomic) ToTableViewController *toController;
#end
#implementation MasterViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_toController = [[ToTableViewController alloc] init];
UIView *toView = [[UIView alloc] initWithFrame:CGRectMake(10,10,250,200)];
toView.backgroundColor = [UIColor redColor];
UITableView *toTableView = [[UITableView alloc] initWithFrame:CGRectMake(10,10,220,180) style:UITableViewStylePlain];
[toTableView setDelegate:_toController];
[toTableView setDataSource:_toController];
[toView addSubview:toTableView];
[self.view addSubview:toView];
}
#end
I have a UITableViewCell nib file in which there is a UIImageView and a UILabel. I have outlets for both of these to my controller as well as an outlet for the cell itself. I am setting the label and image programmatically, but the image does not show up.
So I went to test it, and even if I set the image itself in the nib file, it does not show up. If I set the background color, it shows up fine. Any ideas? I'm stuck.
It seems to be unrelated to code, in my mind, since it doesn't even work via nib file. But here it is anyway in case it helps somehow.
MyViewController.h
#interface MyViewController : UITableViewController
#property (strong, nonatomic) MyModel *myModel;
#property (strong, nonatomic) NSArray *tableViewCells;
#property (strong, nonatomic) IBOutlet UITableViewCell *tableViewCell;
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
#property (weak, nonatomic) IBOutlet UIImageView *myImage;
#end
MyViewController.m
#interface MyViewController ()
- (void)bindMyModel:(MyModel*)model toView:(UITableViewCell*)view;
- (UITableViewCell*)copyUITableViewCell:(UITableViewCell*)cell;
#end
#implementation MenuViewController
#synthesize myModel = _myModel;
#synthesize tableViewCells = _tableViewCells;
#synthesize tableViewCell = _tableViewCell;
#synthesize myLabel = _myLabel;
#synthesize myImage = _myImage;
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *cells = [[NSMutableArray alloc] init];
[MyModel loadAndOnSuccess:^(id data, id context) {
self.myModel = data;
for (MyModel *item in self.myModel.items) {
[[NSBundle mainBundle] loadNibNamed:#"TableCellNib" owner:self options:nil];
[self bindMyModel:item toView:self.tableViewCell];
[cells addObject:[self copyUITableViewCell:self.tableViewCell]];
self.tableViewCell = nil;
}
self.tableViewCells = [[NSArray alloc] initWithArray:cells];
} onFail:^(NSString *error, id context) {
NSLog(#"FAIL with error: %#", error);
}];
}
- (void)viewDidUnload
{
[self setTableViewCell:nil];
[self setMyLabel:nil];
[self setMyImage:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
- (void) bindMyModel:(MyModel*)model toView:(UITableViewCell*)view
{
if (view) {
self.myLabel.text = model.myLabelText;
self.myImage.image = model.myImageResource;
self.myLabel = nil;
self.myImage = nil;
}
}
- (UITableViewCell*)copyUITableViewCell:(UITableViewCell*)cell
{
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject: cell];
return [NSKeyedUnarchiver unarchiveObjectWithData: archivedData];
}
#pragma mark - Table view data source
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [self.tableViewCells objectAtIndex:indexPath.row];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.myModel.items.count;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Irrelevant code here
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return ((UITableViewCell*)[self.tableViewCells objectAtIndex:indexPath.row]).frame.size.height;
}
#end
You are trying to use two IBOutlets on your UITableViewController to populate a multitude of UILabels and UIImageViews that are part of the UITableViewCell. You need to create a custom subclass of UITableViewCell and add the IBOutlets to that subclass.
#interface CustomCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
#property (weak, nonatomic) IBOutlet UIImageView *myImage;
#end
then in your bindMyModel:toView:
- (void) bindMyModel:(MyModel*)model toView:(CustomCell*)view
{
if (view) {
view.myLabel.text = model.myLabelText;
view.myImage.image = model.myImageResource;
}
}
Now you have independent IBOutlets for each of your Cells. You will also need to change some of your bindings as well. This is a fix, but honestly I would rewrite a lot of the code and just use dequeueReusableCellWithIdentifier in your cellForRowAtIndexPath call, and keep a pool of CustomCells that you will reuse, and just set up the myLabel.text and myImage.image in the cellForRowAtIndexPath call.