Different UICollectionViews in UITableView objective-C - ios

I have 2 custom UITableViewCells in my TableView wich are using a UICollectionView. How can I separate each Data Model used for each TableViewCell in a different section like this:
if (indexPath.section == 0) {
//Each CollectionViewCell should display Profile Picture of User
} else if (indexPath.section == 1) {
//Each CollectionViewCell should display Pictures uploaded in this group
}
I am using the TableViewCell provided by Ash Furrow: http://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell
I want to achieve the same results as this guy: multiple UICollectionviews inside a UItabelview with sections - swift
but I have quite a different code and his is written in swift, and I don't don't know to solve my problem by using the solution provided answer given by #pbasdf
Edit: These are the cells, I want the "Going" Cell to show Profile Pictures of the Users and the "Photos" Cell to show images uploaded to this group, but both cells are currently using the same data model and I don't know how to use different data models for each cell (the cells are divided by sections).

Any implementation of a table view or a collection view should have a dedicated model.
Simply pass the model down.
NSArray > NSArray > Profile Pictures
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
NSArray *list = self.lists[indexPath.row];
[cell configureWithList:list];
return cell;
}
#interface CustomTableViewCell () <UICollectionViewDataSource>
#property (nonatomic, weak) NSArray *list;
#property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
#end
#implementation CustomTableViewCell
-(void)setList:(NSArray *)list {
_list = list;
[self.collectionView reloadData];
}
-(void)configureWithList:(NSArray *)list {
self.list = list;
}
EDIT: After reading this, I decided to build this control -> cocoacontrols

Sounds a bit tricky, but I think it should be possible without too much work.
You would need to tell the table view how many sections you have, and change numberOfItemsInSection: to return the number of rows in each respective section (looks like only one, based on your screenshot). Finally, we need to augment the AFIndexedCollectionView collection view subclass. Right now it stores an integer index, but we would need to modify it to store an index path instead. Then the collection view's cellForItemAtIndexPath: method would use that index path to determine which section the collection view is in.
It's a bit tricky, might make sense to draw the UI out on paper and reason about it that way. Good luck!

Related

The nuances of UITableView

I have been using objective c for a few months now, using different foundation classes and generally playing around with the language.
In my own experience nothing has been more confusing than UITableView; Below is a bit of code that does not do much.
//the header file
#interface SLDataBankListTableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#end
//implementation
#interface SLDataBankListTableViewController ()
#property (strong, nonatomic) SLDataSourceObject* dataSource;
#end
#implementation SLDataBankListTableViewController
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
_dataSource = [[SLDataSourceObject alloc] init];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _dataSource.dataBankNames.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"reuse"];
cell.textLabel.text = [_dataSource.dataBankNames objectAtIndex:indexPath.row];
return cell;
}
#end
I have successfully used this class for over a dozen times and every time I make some stupid mistake like now. Finally I gathered some courage and decided to ask for help.
What are the subtle things, nuances of this class that I don't seem to grasp?
Edit: this is a part of a small program, other parts of it work fine, but the table displays nothing; Some changes have been recommended that are important but did not solve the problem.
It is a little hard to debug without known what is not working, but I see some things which might help you out.
UITableViewController has its own tableview but you seem to have another tableview wired up in a nib. Either use the UITableViewController tableview, or create your own, don't do both.
in cellForRowAtIndexPath you are creating a new cell every time instead of reusing the cells you have.
The delegate methods for the tableview can be called before viewWillAppear. You should create your datasource object earlier. I suggest viewDidLoad:. (Another reason viewWillAppear is a bad choice is that it can be call multiple times, and you can end up creating and destroying many datasource objects for no reason)
Hope that helps.
The big thing to remember about a table view is that it's a way for user to interact with an array of objects. The array is represented by a datasource, and the datasource methods describe what the view needs to know:
how many objects are in the array (called numberOfRowsInSection:)
how to display each one of the objects (called cellForRowAtIndexPath:)
To answer the latter question, the datasource must answer a view. That view's job -- like any view -- is to represent an object for the user. In row the table view uses a UITableViewCell.
The datasource array can be arbitrarily large, so directly mapping UITableViewCells to its elements can get arbitrarily expensive in memory terms. Rather than create a cell for every object in the array, the table view reuses cells.
When a cell scrolls off the top, the "new" one that appears at the bottom isn't new, it's the old cell handed back to the datasource to be reconfigured for the new row. To accomplish this, your datasource is expected to not allocate a new cell, as #JonRose correctly points out, but to ask the table view for a reused cell using dequeueReusableCellWithIdentifier.

