How to close expanded table view cell programmatically? - ios

I have two tabs in a view controller: on click of each tab, different data is displayed in a table view with cells that expand and close on click.
The problem is that when I'm in tab A, and expand a cell there, then go and click on tab B, loading new data, that cell is still expanded. How do I close it programatically?
I'm using the FZAccordionTableView subclass to perform the expansions.
- (UIView *)tableView:(FZAccordionTableView *)tableView viewForHeaderInSection:(NSInteger)section {
HeaderView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kAccordionHeaderViewReuseIdentifier];
if (listOfCategoryWhichHaveItems.count > 0) {
if (arrCategory.count > 0) {
arrCategory1 = _btnProduct.selected == YES ? [arrCategory objectAtIndex:0] : [arrCategory objectAtIndex:1];
NSDictionary *dict = arrCategory1[section];
view.lblHeaderTitel.text = [dict valueForKey:kCategory];
bool isSelected = [tableView isSectionOpen:section];
}
}
return view;
}
#pragma mark - <FZAccordionTableViewDelegate> -
- (void)tableView:(FZAccordionTableView *)tableView willOpenSection:(NSInteger)section withHeader:(UITableViewHeaderFooterView *)header {
loadFirstTime = false;
[tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation: UITableViewRowAnimationAutomatic];
if (listOfCategoryWhichHaveItems.count > 0) {
_sectionNumber = &section;
NSDictionary *dict = listOfCategoryWhichHaveItems[section];
[self setDataAccordingToCategories:[dict valueForKey:kCategory]];
}
}
- (void)tableView:(FZAccordionTableView *)tableView didOpenSection:(NSInteger)section withHeader:(UITableViewHeaderFooterView *)header {
}

Please set keepOneSectionOpen property of your table to No on clicking the tab B.
Yourtable.keepOneSectionOpen = No;

