Show a table view footer on only one section - ios

I am developing an app that lists products. The products are section headers. If you tap on a product it expands to show that sections' table, with modifications for that product in cells of that table.
What I want to do is have a footer for each section that will have an 'Add to Cart' button. That footer should only be visible when the product is expanded. If I use this:
- (CGFloat)tableView:(UITableView*)tableView heightForFooterInSection:(NSInteger)section {
if (self.isOpen) {
return 35.0;
}
else {
return 0.0;
}
}
The footer doesn't show on any product section until you select a product, then it is visible on all products. Is there a way to have it a certain height on only one table section? I have tried detecting the last cell in the table with the modifications, but it turned out to be really buggy. The Add to Cart button would show on random rows.
Edit: Here is the cellForRow method to detect the last row:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
... Custom Cell dequeue
// Currently only 3 rows for modifications, so 0-2, last one is 3 for Add to Cart
if(indexPath.row == 3){
cell.textLabel.text = #"Add to Cart";
cell.productName.text = #"";
cell.productDescription.text = #"";
}
else {
...Regular cell items
}
return cell;
}

Are you also returning nil for your viewForFooter method in the other sections?
We will likely need to know how you check if a section has an addToCart button... but here's my guess:
- (CGFloat)tableView:(UITableView*)tableView heightForFooterInSection:(NSInteger)section {
if (self.isOpen && [self hasCartForSection:section]) {
return 35.0;
}
else {
// One table view style will not allow 0 value for some reason
return 0.00001;
}
}
- (UIView*)tableView:(UITableView*)tableView viewForFooterInSection:(NSInteger)section {
if (self.isOpen && [self hasCartForSection:section]) {
UIView *footerView = [UIView.alloc init];
// Build your footer
return footerView;
}
return nil;
}
- (BOOL)hasCartForSection:(int)section {
BOOL someCondition = NO;
// Do your check
if (someCondition) {
return YES;
}
return NO;
}

Related

UITableView showing inconsistent number of rows compared to data source

I'm trying to make a UITableView present a determined number of rows for a section, but even when I verify that its data source is returning x number of rows for numberOfRowsInSection, the table view shows x-1.
The exception to this unexpected behavior is if the numberOfRowsInSection is less than 3.
I've even put a breakpoint in cellForRowAtIndexPath and I confirmed it's being called for the row that is not appearing.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == SectionNumber_One) {
return 6;
} else {
return self.numberOfProjectRows; // This returns x, but x-1 rows are being shown
}
}
For example, if self.numberOfProjectRows is 5, only 4 rows are shown for the second section.
If I increase it manually to 6, it shows 5 rows but the data that should be in the 5th position, isn't there.
It doesn't seem to be related to screen size as I tested it on an iPad with same results.
Why is this happening? Is there some other possible modifier of the number of rows in a section?
I'm attaching an screenshot if it's of any help.
EDIT - Here are my delegate methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Cell with reuse identifier setup
if (section == SectionNumber_One) {
// cell setup for section one that's showing up ok
} else if (section == SectionNumber_Two) {
UITextField *projectField = cell.projectTextField;
if ([self.userProjectKeys count] > row) {
projectField.text = self.availableProjects[self.userProjectKeys[row]];
}
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Hide the password row for existing users
if (indexPath.row == FieldTag_Password && ![self.user.key vol_isStringEmpty]) {
return 0.0f;
} else {
return UITableViewAutomaticDimension;
}
}
The problem is probably not in your Datasource methods, but your Delegate methods, tableView(_:heightForRowAt:).
If you do not return a correct height for the cell, it won't show up.
It doesn't matter if you write 1000 cells in your datasource. If you don't return the height, they wont show up.
You are not comforming to the MVC pattern in implementing the table. You must return the count of the tableView's datasource, not variables of it.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == SectionNumber_One) {
return 6;
} else {
return [_displayingArray count]; // <-- here
}
}
If you want different items for each sections, then declare different datasource (ie array) for each of them.
EDIT: Even returning constant 6 is dangerous in the other section - you ought to add items to another fixed array and return that array's count in this delegate.