How can I allocate and initialize a custom UITableViewCell NOT in cellForRowAtIndexPath?

I won't go into the WHY on this one, I'll just explain what I need.
I have a property in my implementatioon file like so...
#property (nonatomic, strong) MyCustomCell *customCell;
I need to initialize this on viewDidLoad. I return it as a row in cellForRowAtIndexPath always for the same indexPath, by returning "self.customCell".
However it doesn't work, I don't appear to be allocating and initializing the custom cell correctly in viewDidLoad. How can I do this? If you know the answer, save yourself time and don't read any further.
Ok, I'll put the why here in case people are curious.
I had a table view which was 2 sections. The first section would always show 1 row (Call it A). The second section always shows 3 rows (Call them X, Y, Z).
If you tap on row A (which is S0R0[Section 0 Row]), a new cell would appear in S0R1. A cell with a UIPickerView. The user could pick data from the UIPickerView and this would update the chosen value in cell A.
Cell X, Y & Z all worked the same way. Each could be tapped, and they would open up another cell underneath with their own respective UIPickerView.
The way I WAS doing this, was all these cell's were created in the Storyboard in the TableView, and then dragged out of the View. IBOutlets were created for all. Then in cellForRAIP, I would just returned self.myCustomCellA, or self.myCustomCellY.
Now, this worked great, but I need something more custom than this. I need to use a custom cell, and not just a UITableViewCell. So instead of using IBOutlets for all the cells (8 cells, 4 (A,X,Y,Z) and their 4 hidden/toggle-able UIPickerView Cell's), I created #properties for them in my implementation file. But it's not working like it did before with the standard UITableViewCell.
I'm not sure I'm initializing them correctly? How can I properly initialize them in/off viewDidLoad so I can use them?
.m
#interface MyViewController ()
#property (strong, nonatomic) MyCustomCell *myCustomCellA;
...
viewDidLoad
...
self.myCustomCellA = [[MyCustomCell alloc] init];
...
cellForRowAtIndexPath
...
return self.myCustomCellA;
...
If only I understood your question correctly, you have 3 options:
I would try really hard to implement table view data source with regular dynamic cells lifecycle in code and not statically – this approach usually pays off when you inevitably want to modify your business logic.
If you are certain static table view is enough, you can mix this method with overriding data source / delegate methods in your subclass of table view controller to add minor customisation (e.g. hiding certain cell when needed)
Alternatively, you can create cells using designated initialiser initWithStyle:reuseIdentifier: to instantiate them outside of table view life cycle and implement completely custom logic. There is nothing particular that you should do in viewDidLoad, that you wouldn't do elsewhere.
If you have a particular problem with your code, please post a snippet so community can help you
I suggest you to declare all your cells in storyboard (with date picker at right position) as static table and then override tableView:heightForRowAtIndexPath:
Define BOOL for determine picker visibility and its position in table
#define DATE_PICKER_INDEXPATH [NSIndexPath indexPathForRow:1 inSection:0]
#interface YourViewController ()
#property (assign, nonatomic) BOOL isPickerVisible;
#end
Then setup initial value
- (void)viewDidLoad {
[super viewDidLoad];
self.isPickerVisible = YES;
}
Override tableView delegate method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:DATE_PICKER_INDEXPATH] && !self.isPickerVisible) {
return 0;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
And finally create method for toggling picker
- (void)togglePicker:(id)sender {
self.isPickerVisible = !self.isPickerVisible;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
which you can call in tableView:didSelectRowAtIndexPath:
According to your problem, you can create pairs (NSDictionary) of index path and bool if its visible and show/hide them according to that.
Here's what I was looking for:
MyCustomCell *cell = (MyCustomCell *)[[[UINib nibWithNibName:#"MyNibName" bundle:nil] instantiateWithOwner:self options:nil] firstObject];

How should I use Viewcontrollers

I'm making an app with 2 table views. the first has a bunch of cells which lead to the next table view (which can have different data depending on which cell is selected).
My question is, is it better to have a bunch of view controllers for the second menu (1 for each cell selection, or to have one view controller and load different data on it.
There is no right or false. However, I would recommend you to use different viewcontrollers. Especially if you are using a storyboard, this is very straightforward. Just connect your different cell types with the appropriate viewcontroller and pass your data in the -performSegueWithIdentifier: method.
Maybe if you would add some details about the kind of data etc. I could give you a more adequate answer.
Edit:
In this case it would actually make more sense to work with a single second tableviewcontroller, as the input format is always the same and the output is based on the input data. You could do something like this:
FirstTableViewController.h
#interface FirstTableViewController.h : UITableViewController
// array containing NSArrays which themselves contain NSStrings
#property (nonatomic, strong) NSArray *textArrays;
#end
FirstTableViewController.m
#implementation FirstTableViewController
#synthesize textArrays;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.textArrays.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Identifier = #"Your Identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier forIndexPath:indexPath];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier];
}
NSArray *textArray = self.textArrays[indexPath.row];
if (textArray != nil && [textArray isKindOfClass:[NSArray class]]) {
// configure cell
}
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UITableViewCell *cell = (UITableViewCell *)sender;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
SecondTableViewController *controller = [segue destinationViewController].
controller.textArray = self.textArrays[indexPath.row];
}
#end
SecondTableViewController.h
#interface SecondTableViewController.h : UITableViewController
// array containing NSStrings
#property (nonatomic, strong) NSArray *textArray;
#end
SecondTableViewController.m
#implementation SecondTableViewController
#synthesize textArray;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.textArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Identifier = #"Your Identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier forIndexPath:indexPath];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier];
}
NSString *text = self.textArray[indexPath.row];
if (text != nil && [text isKindOfClass:[NSString class]]) {
// configure cell, e.g. cell.textLabel.text = text;
}
return cell;
}
#end
Remember that there are always several ways to achieve something. This is just my way and it does not have to be yours. However, I hope this helps.
Make sure you understand the difference between a class and an instance of that class, i.e. an object. Typically, when a user taps on a cell in your first table, you'll create a new view controller object, load it up with whatever data it needs to display the content corresponding to the tapped cell, and push it onto your navigation stack. When the user is done looking at that content, they hit the "back" button to switch back to the previous view controller. When that happens, the "detail" view controller is normally deallocated because no other objects are referencing it. When the user taps another cell, the process repeats -- a new view controller is created, configured, pushed, and eventually dismissed.
So, that means you'll have two view controller classes, and at any given time one or two view controller objects (not counting a navigation controller), but over time you end up creating many instances of the detail view controller class. You could create just a single instance of the detail view controller and reuse it as necessary, but there's no real advantage to doing so and it ends up being another thing that the main view controller needs to keep track of.
Of course, if different cells in your main table could lead to significantly different kinds of data that need to be displayed in different ways, then you might end up with more than just the two view controller classes. In that case, when the user taps a cell you'd have to figure out which kind of detail view controller you want to use and then instantiate that one. For example, tapping one cell might lead to information about a person while another might lead to a picture and a third might lead to a map.
I would use 2 TableViewController and Custom Cells to do this. Basically once the user chooses a cell on the first TableView, you populate the second Table View with the appropriate data and dequeue the appropriate custom cell to display the data.

How to address a specific UITableViewCell?

I am using a static cell layout in a UITableView. During the workflow I need to address the attributes of a specific cell. All cells have an identifier but i did not found a way to actually address the cell using the identifier.
Thanks,
em
The reason why cellForRowAtIndexPath: is not reliable is probably this (from the documentation):
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
If you ask for a cell that is not visible on screen, it may have been purged from the table view.
Since you mention that you use a static cell layout in your tableview (I assume you don't rely on cell reuse), you could consider keeping the cells as private properties:
In the private interface:
#interface MyViewController ()
#property (nonatomic, readonly) MyTableViewCellClass *myStaticCell;
#end
In the implementation:
#implementation MyViewController {
MyTableViewCellClass* _myStaticCell;
}
- (MyTableViewCellClass*) myStaticCell {
if (!_myStaticCell) {
// Initialize _myStaticCell
}
return _myStaticCell
}
You can then call this lazy loaded property when tableView:cellForItemAtIndexPath:is called and whenever you need to modify it.
Note that this approach is only recommended if you have a tableview with static content and don't rely on cell reuse.
Here indexPathSelected is the selected specific cell
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPathSelected inSection:0]];
indexPathSelected = indexPath.row;
}