In the FZAccordionTableView GitHub repo i found this method:
- (void)closeSection:(NSInteger)section withHeaderView:(nullable FZAccordionTableViewHeaderView *)sectionHeaderView
That will probably do what you want, it is however not declared in the header file so you can't call it directly.
You can however clone the repo, add the line to the header file, add + commit + push to your fork. Then use that repo in your Podfile (or however you're using the library right now) so you're using your fork of the framework, then you can call the method.

On click of each tab, i used this
[_tblProductServies closeAllSectionsExcept:-1];
its working fine now, thanks for all.

Related

How can I have a Table Cell to perform two different segues but with conditions?

I am new to iOS Development so please forgive me for asking silly questions or performing silly actions.
So now, here's the problem. I am developing an app, I have a tab bar controller as the initial, called [Initial Tab View]. One of the tab is a table view that displaying all the items, lets call this [Item View]. Once the user tap on the cell, it will push to another view and show the details, the [Detail View]. Another tab is also a table view but with static cells, [Static View], which I use it to select an item and return.
For a clearer picture: If I access the [Item View] from the initial tab view, just simply follow [Initial Tab View]->[Item View]->[Detail View]. However, if I access the [Static View] first and then go to the [Item View], the procedure will be like [Initial Tab View]->[Static View]->[Item View]->[Static View].
I have no idea how to implement the second one (with the [Static View] one) in [Item View], can anyone help? Or there is another better approach? Many thanks.
// Code from [Item View]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"CustomShopCell";
ShopCustomTableCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[ShopCustomTableCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:identifier];
}
if (searchResult != nil) {
//cell.textLabel.text = [searchResult objectAtIndex:indexPath.row];
Shop *shop = searchResult[indexPath.row];
if ([shop.shopNameChi length] == 0) {
cell.lblName.text = shop.shopName;
} else {
cell.lblName.text = [NSString stringWithFormat:#"%# | %#", shop.shopName, shop.shopNameChi];
}
cell.lblLocation.text = shop.unit;
cell.imgImage.contentMode = UIViewContentModeScaleAspectFit;
cell.imgImage.image = [UIImage imageWithData:shop.imageData];
} else {
//cell.textLabel.text = [list objectAtIndex:indexPath.row];
Shop *shop = shopList[indexPath.row];
if ([shop.shopNameChi length] == 0) {
cell.lblName.text = shop.shopName;
} else {
cell.lblName.text = [NSString stringWithFormat:#"%# | %#", shop.shopName, shop.shopNameChi];
}
cell.lblLocation.text = shop.unit;
cell.imgImage.contentMode = UIViewContentModeScaleAspectFit;
cell.imgImage.image = [UIImage imageWithData:shop.imageData];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(![self isKindOfClass:[NavigationMainTableViewController class]]){
[self performSegueWithIdentifier:#"showShopDetail" sender:self];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
What you can do (if i'm sure i understood what you're doing) is create two segue links with differnt identifiers (with a clear name, I like this formatting "fromHereToThere"). Then in your didselect you can just call your segue with an if statement, like so :
if (your condition){
[self performSegueWithIdentifier:#"fromHomeToStatic" sender:self];
}else{
[self performSegueWithIdentifier:#"fromHomeToDetail" sender:self];
}
Then in your prepareForSegue method (the one in comment by default in every controller class) you can choose what data to pass along, with
if ([segue.identifier isEqualToString:#"fromHomeToDetail"])
{
//Manage your usual stuff here for the detail identifier
//Ask me again if you need extra help with that
}
if ([segue.identifier isEqualToString:#"fromHomeToStatic"]){
//Manage here for the static identifier
}
But i'm not really exactly sure what you meant with your question so i might be completly off topic :D
EDIT : Okay apparently you wanna know where you're coming FROM when you're loading your third controller. So here is a way to do that.
In your destination controller, add this in .h
#property (nonatomic) BOOL fromStatic;
(or whatever you want really)
Then in your origin controller, in the prepareForSegue, if you're coming from Static, set the BOOL of the next controller to YES, if not, set it to NO, like this
DetailViewController *dvc = segue.destionationViewController;
dvc.fromStatic = YES; //if you're writing this from the static controller
In the viewDidLoad of the destination controller just check if it's YES or NO and react accordingly.
Am I getting this right or am I still confused with your problem? I'm still not completly sure what you're asking haha :D
Try to use this one. You can set that method for each cell you taping.
[self performSegueWithIdentifier:#"yourIdentifierHere" sender:self];
If you are using Static Cell Prototype . Just drag from cell to the view which you want to push and select push in segue . If you are using dynamic UITableViewCell. You can performSegue in didSelectRowAtIndex Method .

How to add items to a side menu bar?

Im making my first app. My app will have a side pop out menu. I have gotten the sliding menu part implemented with a UITableView. Now i want to populate the side menu with text and a image next to it and when one is tapped i want to push another view controller. What would be the best way to do this? Thanks
As user132490 said, you probably want to add rows to your tableView. Here's a tutorial: http://www.appcoda.com/uitableview-tutorial-storyboard-xcode5/. It's for Xcode 5 but still will work. You may find it useful from "Adding Table Data" and below. In fact, before the Summary the tutorial teaches you to add a thumbnail next to the text of each of your cells.
Good luck!
When you want to have an image and text for your table, you have to create a UITableViewCell subclass and declare an image view and label. Image view and label must be created in IB, create a prototype cell and add there the image view and label.
Once you've done that you must have an array of what will be the content of your table. It's like this:
In #interface:
#property NSMutableArray *menuArray;
In your viewDidLoad:
NSDictionary *option1 = [NSDictionary dictionaryWithObjectsAndKeys:#"image1.png",#"image",#"Option1",#"desc", nil];
NSDictionary *option2 = [NSDictionary dictionaryWithObjectsAndKeys:#"image2.png",#"image",#"Option2",#"desc", nil];
NSDictionary *option3 = [NSDictionary dictionaryWithObjectsAndKeys:#"image3.png",#"image",#"Option3",#"desc", nil];
NSDictionary *option4 = [NSDictionary dictionaryWithObjectsAndKeys:#"image4.png",#"image",#"Option4",#"desc", nil];
NSDictionary *option5 = [NSDictionary dictionaryWithObjectsAndKeys:#"image5.png",#"image",#"Option5",#"desc", nil];
//assign NSDictionaries to array
self.menuArray = [NSMutableArray arrayWithObjects:option1,option2,option3,option4,option5, nil];
And then you must have table view delegate methods like this:
#pragma mark Table View delegate methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [menuArray count];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//return row height
return 32;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier =#"Cell";
//assign TableCell.h to access custom properties
TableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//get dictionary from menuArray
NSDictionary * dict = [self.menuArray objectAtIndex:indexPath.row];
UIImage *imageIcon = [UIImage imageNamed:[dict objectForKey:#"image"]];
//set image
[cell.img setImage:imageIcon];
//set the label text
cell.label.text = [dict objectForKey:#"desc"];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//research more about pushing another view controller.
//if chosen was option1
if (indexPath.row == 0)
{
NSLog(#"option1");
}
//if chosen was option2
if (indexPath.row == 1)
{
NSLog(#"option2");
}
//if chosen was option3
if (indexPath.row == 2)
{
NSLog(#"option3");
}
// if chosen was option4
if(indexPath.row == 3)
{
NSLog(#"option4");
}
// if chosen is option5
if(indexPath.row == 4)
{
NSLog(#"option5");
}
}
In pushing to other view controller, try to research more because it's a lot more explanation. Try this link: https://github.com/ECSlidingViewController/ECSlidingViewController
But with this codes, you must be able to populate your table and when you select a cell there will be a log. Hope this helps :)

How to pass the in-place edited content back to the previous tableview controller (update the model)

I have two table views, one called mainTableViewController (mtvc), the other called detailTableViewController (dtvc). It's very typical click the accessory button on the main tableview cell bring you to the detail tableview kinda thing.
In the prepareForSegue method, the data passed from the main tableview to detail tableview is a NSMutableArray called item.
And this is how I got it displayed: cell.detailTextLabel.text = self.item[indexPath.row];
The cool thing is I managed to do in-place editing on the detail table view cell (overwrote the NSTableViewCell, added a UITextField as subview to each cell).
everything works, the last thing I spent whole day cannot figure out is how do I update the NSMutableArray item after in-place editing taken place, the ultimate goal is in-place editing, and the main tableview data shall reflect the change.
I tried to use delegation and protocol but it does not work (the in-place edited content didn't got passed back, part of the reason is I don't know how to capture the edited content, it's not like it's a text field with a name, I can't just do updatedContent = self.myTextField.text to grab the change)
I'm running out of ideas, any help would be highly appreciated, thanks.
Here's the prepareForSegue in the main tableview controller
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toInventoryDetail"]) {
NSMutableArray *selectedItem = nil;
if (self.searchDisplayController.active) {
selectedItem = _searchResults[[sender row]];
} else {
selectedItem = _appDelegate.items[[sender row]];
}
UPFInventoryDetailTableViewController *idtvc = segue.destinationViewController;
idtvc.item = selectedItem;
}
}
and here's the cellForRowAtIndex at the detail tableview controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UPFEditableUITableViewCell *cell = [[UPFEditableUITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
cell.textLabel.text = _appDelegate.title[indexPath.row];
cell.detailTextLabel.text = self.item[indexPath.row];
[cell showEditingField:YES];
return cell;
}
I wrote the delegation but delete them after cause they didn't work.
I had an idea, still using delegation and protocol obviously: when the 'done' button in the detail tableview hit, I go grab all the row contents and build a new array, using delegation to pass this new array back to the main tableview controller, add this new array into the model meanwhile delete the old one. The tricky thing is still HOW CAN I GRAB ALL THE CONTENTS in the detail tableview?
update:
Haha! I think solved half of the puzzle !
here's the solution for the detail tableview controller
- (IBAction)doneUpdate:(UIBarButtonItem *)sender {
[self.delegate addItem:[self newItem]];
}
- (NSMutableArray *)saveItem
{
NSMutableArray *newItem = [[NSMutableArray alloc] init];
NSArray *indexPathes = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in indexPathes) {
UPFEditableUITableViewCell *cell = (UPFEditableUITableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
[newItem addObject:cell.editField.text];
}
return newItem;
}
and here's the main tableview controller
- (void)addItem:(NSArray *)item
{
//take the updated item then insert the items array as new item
[_appDelegate.items addObject:item];
//remove the selected item (the one being updated) from the items array
[_appDelegate.items removeObject:_appDelegate.selectedItem];
[self.tableView reloadData];
[self.navigationController popViewControllerAnimated:YES];
}
When you creating a cell - give tags to your UITextFields
You can collect data entered by its delegate methods - you can either make NSDictionary/ key value pairs or you can add it to NSArray.
- (void)textFieldDidEndEditing:(UITextField *)textField {
if(textField.tag == 11) {
// you can add it to your desired array/dictionary
}
}
OR
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if(textField.tag == 11) {
// you can add it to your desired array/dictionary
}
}
You can use Delegation/Protocol or store this values in NSUserDefault and get it back on mainViewController.
Do you have a separate data model class(classes) for your selectedItem? That would be the appropriate way to persist data between the two TableViewControllers. It can be Core Data or simply a NSMutableArray that lives in memory. The DetailViewController updates the item and saves the changes, then the mainTableViewController reloads the TableView (or even just the data backing the previously edited cell.
Perhaps even consider the Model-View-Controller-Store pattern promoted by BigNerdRanch.

Correct subclassing and reusing of UITableViewHeaderFooterView

I have a UITableView where I have section headers that can be tapped to expand or collapse the section. In my particular example each section only has one row, which is either visible (section expanded) or hidden (section collapsed).
As section header i'm using custom UITableViewHeaderFooterView - HeaderAccountView. I created *.xib file at Interface Builder , and set it custom class to my HeaderAccountView (still at IB field).
There are no any changes to init method or smth like this in my HeaderAccountView.h and HeaderAccountView.m files - only some functions to highlight self (selected section) etc.
in my main ViewController .m file
- (void)viewDidLoad
{
[super viewDidLoad];
.........
.........
UITableView *tableView = (id)[self.view viewWithTag:1];
UINib *nib= [UINib nibWithNibName:#"HeaderAccountView" bundle:nil];
[tableView registerNib:nib forHeaderFooterViewReuseIdentifier:#"HeaderCell"];
}
and then
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
HeaderAccountView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"HeaderCell"];
if (headerView==nil)
{ headerView = [[HeaderAccountView alloc]
initWithReuseIdentifier:#"HeaderCell"];
}
return headerView;
}
when i'm running project everything going OK - sections load with needed data in it, when section receive tap - it highlights (like standard cell).
But when i'm scrolling away tableview to bottom for example from selected highlighted section, and this highlighted section already is not visible at view - that section that just appeared from bottom - already highlighted!
I understand that its because it creates new instance of my HeaderAccountView with property BOOL selected set to YES.
But I'm new to objective-c (and coding) and don't understand how to correct resolve this.
I tried to use prepareForReuse method of my custom UITableViewHeaderFooterView like this
HeaderAccountView.m:
-(void) prepareForReuse
{
self.selectedBackground.alpha = 0;
}
It works better - but now i have another issue - when i returning to my first (truly) selected and highlighted section - it obviously don't highlight.
Thanks for any help and sorry if it elementary question.
You have to manually keep a list of your selected headers indexes.
Next, implement the method tableView:willDisplayHeaderView: in your view controller to refresh your header when it will be displayed.
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
view.selectedBackground.alpha = ([_highlightedHeadersList containsObject:#(section)] ? 0.0f : 1.0f);
}
And you have to add / remove indexes in _highlightedHeadersList.
I've done this using the following. In the table view controller I created a property (NSInteger), and called it sectionForSelectedHeader. Set it to -1 initially so no section will be initially selected.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
RDHeader *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"Header"];
header.tag = section;
if (header.gestureRecognizers.count == 0) {
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(headerSelected:)];
[header addGestureRecognizer:tapper];
}
header.selected = (self.sectionForSelectedHeader == section)? 1 : 0;
return header;
}
-(void)headerSelected:(UITapGestureRecognizer *) tapper {
if ([(RDHeader *)tapper.view selected] != 1) {
self.sectionForSelectedHeader = tapper.view.tag;
}else{
self.sectionForSelectedHeader = -1;
}
[self.tableView reloadData];
}
Then in my custom header subclass, I have a method setSelected: (selected is an NSInteger property in the RDHeader class) like this:
-(void)setSelected:(NSInteger)selected {
_selected = selected;
if (selected) {
self.contentView.backgroundColor = [UIColor orangeColor];
}else{
self.contentView.backgroundColor = [UIColor yellowColor];
}
}

Setting scroll position in UITableView

I have an application that works some what similar to how iPhone's Contact application works. When we add a new Contact user is directed to a view only screen with Contact information. If we select "All Contacts" from the navigation bar, user is navigated to list of all contacts where the recently added contact is in view.
We can move the view to a particular row using:
[itemsTableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionBottom];
... but it's not working. I'm calling this right after calling:
[tableView reloadData];
I think I'm not suppose to call selectRowAtIndexPath:animated:scrollPosition method here. But if not here, then where?
Is there any delegate method that gets called after the following method?
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
I think I've got a similar app: A list of item., tap '+' -> new screen, come back and see the updated list, scrolled to show the added item at the bottom.
In summary, I put reloadData in viewWillAppear:animated: and scrollToRowAtIndexPath:... in viewDidAppear:animated:.
// Note: Member variables dataHasChanged and scrollToLast have been
// set to YES somewhere else, e.g. when tapping 'Save' in the new-item view.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (dataHasChanged) {
self.dataHasChanged = NO;
[[self tableView] reloadData];
} else {
self.scrollToLast = NO; // No reload -> no need to scroll!
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (scrollToLast) {
NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([dataController count] - 1) inSection:0];
[[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
I hope this helps. If you add something in the middle of the list, you could easily scroll to that position instead.
You can try this , my application in some kind of similar to you when i click on a button scroll of uitableview is up.
[tableviewname setContentOffset:CGPointMake(0, ([arrayofcontact count]-10)*cellheight) animated:YES];
10 is for how many cell you want to scroll up
Hope this will help you:-)
Scrolling animation is inevitable when offset is set from viewDidAppear(_:). A better place for setting initial scroll offset is viewWillAppear(_:). You'll need to force the layout of a table view, because it's content size is not defined at that point.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.setNeedsLayout()
tableView.layoutIfNeeded()
if let selectedIndexPath = selectedIndexPath, tableView.numberOfRows(inSection: selectedIndexPath.section) > 0 {
tableView.scrollToRow(at: selectedIndexPath, at: .top, animated: false)
}
}
Do you have enough dummy contacts added to test the scrolling? It seems that only when your tableView is of a certain size the iPhone finds the inputus to scroll.
This is the code that works for me in my project. I use a previous button and therefore I scroll the tableview a little bit further down that it usually would go with UITABLEVIEWSCROLLPOSITION. (my previous button won't work if it can't "see" the previous cell.
Ignore some of the custom method calls.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
//Show the navButtons
[self navButtonAnimation];
DetailsStandardCell *cell = (DetailsStandardCell *)textField.superview.superview.superview;
self.parentController.lastCell = cell;
//Code to scroll the tableview to the previous indexpath so the previous button will work. NOTE previous button only works if its target table cell is visible.
NSUInteger row = cell.cellPath.row;
NSUInteger section = cell.cellPath.section;
NSIndexPath *previousIndexPath = nil;
if (cell.cellPath.row == 0 && cell.cellPath.section != 0) //take selection back to last row of previous section
{
NSUInteger previousIndex[] = {section -1, ([[self.sections objectForKey:[NSNumber numberWithInt:section - 1]]count] -1)};
previousIndexPath = [[NSIndexPath alloc] initWithIndexes:previousIndex length:2];
}
else if (cell.cellPath.row != 0 && cell.cellPath.section != 0)
{
NSUInteger previousIndex[] = {section, row - 1};
previousIndexPath = [[NSIndexPath alloc] initWithIndexes:previousIndex length:2];
}
[self.theTableView scrollToRowAtIndexPath: cell.cellPath.section == 0 ? cell.cellPath : previousIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

Resources