best approach for tableview with rows expand and collapse option

I have view which contains tableview and searchbar. When i begin searching it should search the tableview. Below is the sample data of tableview in attached image:
STUDENT 1, STUDENT 2 are different sections in the table
Suppose i search for "S" then result should be as shown below where matched rows should be visible and unmatched rows - should be hidden and a button "Show Remaining" should be visible. When i click on "Show Remaining" button it should show remaining rows of the same section :
how do i achieve this and if possible please provide the example for above scenario.
I had created the code for your requirement please find the below url for download the code and review it.
Link : https://www.dropbox.com/s/22ijwgxybn98wyj/ExpandCollapse.zip?dl=0
Environment : Xcode 8 and Objective-C
Highlight of the code which I had done it.
I had created NSObject of student based on your structure, create two(StudentListCell and ShowMoreCell) UITableViewCell for display the record of student and "Show Remaining button" record.
Also I have taken two(arrStudentInfo, arrSearchInfo) NSMutablebleArray for keeping the search records and original records.
After that I initilize the NSObject of student in ViewController.m. Please find the below code.
- (void)setupData {
self.arrStudentInfo = [[NSMutableArray alloc] init];
self.arrSearchInfo = [[NSMutableArray alloc] init];
StudentInfo *student_1 = [[StudentInfo alloc] init];
student_1.name = #"Sia S";
student_1.style = #"S";
student_1.trend = #"S";
student_1.age = #"60";
student_1.locale = #"Node";
student_1.street = #"BR";
student_1.city = #"JP";
student_1.country = #"US";
student_1.isOpen = YES;
student_1.isShowMore = NO;
[self.arrStudentInfo addObject:student_1];
StudentInfo *student_2 = [[StudentInfo alloc] init];
student_2.name = #"Nitin";
student_2.style = #"N";
student_2.trend = #"N";
student_2.age = #"40";
student_2.locale = #"Node";
student_2.street = #"BR";
student_2.city = #"SP";
student_2.country = #"US";
student_2.isOpen = YES;
student_2.isShowMore = NO;
[self.arrStudentInfo addObject:student_2];
}
I had created the method which return the currently active which means search record or original record of array.
- (NSMutableArray *)getCurrentArray {
if(self.isSearch)
return self.arrSearchInfo;
else
return self.arrStudentInfo;
}
Load the data based on scenario,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
StudentInfo *studentInfo = [self getCurrentArray][indexPath.section];
if(indexPath.row == SHOW_MORE_INDEX && studentInfo.isShowMore == NO) {
static NSString *strCellIdentifier = #"ShowMoreCell";
ShowMoreCell *cell = [tableView dequeueReusableCellWithIdentifier:strCellIdentifier];
cell.btnShowMore.tag = indexPath.section;
[cell.btnShowMore addTarget:self action:#selector(clickONShowMore:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
else {
static NSString *strCellIdentifier = #"StudentListCell";
StudentListCell *cell = [tableView dequeueReusableCellWithIdentifier:strCellIdentifier];
[cell setupCell:studentInfo indexPath:indexPath];
return cell;
}
}
For searching the student name,
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
self.isSearch = YES;
NSPredicate *predicateStudent = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"self.name CONTAINS[cd] '%#'",searchBar.text]];
NSArray *arrGetSearch = [self.arrStudentInfo filteredArrayUsingPredicate:predicateStudent];
if(self.arrSearchInfo.count)
[self.arrSearchInfo removeAllObjects];
[self.arrSearchInfo addObjectsFromArray:arrGetSearch];
[self.tblView reloadData];
}
And again load the original record if user click on "X" button on UISearchbar.
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if([searchText isEqualToString:#""] || searchText==nil) {
[searchBar resignFirstResponder];
self.isSearch = NO;
[self.tblView reloadData];
}
}
Hope this will work for you.
There are many examples available for expand and collapse table.
You can refer below example
http://www.appcoda.com/expandable-table-view/
By setting up dynamically in plist file. you can maintain expandable and collapsable tableview.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCellWithIdentifier(currentCellDescriptor["cellIdentifier"] as! String, forIndexPath: indexPath) as! CustomCell
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
cell.textField.placeholder = currentCellDescriptor["primaryTitle"] as? String
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellSwitch" {
cell.lblSwitchLabel.text = currentCellDescriptor["primaryTitle"] as? String
let value = currentCellDescriptor["value"] as? String
cell.swMaritalStatus.on = (value == "true") ? true : false
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellValuePicker" {
cell.textLabel?.text = currentCellDescriptor["primaryTitle"] as? String
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellSlider" {
let value = currentCellDescriptor["value"] as! String
cell.slExperienceLevel.value = (value as NSString).floatValue
}
return cell
}
I can't think of any general "best approach" for collapsing/expanding behaviour in UITableView. I suspect that is the reason why there's no provided by Apple. Instead, they gave us enough instruments to implement it the way we wish.
In your particular case I have next vision.
I don't know, and don't need to know, how exactly do you work with model side of your task and the following code serves only this answer's purpose, which is - to provide you with collapsing behaviour you seek.
I would suggest to implement your desired behaviour in such a manner. In ViewController, in which you control both search bar and table view add fields
#property NSArray* filteredStudents;
#property NSMutableIndexSet* expandedStudents;
Those properties would be nil (or empty instances) while no search is taking place. Once search starts (first symbols entered or "Search" button tapped - whatever you like most) - instantiate them.
filteredStudents - array that holds reference to those students, that satisfy your search criteria. It is updated every time search executed. Each time you update it - do [tableView reloadData].
expandedStudents - index set that tells you in which sections have user pressed "Show all". Don't forget - it holds only section numbers. So, when filteredStudents array changes, you have to update it manually. For example, when you entered "S" - you have two students shown. You press Show All on the second student. expandStudents now has number 1 in it. Than you enter "Se", and only second student remains. You have to, in that case, replace 1 in expandedStudents with 0, 'cause second student's section becomes first.
Another property
#property BOOL searchMode;
This property should be YES whenever filtered results display takes place. NO - otherwise.
Next, modify UITableViewDataSource and Delegate methods like this
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
if(searchMode) {
return self.filteredStudents.count;
}
else {
//Your current behaviour here
}
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(searchMode) {
if([self.expandedStudents containsIndex:section]) { // Button "Show all" was already tapped for this student
return /* Number of all fields you need to show for student. Just like you do now. It is for expanded section */
}
else {
return /* Number of fields that satisfy search criteria for student self.filteredStudents[section] */
}
}
else {
//Your current behaviour here
}
}
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(searchMode) {
if(![self.expandedStudents containsIndex:section] && /* indexPath corresponds to place, where "Show All" should be */) {
//Create "Show All" cell
}
else {
//Create cell that contains info from filtered student fields
}
}
else {
//Your current behaviour here
}
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(searchMode && ![self.expandedStudents containsIndex:section] && /* indexPath corresponds to place, where "Show All" should be */) {
//"Show All" button pressed
[self.expandedStudents addIndex:indexPath.section];
[tableView beginUpdates];
/* For each row, that wasn't presented in collapsed student perform insertRowsAtIndexPaths:withRowAnimation: method */
[tableView endUpdates];
}
else {
//Your current behaviour
}
}