UITableView not displaying content in UIViewController

I have added a UITableView inside a UIViewController in IB, I have set the TableView content to "Static Cells" since that's what I want, and everything seems fine when I haven't added any content to the UITableView. But if I for example change the first UITableViewCell's background color to black it doesn't display black when running the app. I have added UITableViewControllerDelegate and UITableViewDataSource and set the tableView.delage = self;. But still no changes I make to the tableView displays when I run the app.
What can the problem be?
NOTE: I have also tried to add tableView.dataSource = self;, but that just make the app crash.
Yes, you can have static table view content in UIViewController.
All you need to do is:
-Create the table's static cells in interface builder and design them the way you like.
-Make the UIViewController implement table view's data source and delegate:
#interface MyViewController : UIViewController<UITableViewDataSource, UITableViewDelegate>
-Connect the table view's delegate and dataSource to the view controller in interface builder
-Implement -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section to return the number of your cells. (e.g. return 10, yes simple as that)
-Connect your cells to your code as IBOutlets in Interface Builder. IMPORTANT: Make sure they are strong, weak won't work. e.g. #property (strong, nonatomic) IBOutlet UITableViewCell *myFirstCell;
-Implement -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath to return the correct cell at index path. e.g:
int num = indexPath.row;
UITableViewCell *cell;
switch (num) {
case 0:
cell = self.myFirstCell;
break;
case 1:
cell = self.mySecondCell;
break;
}
return cell;
If you apply all these steps, you should have working static cells that works for tables with not many cells. Perfect for tables that you have a few (probably no more than 10-20 would be enough) content. I've ran the same issue a few days ago and I confirm that it works. More info check here: Best approach to add Static-TableView-Cells to a UIViewcontroller?
You will want to use a UITableViewController, not a UIViewController with a UITableView added to it, because you're only supposed to use static cells with a UITableViewController. There are probably ways to hack around it so you can get the static cells to work, but it's much simpler to just use a UITableViewController, and you'll have fewer issues to deal with, especially if you ever change the content of the table.
Seems you have problem with the background issue for UITableViewCell. So don't use background for checking if content is drawing or not.
You can use debugger for this for example or NSLog.
NOTE: the cell has content view that can be modified. I don't remember but seems the cell has not got background property that can be adjusted with a color.
If you tried this line below e.g. - it will no work and color will be white as default color.
[cell setBackgroundColor:[UIColor blackColor]];
Try to add something to the cell for example picture and then you can see the result as I think.
Use this code:
[cell.contentView setBackgroundColor:[UIColor blackColor]]; in this delegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
it will help you as I think.
Have you implementede the protocol? ...
another thing is that when implementing the protocol i had an issue when no cell was displayed..
try with this implementation for the given method.
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier=#"Cell";
CobranzaCell *cell = [[CobranzaCell alloc]init];
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
if (cell == nil) {
cell = [[CobranzaCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Configure the cell...
return cell;
}
You cannot use the static cells in simple UIViewController subclass. Instead, you should use the instance of UITableViewController. The hack is in your storyboard drag the instance of UIViewController to your storyboard canvas. Then, drag the Container View from objects library and drop it to your UIViewController's view. By default it will create the embed segue with related UIViewController instance. What you want to do - delete this view controller, drag and drop instance of UITableViewController from objects library, then right click and drag from your Container View to just dropped UITableViewController. Chose embed. Now your table view controller gets the size of container view and you can use static cells in it! Hope it will help!

Resources