UITableView Delegate and DataSource methods not getting called in a custom view - ios

I created a UIView subclass, which has a tableView as a property.
I create the tableView object in the initWithFrame of the view and set its delegate and dataSource to the custom view.
I also set the scrollEnabled to NO.
and then add it as a subview.
Result: The tableView appears, it doesn't scroll as expected.
BUT the datasource methods are not at all getting called.
the tableView displays nothing.
I added a breakpoint inside cellForRowAtIndexPath but the control never reaches there.
I have declared the UITableViewDataSource and UITableViewDelegate protocols in the interface of the custom view.
Here's the code in initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Custom initialization
self.frame = frame;
self.indexListView = [[UITableView alloc] initWithFrame:self.bounds];
self.indexListView.delegate = self;
self.indexListView.dataSource = self;
self.indexListView.scrollEnabled = NO;
[self addSubview:self.indexListView];
[self.indexListView reloadData];
if([self respondsToSelector:#selector(tableView:cellForRowAtIndexPath:)])
NSLog(#"YES.. selector is there");
}
return self;
}
What could be the problem ?

Related

UITableView didSelectRowAtIndexPath not being called

I have a view controller with a UITableView. I have both the datasource and delegate set to the view controller. The method cellForRowAtIndexPath: is being called and behaving as expected however didSelectRowAtIndexPath: is not being called. Any ideas whats going on here?
I have this in my .h:
#interface DetailViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
And in my viewDidLoad in the .m I have:
self.tableView.dataSource=self;
self.tableView.delegate=self;
It doesn't seem to make sense that the cellForRowAtIndexPath would be called but not didSelectRowAtIndexPath.This is what the method looks like for now:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"did select row at index path");
}
I have encountered some possible reasons:
Make sure the the editing property of tableView is set to NO. The didSelectRowAtIndex: method isn't called when the editing property of table is set to YES. By default this property is set to NO.
self.tableView.editing = NO;
Make sure the allowsSelection property is set to YES. By default it is.
Try to call.[super viewDidLoad] in the viewDidLoad method or setting the delegate and the dataSource properties in the init initializer.
Set the delegate and the dataSource properties in the loadView method and assign the tableView to the view property of the UIViewController. Here's an example:
- (void)loadView
{
UITableView *tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
tableView.delegate = self;
tableView.dataSource = self;
[tableView reloadData];
self.view = tableView;
}
(this is, actually, what I usually do) Try to subclass DetailViewController from UITableViewController which inherits from UIViewController too. UITableViewController comforms automatically to UITableViewDelegate and UITableViewDataSource.
Gesture recognizer on a view controller's view prevented touches, solved by setting gesture recognizer's cancelsTouchesInView to false.
In my case, I used UITapGestureRecognizer and it worked like a magic for me.
cell.tag=indexPath.row;
UITapGestureRecognizer* tap=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapOnCell:)];
[cell addGestureRecognizer:tap];
Here is my tapOnCell method implementation
-(void)tapOnCell:(UITapGestureRecognizer*)tap
{
ADPointCashTransaction* aTransaction=[transactionArray objectAtIndex:tap.view.tag];
// Work with aTransaction Object
}
Putting the answer in the comments of seto nugroho with my work around the problem. If you have a gesture recognizer in the tableView, then the didSelect row will not be called. What you can do is increase the number of taps for the gesture recognizer to two.
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
gestureRecognizer.numberOfTapsRequired = 2
view.addGestureRecognizer(gestureRecognizer)`
I'll add one more: make sure you don't have any other views overlapping the table or cells at any point, even hidden ones. If you do, disable 'User interaction enabled' in storyboard unless you explicitly need to catch gestures through the view.
In my case, I was assigning the dataSource and calling reloadData() all in a background thread. So, make sure you are doing this on the main thread.

How to initialize UITableView before viewDidLoad?

I have to initialize my UITableView instance within viewDidLoad, such as:
tableView1 = UITableView(frame: frame)
However, when I tried to access the view controller that includes the tableView1, the UITableViewDataSource's tableView: numberOfRowsInSection: is called before the viewDidLoad method, and within the numberOfRowsInSection:, I want to refer to the tableView1 like if tableView == tableView1.
However, then how can I initialize the tableView1 before calling viewDidLoad? If I tried to initialize it within viewDidLoad but still refer to it from within numberOfRowsInSection:, then it causes the error: fatal error: unexpectedly found nil while unwrapping an Optional value because it is not initialized yet and hence its value is nil.
I think if I use storyboard and #IBOutlet, I don't have the problem. But now I use them all from my code, so I'm not sure how I can cope with the issue.
UPDATE
If this cannot be done as posted by #coolcracker, then how can I port this Objective-C code?
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo;
if (tableView == _tableView1) {
sectionInfo = _frc1.sections[section];
} else if (tableView == _tableView2) {
sectionInfo = _frc2.sections[section];
}
return sectionInfo.numberOfObjects;
}
Don’t use Storyboard at all, just declare and initialise your tableview as follows
#interface ApplicationsPageGroups : UIViewController
{
UITableView* tableView1;
}
- (void)viewDidLoad
{
[super viewDidLoad];
tableView1 = [[UITableView alloc] init];
tableView1.frame = CGRectMake(10, 95, [UIScreen mainScreen].bounds.size.width-20, [UIScreen mainScreen].bounds.size.height-230);
tableView1.delegate = self;
tableView1.dataSource = self;
tableView1.tableFooterView = [[UIView alloc] init] ;
[self.view addSubview:tableView1];
[self getTableData];
}
-(void) getTableData
{
// after you get some array of data to fill the tableview
[tableView1 reloadData];
}
Now override all the UITableView delegate and dataSource methods.
simply add
[tableView1 reloadData];
which call your DataSource methods call again
You could override the view controller initWithFrame or initWithNibOrNil method and initialize the tableViews there.
Also, have you tried initializing the table views BEFORE calling the [super viewDidLoad] method? I have not checked, but it may be possible that the superclass controller is calling the reload method before your initializers, depending on your class hierarchy.

Faster UICollectionView cells

There are a lot of answers out there for loading images into UITableViews or UICollectionViews. But what if my UICollectionView is displaying views from other view controllers?
Say in my UICollectionViewCell subclass I have this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.catViewController = [[CatViewController alloc] initWithNibName:nil bundle:nil];
self.catViewController.view.frame = self.bounds;
[self addSubview:self.catViewController.view];
}
return self;
}
And in my collection view Datasource:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:kSomeId forIndexPath:indexPath];
cell.catViewController.data = self.data[indexPath.row]; //see below
return cell;
}
The catViewController has a setter for the data property. When this property is set, the cat will load it's image, along with some other related images for that view. So how do I properly reuse the MyCell cells so that the collection view doesn't stutter each time it creates (or reuses) a cell? Each MyCell takes up the full width of the collection view, which scrolls horizontally, so every time a new cell scrolls into view, the collection view stalls for a moment.
For High performance CollectionView cells , Use following new stuffs in iOS10 with xcode8
Implement protocol "UICollectionViewDataSourcePrefetching" in you ViewController as
class ViewController: UIViewController , UICollectionViewDataSourcePrefetching {
Set following delegates to your collection view in storyboard (see the attached image)
or programmatically
In ViewController's viewDidLoad method
collectionView.delegate = self
collectionView.dataSource = self
collectionView.prefetchDataSource = self

cellForRowAtIndexPath not called but numberOfRowsInSection called

I have a simple problem but I can't understand what is.
I have a UIViewControler (called RootController) that load a UIView (called SecondView) that contain a tableView. The problem is that the UIView call the numberOfSectionsInTableView and the numberOfRowsInSection but don't call cellForRowAtIndexPath and the table view is not displayed.
The code of the RootViewController is:
SecondView *secondView = [[seconddView alloc] initWithFrame:CGRectMake(0, 60, self.view.bounds.size.width, self.view.bounds.size.height)];
[self.view addSubview:secondView];
And the code of the SecondView is:
#interface SecondView () <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic,retain) UITableView *table;
#end
#implementation SecondView
#synthesize table;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.table = [[UITableView alloc] init];
self.table.delegate = self;
self.table.dataSource = self;
[self addSubview:self.table];
}
return self;
}
- (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];
}
cell.textLabel.text = #"Prova";
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 5;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
Can you help me to find the problem? Thank you.
You need to set the Frame of UITableView
My problem was that I had a simple class doing the implementing of my delegate and data source, but the lifetime of the simple class was too short.
I was doing
MyDataSourceClass* myClass = [[MyDataSourceClass alloc] initWithNSArray:someArray];
tableView.dataSource = self.tableViewDataSource;
tableView.delegate = self.tableViewDataSource;
[tableView reloadData];
// end of function, myClass goes out of scope, and apparently tableView has a weak reference to it
Needed to be doing
self.tableDataSource = [[MyDataSourceClass alloc] initWithNSArray:someArray];
tableView.dataSource = self.tableDataSource;
tableView.delegate = self.tableDataSource;
[tableView reloadData];
// now at the end of the function, tableDataSource is still alive, and the tableView will be able to query it.
Note that the code above is pseudocode from memory. Take from it the concept of "make sure your data source/delegate lives long", but don't copy paste it, because there's other stuff you need to do (like set your frame etc etc).
This could also happen if reloadData is called on a different thread. Make sure it is run on the main thread since all UI stuff has to happen on the main thread.
dispatch_async(dispatch_get_main_queue(),{
self.myTableView.reloadData()
});
In XCode6 same weird problem was occurring to me when "Add Missing Constraints" is applied on tableView on Storyboard to adjust views.
To resolve this issue on Storyboard first clear constraints:
then apply constraints in following fashion:
You can only call the viewcontroller's view AFTER viewDidLoad is called.
You can't interact with self.view in your init method
- (void)viewDidLoad {
[super viewDidLoad];
self.table = [[UITableView alloc] initWithFrame:self.view.bounds];
self.table.delegate = self;
self.table.dataSource = self;
[self addSubview:self.table];
}
In your case, you need to init your tableview with a frame (like a suggested in the code above). Just make sure you add the code in viewDidLoad in your viewController
SecondView *secondView = [[seconddView alloc] initWithFrame:CGRectMake(0, 60, self.view.bounds.size.width, self.view.bounds.size.height)];
[self.view addSubview:secondView];
I have same issue like you:
I have only one method from delegate protocol that called and this is numberOfRowsInSection. Meanwhile I have second method cellForRowAtIndexPath that was not called.
The reason of this behaviour was that my numberOfRowsInSection returned 0 rows and cellForRowAtIndexPath didn't call because it needed to draw 0 cells.
I would comment on Hussain Shabbir's answer, to clarify it, but as I am not yet able to comment, I will post an answer instead.
I had exactly the same issue. numberOfRowsInSection would fire, but cellForRowAt would not. I tore my code apart looking for the reason, but the reason is not in the code.
The solution (for me) was in the storyboard. I had not set constraints for the Table View. Select the table view, click the "Add New Constraints" icon (looks like a square TIE fighter) and set constraints for top, bottm, leading and trailing. Then cellForRowAt will be called and your table will populate.
I hope this helps someone.
This solution was specific for my case but maybe helps someone.
I had the same problem. I was using a computed property instead of stored property; so every time I call the tableView, I was getting a new one.
I had this code:
var tableView: UITableView{
let tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
tableView.register(SomeCell.self, forCellReuseIdentifier: cellID)
return tableView
}
And this is the fixed code:
lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
tableView.register(SomeCell.self, forCellReuseIdentifier: cellID)
return tableView
}()
Delete UITableView and add again in your ViewController

UICollectionView datasource methods not getting called, but are being set in the init

Here is my source code
- (id)initWithCollectionView:(UICollectionView *)collectionView
{
self = [super init];
if (self)
{
self.collectionView = collectionView;
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
[self.collectionView registerClass:[TWNTweetCell class] forCellWithReuseIdentifier:kCell];
self.collectionViewLayout = self.collectionView.collectionViewLayout;
self.tweetArray = #[];
self.tweetTextArray = #[];
self.twitter = [STTwitterAPI twitterAPIOSWithFirstAccount];
}
return self;
}
#pragma mark - CollectionView
#pragma mark DataSource
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section
{
return [self.tweetArray count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TWNTweetCell *cell = (TWNTweetCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kCell forIndexPath:indexPath];
NSDictionary *status = [self.tweetArray objectAtIndex:indexPath.row];
NSString *text = [status valueForKey:#"text"];
cell.usernameLabel.text = screenName;
// cell.createdAtLabel.text = dateString;
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth;
UITextView *textView = [self.tweetTextArray objectAtIndex:indexPath.row];
[cell setTweet:text withTweetTextView:textView];
return cell;
}
All the methods don't get interupted at all by breakpoints. The tweets are getting loaded in the log so I know everything else is ok, its just not recognizing the collection view. And yes i've set the
Anyone have any idea whats going on?
It is not your case, it might be helpful for others who will came here having problem with data source methods not being called. It could be assigning data source like:
collectionView.dataSource = MyDataSource()
which is wrong as dataSource is a weak reference so it needs to be stored by some strong reference to be alive after creating it. Added a private property in a ViewController to keep the strong reference, initialising and then assigning it fixes the issue.
A few suggestions:
Do all your UICollectionView setup and configuration in viewDidLoad.
Ensure you calling the create init method from your other class
Your tweetArray is also empty, so if the number of items method is called, it will return nothing and the other methods will not be called
A couple things:
1) in (and only in) your "init" method, use the underlying instance variable for your #property. That is,
_collectionView = collectionView;
_collectionView.dataSource = self;
_collectionView.delegate = self;
This is called "direct access", and more information can be seen in this related question.
2) in your .h file, make certain to declare that your object conforms to the data source & delegate protocols. E.G.
#interface JustinViewController : UIViewController <UICollectionViewDelegate, UICollectionViewDataSource>
for swift do this , set a property
//MARK: props
let dataSource = MyDataSource()
and in
viewDidLoad(){
// your other code
..
..
collectionView.dataSource = dataSource // it is a strong reference
}
apart form these other general pitfall are
not returning the count or the data source
not populating the data source
Add the collectionView to a view hierarchy.
In the init method you set the property (self.collectionView) but you do not add the collectionView to a view hierarchy. So the collectionView won't call any dataSource or delegate method.
I created collection view in storyboard and linked datasource and delegate but they were not being called in Xcode 8.0 with Swift 3.0. Tried multiple things but the solution was to declare the delegate and datasource in class declaration line:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
...
}
Previously when we linked delegate and datasource through storyboard it was not required, may be a bug :)
Call [collectionView reloadData] at the end of your init method. The collection view needs to be told to populate itself. I assume UICollectionViewController does this internally, but you don't seem to be using UICollectionViewController (or at least not in the usual way).

Resources