trying to create different cells in same table view - but can't figure out how to code in the tableviewcontroller

I have created a tableview with multiple prototype (4) cells in order to display different content in each cell but being a newb - I am not clear on how to then code this in the tableviewcontroller to pull data into the multiple cells (just using multiple simple arrays with one data point for each label for now to test it) but I can only get the data to populate in the first cell - and not clear how to code to get the data into the remaining cells. I created 4 separate customtableviewcell files as well. Can someone point me in the right direction on how to code so I can get data into the four separate prototype cells? I need it to be able scroll as it won;t all fit on the the screen which is why I chose table view to do this - but there will only ever be these four sections in the view (with different data depending on what you pushed to get here) should I not be using tableview? if I should be using something different like a view controller with 4 views instead? will it scroll so the user can see all sections? Thanks in advance for any help and suggestions.
You should assign unique Identifier to the each cell in the Storyboard. Then, you can populate appropriate cells like this:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section)
{
case 0:
{
MyCustomCell1 *cell = [tableView dequeueReusableCellWithIdentifier:#"cell_1"];
// Configure cell
return cell;
}
case 1:
...
default: return nil;
}
}
Consider creating custom subclasses of UITableViewCell to provide handy IBOutlets.
(1) You need 4 cells. So, prepare 4 custom cells by creating subclass of UITableViewCell. You will know how to create custom cell by search on google.
(2) Set number of sections to 1.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
(3) Set desired height for each cell
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch(indexPath.row)
{
case 0:
{
return 40;
}
case 1:
{
return 50;
}
case 2:
{
return 30;
}
case 3:
{
return 45;
}
default:
{
return 0; // Default case
}
}
}
(4) Set contents of each cell. The data will come from your data source i.e. Array or Dictionary.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [NSString stringWithFormat:#"CellIdentifier%d%d",indexPath.section,indexPath.row];
if(indexPath.row == 0)
{
CustomCell1 *objCustomCell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(objCustomCell1 == nil)
{
objCustomCell1 = [[CustomCell1 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
objCustomCell1.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Set row specific data here...
NSDictionary *dicObj = [arrYourDataSource objectAtIndex:indexPath.row];
objCustomCell1.myLabel.text = [dicObj objectForKey:#"your key"];
return objCustomCell1;
}
else if(indexPath.row == 1)
{
CustomCell2 *objCustomCell2 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(objCustomCell2 == nil)
{
objCustomCell2 = [[CustomCell2 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
objCustomCell2.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Set row specific data here...
NSDictionary *dicObj = [arrYourDataSource objectAtIndex:indexPath.row];
UIImage *theImage = [UIImage imageNamed:[dicObj objectForKey:#"your key"]];
objCustomCell2.myImageView.image = theImage;
return objCustomCell2;
}
// Do same for remaining 2 rows.
return nil;
}
First do this and then add comment. We will move ahead.

viewForHeaderInSection - What to return for no header?

I have a UIViewController with several UITableViews imbedded in it. For some of the tables, I need to show a custom header view with multiple labels. For other tables, I don't want to show a header at all (that is, I want the first cell in myTable2 to be right at the top of the frame for myTable2). Here's roughly what I have:
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if (tableView == self.myTable1)
// Wants Custom Header
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0,tableView.contentSize.width,20);
// ... Do stuff to customize view ...
return view;
}
elseIf (tableView == self.myTable2)
// Wants No header
{
return nil;
// also tried
// return [[UIView alloc] initWithFrame:CGRectMake(0,0,0,0)];
// but that didn't work either
}
}
This works great for the custom headers, but for the tables where I don't want any header, it's showing a white box. I figured that return nil; would prevent any header from being shown for that table. Is that assumption correct? Is there something else overwritting that? How can I make it so nothing shows?
Whenever you implement the viewForHeaderInSection method you must also implement the heightForHeaderInSection method. Be sure to return 0 for the height for sections that have no header.
If you use this way you don't need to calculate the height.
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 50
If you have empty sections you can set the height to zero or you don't need this method.
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 2 {
return 0
}
return UITableView.automaticDimension
}
check this code ,as per #rmaddy comments
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
if (tableView == self.myTable1)
// Wants Custom Header
{
//custome header height
return 20.0f;//height of custom header
}
elseIf (tableView == self.myTable2)
// Wants No header
{
return 0;
}
}
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if (tableView == self.myTable1)
// Wants Custom Header
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0,tableView.contentSize.width,20);
// ... Do stuff to customize view ...
return view;
}
elseIf (tableView == self.myTable2)
// Wants No header
{
return nil;
}
}

allow editing in uitableview, but not in one row

I have a UITableView with multiple sections. When I set the table into edit mode, then I would prefer that one section (which only includes 1 row) does not display the edit symbol and not call tableView:commitEditingStyle:forRowAtIndexPath:.
Is this possible?
regards
John
here i assume for section 1 and row 0 which can not edit
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
if (indexPath.section == 1) {
if (indexPath.row == 0) {
return NO;
}
}
return YES;
}

Resources