Keeping (storing) View Controller content with Storyboard and SASlideMenu - ios

I'm using the SASlideMenu library to implement a left slide menu in my iOS ARC storyboard app. I have added three View Controllers called with the [self performSegueWithIdentifier:#"segueID" sender:self]; code, and the slide menu works fine, but every time it create a NEW INSTANCE of a View Controller while I need to cache each controller content (example, in View Controller 1 we have a text field: If I write a word, after changing VC I must find again same word!). This is generic Storyboard:
This is my UITableView delegate, in SASlideMenuViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:
UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
if (indexPath.row == 0) {
cell.textLabel.text = #"ViewController 1";
}
if (indexPath.row == 1) {
cell.textLabel.text = #"ViewController 2";
}
if (indexPath.row == 2) {
cell.textLabel.text = #"ViewController 3";
}
cell.textLabel.font = [UIFont systemFontOfSize:14];
cell.textLabel.textColor = [UIColor blackColor];
cell.imageView.image = [UIImage imageNamed:#"image.png"];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.row==0)
{
[self performSegueWithIdentifier:#"segueID1" sender:self]; // I NEED TO RETRIEVE SAME VIEW CONTROLLER 1 CONTENT AFTER ANOTHER VIEW CONTROLLER CALL!
}
if(indexPath.row==1)
{
//[self performSegueWithIdentifier:#"segueID2" sender:nil]; // I NEED TO RETRIEVE SAME VIEW CONTROLLER 2 CONTENT AFTER ANOTHER VIEW CONTROLLER CALL!
}
if(indexPath.row==2)
{
//[self performSegueWithIdentifier:#"segueID3" sender:nil]; // I NEED TO RETRIEVE SAME VIEW CONTROLLER 3 CONTENT AFTER ANOTHER VIEW CONTROLLER CALL!
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
And this is a screenshot of slide menu in app:
In the author tutorial I can read: "If you are using dynamic cell prototype or you are using static cells and you want to cache the content view controller, assign an identifier that will be returned in the segueIdForIndexPath: method linked to the desired indexPath" but I'm losing my head and I cannot get the precise point.. please can u provide the precise code to resolve this? There is something else to add, but where? I need to reproduce this situation: EVERYTIME I call a View Controller from the left menu I MUST retrieve it identical as I have leaved before another previous VC call. Thanks!

In order to have your code working you need to implement two methods of the SASlideMenuDataSource protocol and you have to initialize the datasource property of your ViewController. Moreover you have to avoid to directly invoke performSegueWithIdentifier and you should avoid to implement tableView:didSelectRowAtIndexPath: method, because otherwise caching will not work.
The first method to implement is configureMenuButton:. You need it because you need the menu button in the content ViewController you are sliding in. One possibility is to copy the icons provided in the example project and add them to your project:
-(void) configureMenuButton:(UIButton *)menuButton{
menuButton.frame = CGRectMake(0, 0, 40, 29);
[menuButton setImage:[UIImage imageNamed:#"menuicon.png"] forState:UIControlStateNormal];
[menuButton setBackgroundImage:[UIImage imageNamed:#"menu.png"] forState:UIControlStateNormal];
[menuButton setBackgroundImage:[UIImage imageNamed:#"menuhighlighted.png"] forState:UIControlStateHighlighted];
[menuButton setAdjustsImageWhenHighlighted:NO];
[menuButton setAdjustsImageWhenDisabled:NO];
}
Than you need to implement segueIdForIndexPath:. The method returns the segueId associated to the indexPath of the row of the menu, looking at your code should be something like:
-(NSString*) segueIdForIndexPath:(NSIndexPath *)indexPath{
if(indexPath.row==0){
return #"vista1";
} else if (indexPath.row==1){
return #"vista2";
}else if(indexPath.row==2){
return #"vista3";
}
return #"vista1";
}
Finally you have to correctly initialize the dataSource property of your ViewController:
-(id) initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
self.slideMenuDataSource = self;
}
return self;
}
I hope you will find the answer useful.

Related

Custom UITableViewCell weird behavior with fast scroll

I have a grouped tableView with 5 sections. The tableView uses a custom UITableViewCell with 2 label and 4 buttons in it. When i select 1 or more buttons at the beginning of the table and then scroll to the end of it, i find those buttons selected in the last cell, sometimes in the second-last. It seems to me that there is some issues with the dequeueReusableCellWithIdentifier but i cannot figure it out.
For clarity i have this code in my viewDidLoad:
// table cell
UINib *nib = [UINib nibWithNibName:#"SMRateMeetingTableViewCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"SMRateMeetingTableViewCell"];
and this in my cellForRowAtIndexPath:
SMRateMeetingTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SMRateMeetingTableViewCell" forIndexPath:indexPath];
// Configure the cell...
if (cell == nil) {
cell = [[SMRateMeetingTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"SMRateMeetingTableViewCell"];
}
Pretty basic stuff.
I added some screens for better understanding.
EDIT: adding buttons code.
For an easier analysis let's assume i only have 1 button in the custom cell
This is the table view code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SMRateMeetingTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SMRateMeetingTableViewCell" forIndexPath:indexPath];
// Configure the cell...
if (cell == nil) {
cell = [[SMRateMeetingTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"SMRateMeetingTableViewCell"];
}
//the tag will allow me to understand in which section is the button
cell.firstYesButton.tag = indexPath.section;
[cell.firstYesButton addTarget:self action:#selector(firstYesButtonAction:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
And the relative method associated to the button:
-(IBAction)firstYesButtonAction:(id)sender
{
UIButton *senderButton = (UIButton *)sender;
[self.votesArray replaceObjectAtIndex:(senderButton.tag*2) withObject:[NSNumber numberWithInt:1]];
//NSLog(#"%#", self.votesArray );
}
An this is the code in the implementation file of the custom cell:
#implementation SMRateMeetingTableViewCell
- (void)awakeFromNib {
// Initialization code
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateDisable.png"] forState:UIControlStateNormal];
}
- (IBAction)firstYesAction:(id)sender {
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateEnable.png"] forState:UIControlStateNormal];
}
This is because the table view is recycling your cell. This is what the dequeueReusableCellWithIdentifier: method is doing. Because it is recycling views instead of a new one always being created, you will have to set certain properties, such as the selected state of your buttons, otherwise they will retain the properties that they had when they were enqueued.
As UITableViewCell's get recycled which is quite unpredictable.
So one approach is to maintain the state with key value pairs
that this NSDictionary and set images as per state changed in NSDictionary like
#{
"0":"EnableImage",
"1":"DisableImage",
"2":"DisableImage",
"3":"EnableImage",
}
so set image as per the above dictionary in cellForRowAtIndexPath.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.... ....
You need set image as per state maintained in above dictionary
return cell;
}
And Remove below code
(void)awakeFromNib {
// Initialization code
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateDisable.png"] forState:UIControlStateNormal];
}
- (IBAction)firstYesAction:(id)sender {
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateEnable.png"] forState:UIControlStateNormal];
}

Expanding and Collapsing table view cells in ios

I have a table view of custom cells and some buttons in each cell.Clicking on any of the button inside the cell will reveal another custom view below that cell.Next click on the same button will collapse the view and need this same for all cells.I tried with insertrow method on the button click but in vain.How can i do this with using only the table view delegates.
This is what i tried:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"CustomCell_For_Dashboard";
CustomCellFor_Dashboard *customCell = (CustomCellFor_Dashboard *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (customCell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellFor_Dashboard" owner:self options:nil];
customCell = [nib objectAtIndex:0];
}
[customCell.howyoulfeelBtn addTarget:self action:#selector(buttonclicked:) forControlEvents:UIControlEventTouchUpInside];
customCell.nameLabel.text = #"test";
customCell.imgView.image = [UIImage imageNamed:#"Default.png"];
// customCell.prepTimeLabel.text = [prepTime objectAtIndex:indexPath.row];
return customCell;
}
-(void)buttonclicked:(id)sender{
NSIndexPath *indexPath = [myTable indexPathForCell:sender];
[myTable beginUpdates];
NSIndexPath *insertPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
[myTable insertRowsAtIndexPaths:[NSArray arrayWithObject:insertPath] withRowAnimation:UITableViewRowAnimationTop];
}
can anyone help me?
I got the same task on one project with just one thing different: There were no buttons, just tapping on cell will expand or collapse it.
There are several things you should edit in your code. First, the button method code will look something like this:
- (void) collapseExpandButtonTap:(id) sender
{
UIButton* aButton = (UIButton*)sender; //It's actually a button
NSIndexPath* aPath = [self getIndexPathForCellWithButtonByMagic:aButton];
//expandedCells is a mutable set declared in your interface section or private class extensiont
if ([expandedCells containsObject:aPath])
{
[expandedCells removeObject:aPath];
}
else
{
[expandedCells addObject:aPath];
}
[myTableView beginEditing];
[myTableView endEditing]; //Yeah, that old trick to animate cell expand/collapse
}
Now the second thing is UITableViewDelegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Key thing here is to determine if your cell should be expanded/collapsed and return right height in delegate method.
Going off of what #eagle.dan.1349 said, this is how to do it on the clicking of the cell. In storyboard, you also need to set the table cell to clip subviews, otherwise the content that would be hidden will show.
.h
#property (strong, nonatomic) NSMutableArray *expandedCells;
.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.expandedCells containsObject:indexPath])
{
[self.expandedCells removeObject:indexPath];
}
else
{
[self.expandedCells addObject:indexPath];
}
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat kExpandedCellHeight = 150;
CGFloat kNormalCellHeigh = 50;
if ([self.expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Saw this post and just wanted to give my 2 cents as my solution to this is very similar to the chosen answer (the tapping of a whole area).
Many people architect this by using just cells alone, but I believe there is a way to build this that might align better with what people are trying to achieve:
There are headers and there are cells. Headers should be tappable, and then cells underneath the headers would show or hide. This can be achieved by adding a gesture recognizer to the header, and when tapped, you just remove all of the cells underneath that header (the section), and viceversa (add cells). Of course, you have to maintain state of which headers are "open" and which headers are "closed."
This is nice for a couple of reasons:
The job of headers and cells are separated which makes code cleaner.
This method flows nicely with how table views are built (headers and cells) and, therefore, there isn't much magic - the code is simply removing or adding cells, and should be compatible with later versions of iOS.
I made a very simple library to achieve this. As long as your table view is set up with UITableView section headers and cells, all you have to do is subclass the tableview and the header.
Link: https://github.com/fuzz-productions/FZAccordionTableView
I also had a same situation and my solution was to put a button on top of the Section Title with viewForHeaderInSection method.
noOfRows defines how many rows are there in each section and button.tag keeps which button of section is pressed.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIButton *btnSection = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)];
btnSection.tag = section;
[btnSection setTitle:[sectionArray objectAtIndex:section] forState:UIControlStateNormal];
[btnSection addTarget:self action:#selector(sectionButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
return btnSection;
}
- (void)sectionButtonTapped:(UIButton *)button {
sectionIndex = button.tag;
if (button.tag == 0) {
noOfRows = 3;
} else if (button.tag == 1) {
noOfRows = 1;
} else if (button.tag == 2) {
noOfRows = 2;
}
[self.tableView reloadData];
}
Hope this will help you..

Add xib as a view to cover a custom UITableViewCell in editing mode

I'm trying to to something like apple's alarm clock, when tap the edit button, a custom view cover the custom UITableViewCell.
The code above:
// CGRect frame = [tableView rectForRowAtIndexPath:indexPath];
// CGPoint yOffset = self.tableViewBlock.contentOffset;
// CGRect newFrame = CGRectMake(frame.origin.x, (frame.origin.y - yOffset.y + 45), frame.size.width, frame.size.height);
CallBlock_Date_EditMode *viewController = [[CallBlock_Date_EditMode alloc] initWithNibName:#"CallBlock_Date_EditMode" bundle:nil];
// self.view.frame = newFrame;
// [self.view addSubview:viewController.view];
// [self addChildViewController:viewController];
UITableViewCell *cell = (UITableViewCell*)[self.tableViewBlock cellForRowAtIndexPath:indexPath];
[cell.contentView addSubview:viewController.view];
Cover the specific cell when I put in under:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Just to make sure the size is ok (although when I tap a button in that xib the app crash without even a single error).
But I want to do like apple's alarm clock (actually, mimic it), tap my edit button and my custom UITableViewCell will get cover with this xib as a view.
Maybe there is a better approach to do it?
EDIT:
My updated code is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CallBlock_TableCell *cell = (CallBlock_TableCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil)
{
cell = [[CallBlock_TableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(CallBlock_TableCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
cell.accessoryType = self.isEditing ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
CallBlock_ByDate *callBlock = (CallBlock_ByDate*)[fetchedObjects objectAtIndex:indexPath.row];
cell.labelTime.text = callBlock.startDate;
cell.labelRepeat.text = callBlock.repeat;
cell.labelTextLabel.text = callBlock.label;
cell.switchCallBlock.on = YES;
cell.switchCallBlock.tag = (NSInteger)indexPath.row +1;
[cell.switchCallBlock addTarget:self action:#selector(handleSwitch:) forControlEvents:UIControlEventValueChanged];
cell.switchCallBlock.hidden = self.isEditing ? YES : NO;
if (self.isEditing)
{
cell.switchCallBlock.hidden = YES;
UIButton *btnArrow = [[UIButton alloc] init];
btnArrow.frame = CGRectMake(282.0, 31.0, 18.0, 21.0);
[btnArrow setBackgroundImage:[UIImage imageNamed:#"arrow_FWR_off"] forState:UIControlStateNormal];
[btnArrow setBackgroundImage:[UIImage imageNamed:#"arrow_FWR_on"] forState:UIControlStateHighlighted];
btnArrow = [UIButton buttonWithType:UIButtonTypeCustom];
[btnArrow addTarget:self action:#selector(handleTapToEdit:) forControlEvents:UIControlEventTouchUpInside];
btnArrow.tag = indexPath.row + 1;
[cell.contentView addSubview:btnArrow];
[cell.contentView bringSubviewToFront:btnArrow];
}
}
But I cannot get the btnArrow appear on the UTableView.
The reason you are getting a crash is because nothing is retaining your CallBlock_Date_EditMode view controller. You add its view to your cell as a subview, but nothing maintains a reference to the view controller, so it is deallocated and then, when pressing a button that is supposed to pass a message to your view controller, it is sent to a deallocated object and you get a crash.
There are two possible solutions to this. First, you could store that view controller in one of your properties to maintain a reference to it so that it does not deallocated. This is, for the most part, probably not what you want.
Instead, what I would suggest doing is do not make your CallBlock_Date_EditMode a UIViewController, but instead make it a UIView. You may be wondering "But how can I use a xib without a UIViewController?". I would do something like the following:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
UIView *view = [[[NSBundle mainBundle] loadNibNamed:#"CallBlock_Date_EditMode" owner:self options:nil] objectAtIndex:0];
self.myEditButton = (UIButton *)[view viewWithTag:2];
[self addSubview:view];
}
return self;
}
This would be code inside your custom UIView that would load in a xib file and add it as a subview. In order to get access to your subviews, you have to use tags inside interface builder, so you do lose the convenience of drawing/connecting IBOutlets... But in the end, it is much better than allocating/storing a bunch of unnecessary UIViewControllers.
If I understand you right and you want to mimic the functionality of the alarm clock that comes pre-installed from Apple, your solution is much simpler than creating a custom view. It looks like all they do is set the On-Off switches to hidden and add a disclosure indicator to the cell. This is what I would do...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
bool hide = (tableView.editingStyle == UITableViewCellEditingStyleDelete); // set to true or false depending on if the table is in editing mode
for (UIView *sv in [cell subviews] ) {
if([sv isKindOfClass:[UISwitch class]]) { // find the on-off switch
[sv setHidden:hide]; // hide the switch depending on the t/f value of hide
break;
}
}
if(hide) { // adds the arrow like in apple's alarm clock table if the cell is in edit mode
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
return cell;
}

Passing name of cell to another view via table cell button

Alright, so I want to pass the name contained in a cell of a table in one view controller to an array in another view controller. I want to do this via a button that is also contained in the cell of the first table.
Here is where the table cells are described:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// add "add" button
UIButton *addFriendButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
addFriendButton.frame = CGRectMake(250.0f, 5.0f, 75.0f, 30.0f);
[cell addSubview:addFriendButton];
[addFriendButton addTarget:self
action:#selector(addFriend:)
forControlEvents:UIControlEventTouchUpInside];
if(!cell)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [array objectAtIndex:indexPath.row];
return cell;
}
And here is where the button method would be described:
- (IBAction)addFriend:(id)sender
{
NSLog(#"Add friend.");
}
The name of the first view controller - the one doing the pass - is called ViewController - and the second view controller - the one receiving the passed info - is called MyMealViewController. The array that I want the passed info to be stored in is called myMenuArray.
I think that's basically all the relevant information. If there is any additional information that you need from me - or if you need clarification on the question I'm asking - to make my question answerable, please let me know!
You can use
cell.textLabel.text = [array objectAtIndex:indexPath.row];
[[addFriendButton addTarget:self
action:#selector(addFriend:) toTarget:self withObject:cell.textLabel.text]
forControlEvents:UIControlEventTouchUpInside];
- (void)addFriend:(NSString *)theFriendName
{
NSLog(#"Add friend.");
// Now here you make the instance of the new ViewController and you set the text you are adding.
MyViewController *theVC = [MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
[theVC setNewFriend:theFriendName];
[self presentModalViewController:theVC animated:YES];
}

UITableViewCell, two cells are both getting an accessory view

Situation: When a user selects a cell, a button is added to that cell. When they select a new cell, the button is removed from the previous cell and added to the new cell. That works. The problem is when more data is added to the table. So lets say there are 20 cells, then I add another 20 cells. I then select the first cell, however the button is added to cell 1 AND cell 21. The select delegate method only registers the first one being selected though.
From my didSelectRowAtIndexPath method:
if (self.selectedCell) {
self.selectedCell.accessoryView = nil;
self.selectedCell = nil;
}
UIButton *downloadButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[downloadButton setTitle:#"Download" forState:UIControlStateNormal];
[downloadButton setFrame:CGRectMake(0, 0, 130, 34)];
self.selectedCell = [self.tableView cellForRowAtIndexPath:indexPath];
self.selectedCell.accessoryView = downloadButton;
[self.selectedCell setNeedsDisplay];
In my method that adds more data to the table I end with:
if(self.selectedCell){
self.selectedCell.accessoryView = nil;
self.selectedCell = nil;
}
[self.tableView reloadData];
[self.tableView setNeedsLayout];
Cells are reused. In your cellForRowAtIndexPath:, you're forgetting to clear accessoryView. When the cell is reused, the accessoryView comes along.
A technique I like is to set the accessoryView in tableView:willDisplayCell:forRowAtIndexPath:. This is called right before the cell is put on the screen. You can then do something like:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (cell.isSelected) {
cell.accessoryView = self.downloadButton; // No reason to create a new one every time, right?
}
else {
cell.accessoryView = nil;
}
}

Resources