I am attempting to create a animated cell when it is selected. Everything seems to be working really odd. If I send the ButtonView to the Self.contentView of the UITableView Cell it works like it is supposed to and adds the cell in desired row but I loose button functionality at index row. If I send the buttonview to just self "UITableViewCell" I have button index row but it adds the button view in about every 10 rows.
Now the animation itself looks great when it works right. The issue is I am using a method to toggle the path selected so I can grow the cell height to twice the size when selected and stay selected until another cell is selected.
Really I have no idea where to go from here I was hoping maybe someone could help me find my mistake or have delt with something similar.
Thanks I appreciate any insight!
- (BOOL)cellIsSelected:(NSIndexPath *)indexPath {
// Return whether the cell at the specified index path is selected or not
NSNumber *selectedIndex = [selectedIndexes objectForKey:indexPath];
return selectedIndex == nil ? FALSE : [selectedIndex boolValue];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath {
[sounds PlayButtonClick:nil];
// Deselect cell ..?
//[tableView deselectRowAtIndexPath:indexPath animated:TRUE];
// Toggle 'selected' state
BOOL isSelected = ![self cellIsSelected:indexPath];
// Store cell 'selected' state keyed on indexPath
NSNumber *selectedIndex = [NSNumber numberWithBool:isSelected];
[selectedIndexes setObject:selectedIndex forKey:indexPath];
// This is where magic happens...
[tableView beginUpdates];
[tableView endUpdates];
[sounds PlaySlide:nil];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath
*)indexPath {
// If our cell is selected, return double height
if([self cellIsSelected:indexPath]) {
return 70 *2;
}
return 70;
}
And from my CellClass
- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
if (self == [super initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:reuseIdentifier]) {
self.contentView.clipsToBounds = YES;
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (selected == YES) {
NSLog(#"is selected");
[self showButtonView:!selected];
[self applyDropShadow:!selected];
// TweettextLabel.hidden = YES;
// Tweettext.hidden = NO;
}
if (selected == NO){
[self removeButtonView:!selected];
[self removeDropShadow:!selected];
//TweettextLabel.hidden = NO;
//Tweettext.hidden = YES;
NSLog(#"is NOT selected");
}
}
-(void)showButtonView:(int)view{
CGRect viewFrame = [ButtonView frame];
viewFrame.origin.y = 0;
self.ButtonView.frame = viewFrame;
[self.contentView insertSubview:self.ButtonView belowSubview:self.cellView];
[self addSubview:self.contentView];
[UIView beginAnimations:#"animateView" context:nil];
[UIView setAnimationDuration:0.3];
CGRect viewFrame1 = [ButtonView frame];
viewFrame1.origin.y = 70;
ButtonView.frame = viewFrame1;
[UIView commitAnimations];
}
-(void)removeButtonView:(int)view{
[UIView beginAnimations:#"animateView" context:nil];
[UIView setAnimationDuration:0.3];
CGRect viewFrame1 = [self.ButtonView frame];
viewFrame1.origin.y = 0;
self.ButtonView.frame = viewFrame1;
[UIView commitAnimations];
// timer = [NSTimer scheduledTimerWithTimeInterval:0.4 target:self
selector:#selector(removeLastViewFromSuper) userInfo:Nil repeats:NO];
}
-(void)applyDropShadow:(int)shadow{
self.contentView.layer.shadowColor = [[UIColor blackColor] CGColor];
self.contentView.layer.shadowOffset = CGSizeMake(0.8f, -4.0f);
self.contentView.layer.shadowRadius = 5.0f;
self.contentView.layer.shadowOpacity = 1.0f;
}
-(void)removeDropShadow:(int)shadow{
self.contentView.layer.shadowColor = [[UIColor clearColor] CGColor];
self.contentView.layer.shadowOffset = CGSizeMake(0.0, 0.0);
self.contentView.layer.shadowRadius = 0.0;
self.contentView.layer.shadowOpacity = 0.0;
}
Add the buttonView to the cell's contentView when you create it. Show and hide it by setting the hidden or alpha property. Now the problem of not getting the button to work should go away.
Also, your dictionary with the selected indexes seems a bit overdone if you only have one cell that is selected. Just create a property "selected" of type NSIndexPath in the controller.
Finally, your selected cell repeating when you scroll is due to the re-queuing mechanism of UITableView. Make sure that you set the correct hidden property for the button in cellForRowAtIndexPath each time. This way, when a cell is recycled that has a non-hidden button, it will hide it if the index path is not the selected one.
Related
I would like to be able to drag a cell over another cell, and when I release it would effectively "merge" the cells (display a different cell). I'm not sure if a UITableView is the best control or a UICollectionView. Does anyone have any thoughts on how to proceed?
May be I didn't get u properly but as I got, u want to merge cells by dragging so implement some code for you.Have a look at this.
If u Wanna something like this...
use editing code like that :---
ViewDidLoad initializing array and set table to edit mode as:
fruitsArray=[[NSMutableArray alloc] initWithObjects:#"Apple",#"Banana",#"Mango",#"Guava",#"PineApple",#"Watermelon",#"Grapes",#"GroundNut",#"Muskmelon",#"Orange",#"Cherry",nil];
[tblView setEditing:YES];
datasources for tableView set as:->
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewCellEditingStyleNone;
}
- (BOOL)tableView:(UITableView *)tableview shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
if (sourceIndexPath.row!=destinationIndexPath.row) {
NSString *sourceString=[fruitsArray objectAtIndex:sourceIndexPath.row];
NSString *destinationString=[fruitsArray objectAtIndex:destinationIndexPath.row];
destinationString=[destinationString stringByAppendingFormat:#",%#",sourceString];
[fruitsArray replaceObjectAtIndex:destinationIndexPath.row withObject:destinationString];
[fruitsArray removeObjectAtIndex:sourceIndexPath.row];
[tblView reloadData];
}
}
Try this sampleCode
Here's the solution I used to develop it in pseudocode.
Add a UILongPressGestureRecognizer to the UITableView
On the UIGestureRecognizerStateBegan, use the [UITableView indexPathForRowAtPoint] in order to determine what cell is being "long pressed". You will turn this cell into the hover UITableViewCell.
Set a reference variable to the corresponding model object.
Use the UIGraphicsBeginImageContextWithOptions to render an image of the actual UITableViewCell contents. Create a UIImageView and add some custom chrome to it (shadows, etc.)
Add the custom UIImageView as a subview to the UITableView, and remove the original selected pinned UITableViewCell (removeFromSuperview).
On UIGestureRecognizerStateChanged, calculate the location of the hover UITableViewCell in the UITableView and animate the pinned cell. (You will also want to toggle the animation for the previously highlighted pinned cell).
When the user releases the LongPress, you'll want to re-order the model array, remove the underlying pinned cell, and then reload the UITableView all in a beginUpdates call.
There are a few other things to watch out for such as scrolling when you hit a boundary condition, dropping the hover cell over the original location, etc. but that's the basic gist of it. Hopefully that helps.
I used Long press gesture for this. here is sample code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:cell action:nil];
longPress.delegate = self;
[cell addGestureRecognizer:longPress];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
[gestureRecognizer addTarget:self action:#selector(moveRight:)];
}
return YES;
if([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]){
[gestureRecognizer addTarget:self action:#selector(tapAction:)];
}
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
- (void)moveRight:(UILongPressGestureRecognizer *)sender {
/*if (sender.view.tag==4)
{
[super setEditing:YES animated:YES];
[self.Table4 setEditing:YES animated:YES];
}
else
{
*/
UIView *view = sender.view;
int t = sender.view.tag;
CGPoint point = [sender locationInView:view.superview];
NSLog(#"its added %f", point.x);
NSLog(#"its added %f", point.y);
// view.frame = CGRectMake(sender.view.frame.origin.x, sender.view.frame.origin.y+200,200, 30);
CGPoint center = view.center;
center.x = point.x ;
center.y = point.y ;
view.center = center;
[self.view addSubview:view];
// int x = Table4.frame.origin.x;
// int y = Table4.frame.origin.y;
if (sender.state == UIGestureRecognizerStateChanged) {
CGPoint center = view.center;
center.x += point.x - _priorPoint.x;
center.y += point.y - _priorPoint.y;
NSLog(#"its Center %f", center.x);
if (center.x > Table4.frame.origin.x &&
center.x < Table4.frame.origin.x +
Table4.frame.size.width &&
center.y > Table4.frame.origin.y &&
center.y < Table4.frame.origin.y +
Table4.frame.size.height
)
{
int count = [self.rowdata count];
if (t>=100 && t<200)
{
t=t-100;
[self.rowdata insertObject:[listData objectAtIndex:t] atIndex:count];
}
else if (t>=200 && t<300)
{
t=t-200;
[self.rowdata insertObject:[listData2 objectAtIndex:t] atIndex:count];
}
else
{
t=t-300;
[self.rowdata insertObject:[listData3 objectAtIndex:t] atIndex:count];
}
//[self.rowdata addObject:[listData objectAtIndex:t]];
//[sender release];
[Table4 reloadData];
[Table1 reloadData];
[Table2 reloadData];
[Table3 reloadData];
}
view.center = center;
//}
_priorPoint = point;
}
}
I think this is helpful for you. Take some code what you want from this.
I have a standard UITableView. I want to set the shadowColor of the cell's textLabel to [UIColor whiteColor], but only when the cell is touched. For that, I'm using the following code. It's a custom UITableViewCell subclass that overrides setSelected/setHighlighted:
#implementation ExampleTableViewCell
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
[self setShadowColorSelected:selected];
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
[super setHighlighted:highlighted animated:animated];
[self setShadowColorSelected:highlighted];
}
- (void)setShadowColorSelected:(BOOL)selected {
if (selected) {
self.textLabel.shadowColor = [UIColor blackColor];
}else {
self.textLabel.shadowColor = [UIColor whiteColor];
}
}
#end
My problem with this approach is that, on deselection, the cell has a very short period where both the label's text and shadow are white. See this screenshot, which was taken in the exact moment of deselection:
It's basically the same approach as in these two posts:
UILabel shadow from custom cell selected color
Removing text shadow in UITableViewCell when it's selected
I'm using the approach of the accepted answer in the latter question.
I have created a very very simple code project and uploaded it to github. It shows off my problem. It's just a UITableViewController that displays a single cell.
Apart from that, there's nothing fancy. UITableView delegate methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[ExampleTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = #"test";
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES]; //setting this to NO doesn't work either!
}
Any ideas?
If I understood the problem, you need to display the shadow color until the cell selection is animated to be gone. I'm not sure what is incorrect in the way you tried, more straightforward solution works fine though.
Please note, you'll need to remove observer once it's not needed.
ExampleTableViewCell.h
#interface ExampleTableViewCell : UITableViewCell {
}
- (void) setSelectionShadowOfColor:(UIColor *) selColor;
#end
ExampleTableViewCell.m
#implementation ExampleTableViewCell
- (void) setSelectionShadowOfColor:(UIColor *) selColor {
self.textLabel
[self addObserver:self
forKeyPath:#"textLabel.highlighted" // not isHighlighted as that is a getter name of the highlighted property
options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
BOOL isHighlighted = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
if (isHighlighted) {
self.textLabel.shadowColor = [UIColor blackColor];
} else {
self.textLabel.shadowColor = [UIColor whiteColor];
}
}
#end
ExampleTableViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
ExampleTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // note the type ExampleTableViewCell is used here to avoid the interface lookup mess
if (!cell) {
cell = [[ExampleTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell setSelectionShadowOfColor:[UIColor blackColor]];
}
cell.textLabel.text = #"test";
return cell;
}
Add the below to your UITableViewDelegate:
NSIndexPath *selectedIndex;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[ExampleTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = #"test";
if(indexpath == selectedIndex)
{
cell.textlabel.shadowColor = [UIColor blackColor];
}
else
{
cell.textlabel.shadowColor = [UIColor whiteColor];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView beginUpdates];
selectedIndex = indexpath;
[self.tableView endUpdates];
}
This is the best I was able to do:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:NO];
[self setShadowColorSelected:selected];
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
[super setHighlighted:highlighted animated:YES];
[self setShadowColorSelected:highlighted];
}
- (void)setShadowColorSelected:(BOOL)selected {
[self.textLabel setBackgroundColor:[UIColor clearColor]];
if (selected)
{
[self.textLabel setTextColor:[UIColor whiteColor]];
[self.textLabel setShadowColor:[UIColor blackColor]];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView transitionWithView:self.textLabel duration:0.25 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
if (selected)
{
[self.textLabel setTextColor:[UIColor whiteColor]];
[self.textLabel setShadowColor:[UIColor blackColor]];
}
else
{
[self.textLabel setTextColor:[UIColor blackColor]];
[self.textLabel setShadowColor:[UIColor whiteColor]];
}
} completion:nil];
}
else
{
[self.textLabel setTextColor:[UIColor blackColor]];
[self.textLabel setShadowColor:[UIColor whiteColor]];
}
}
Here is a simple way to accomplish what you want:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.textLabel.shadowColor = [UIColor blackColor];
[super touchesBegan:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.textLabel.shadowColor = [UIColor whiteColor];
[super touchesEnded:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
self.textLabel.shadowColor = [UIColor whiteColor];
[super touchesCancelled:touches withEvent:event];
}
I guess you should need to animate the changing of the text/shadow color with the same duration that UITableView uses to animate selection/deselection. In my understanding, you change the text/shadow color at the exact moment that the tableView begins animating the (dis)appearing of the selection highlight, so what you get is that your colors change momentarily, while the selection highlight takes some time to animate from one state to another
Try something like this:
__block UIColor *newShadowColor = selected ? [UIColor blackColor] : [UIColor whiteColor];
[UIView animateWithDuration:0.2
animations:^{
/* change your label/shadow color here. */
self.textLabel.shadowColor = newShadowColor;
}
completion:^(BOOL finished){
/* this is where the cell is no longer selected
or highlighted. You may do some additional style changes to your
label here */ }];
I had the same issue. All solutions I looked at required subcassing / too much additional code.
What I did in the end is to create a second UILabel underneath the primary UILabel to act as a shadow.
Don't set shadows on your primary and shadow labels. For the shadow label, set the 'Normal Color' to what you wanted your shadow color to be and set the highlighted color to 'Clear Color'.
Obviously you have to update the shadow label each time you update the primary label. Not a big price to pay in many cases.
Hope that helps!
According to my understanding you can eliminate this issue by changing the color of the tableview by using following code with the color needed
[tableView setBackgroundColor:[UIColor "GrayColor"]];
I was also facing the same problem.
Instead of using default label, you can use UIButton and your problem will be solved.
Put custom button in cell.
My requirements were solved. It might help you.
I'm trying to create a view, where user enter his info (name, email, bio...).
To do this I'm using a group table view with 5 section (0..4), each has one cell that has a UITextField inside (except the cell in section 4 that has a UITextView inside).
When user tap on one of those textFields/textView the keyboard appears, and the cell (with the selected textField/textView) scroll to the position just above the keyboard.
My problem is with the textView at section 4, when keyboard appears it somehow automatically scroll the cell to be visible, but not exactly in the right position (half of the text view is hidden behind the keyboard).
When I try to do the scroll myself (after keyboard finish animating) I get an ugly bounce of the textView.
It seams that the cause for the ugly bounce is that the textView automatic scrolls when the keyboard appear + my scrolling. This behavior (of automatic scrolling) happens only for the textView, for example if I use a textField instead of the textView in section 4, it doesn't automatically scroll.
So the question is: how can I smoothly scroll the textView to the right position?
This is my code:
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 5;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
switch (indexPath.section) {
case 0:
[cell.contentView addSubview:self.textField1];
break;
case 1:
[cell.contentView addSubview:self.textField2];
break;
case 2:
[cell.contentView addSubview:self.textField3];
break;
case 3:
[cell.contentView addSubview:self.textField4];
break;
case 4:
// this is the text view
[cell.contentView addSubview:self.textView];
break;
default:
break;
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
self.activeResponder = textField;
return YES;
}
#pragma mark - UITextViewDelegate
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
self.activeResponder = textView;
return YES;
}
- (void)keyboardWillShow:(NSNotification*)notification {
NSLog(#"keyboard show");
CGFloat animationDuration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect tableViewFrame = self.tableView.frame;
tableViewFrame.size.height -= keyboardFrame.size.height;
[UIView animateWithDuration:animationDuration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.tableView.frame = tableViewFrame;
} completion:^(BOOL finished) {
[self scrollToActiveResponder];
}];
}
- (void)keyboardWillHide:(NSNotification*)notification {
NSLog(#"keyboard hide");
CGFloat animationDuration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect tableViewFrame = self.tableView.frame;
tableViewFrame.size.height += keyboardFrame.size.height;
[UIView animateWithDuration:animationDuration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.tableView.frame = tableViewFrame;
} completion:^(BOOL finished) {
}];
}
- (NSIndexPath *)indexPathForActiveResponder {
NSIndexPath *indexPath = nil;
if (self.activeResponder == self.textField1) {
indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
}
else if (self.activeResponder == self.textField2) {
indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
}
else if (self.activeResponder == self.textField3) {
indexPath = [NSIndexPath indexPathForRow:0 inSection:2];
}
else if (self.activeResponder == self.textField4) {
indexPath = [NSIndexPath indexPathForRow:0 inSection:3];
}
else if (self.activeResponder == self.textView) {
indexPath = [NSIndexPath indexPathForRow:0 inSection:4];
}
return indexPath;
}
- (void)scrollToActiveResponder {
NSIndexPath *activeResponderIndexPath = [self indexPathForActiveResponder];
if (activeResponderIndexPath) {
[self.tableView scrollToRowAtIndexPath:activeResponderIndexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
}
}
I would try something like this:
.h
#interface YourViewController : UIViewController <UITextViewDelegate>
.m
//Set delegate to your textView
textView.delegate = self
#pragma mark - TextView Delegate
//TextView Became First Responder
- (void)textViewDidBeginEditing:(UITextView *)textView{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:4];
// Other way to get the IndexPath
//NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)[[textView superview] superview]];
CGRect frame = [self.tableView rectForRowAtIndexPath:indexPath];
//It only works for Portrait iPhone
CGFloat keyboardHeight = 216.0f;
CGPoint offset = CGPointMake(0, self.view.frame.size.height - keyboardHeight - frame.size.height - frame.origin.y);
[self.tableView setContentOffset:offset animated:YES];
}
I didn't test it, but it's how I would try to approach this problem. You can find more information about how calculate keyboard size here: (http://www.idev101.com/code/User_Interface/keyboard.html)
I have a custom UITableViewCell that has 1 textfield and a label on it. I would like to enable the textfield editing only when the tableview is in edit mode.
I'm using following methods to enable textfield edit mode and disable textfield edit mode. but this is not working. I'm not sure whether this is correct approach or not. If it's not the correct way, can you let me know how to disable enable textfield?
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *rowToSelect = indexPath;
EditingTableViewCell *detSelCell;
detSelCell = (EditingTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
detSelCell.textField.enabled = self.editing;
// Only allow selection if editing.
if (self.editing)
{
return indexPath;
}
else
{
return nil;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (!self.editing)
{
return;
}
EditingTableViewCell *detcell;
detcell = (EditingTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
detcell.selectionStyle = style;
detcell.textField.enabled = self.editing;
also I've the following lines:
self.tableView.allowsSelection = NO; // Keeps cells from being selectable while not editing. No more blue flash.
self.tableView.allowsSelectionDuringEditing = YES; // Allows cells to be selectable during edit mode.
Please help!
--- I found the answer:
I've removed the enable/disable code from following methods:
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
and added the following in custom cell.m
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1];
[UIView setAnimationDelegate:self];
[UIView setAnimationBeginsFromCurrentState:YES];
if(editing){
textField.enabled = YES;
}else{
textField.enabled = NO;
}
[UIView commitAnimations];
}
it's working now. i'm not sure whether this is correct approach or not but its working fine.
EDIT: my bad, didn't properly read the method signatures. What you're asking is quite hard to do, because all textfields are most likely subviews of the cell or it's contentView. What you need to do is disable the cell's selectionStyle, return nil for everything in willSelect... Then allow selection during editing.
Since I've been looking for the same for some time, here's the full code achieving it
TextFieldTableViewCell.h
#interface TextFieldTableViewCell : UITableViewCell <UITextFieldDelegate> {
UITextField *textField;
}
#property (nonatomic, strong) UITextField *textField;
// add delegate/protocol to inform of change
#end
TextFieldTableViewCell.m
#import "TextFieldTableViewCell.h"
#implementation TextFieldTableViewCell
#synthesize textField;
- (void)initializeTextField {
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.textField = [[UITextField alloc] initWithFrame:CGRectZero];
self.textField.autocorrectionType = UITextAutocorrectionTypeDefault;
self.textField.enabled = NO; // not editable unless the table is in edit mode
// do all the necessary textfield setup here, font/alignment and so on
[self addSubview:self.textField];
self.accessoryType = UITableViewCellAccessoryNone;
}
// from code
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if( (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self initializeTextField];
}
return self;
}
// from storyboard
- (id)initWithCoder:(NSCoder *)aDecoder {
if( (self = [super initWithCoder:aDecoder])) {
[self initializeTextField];
}
return self;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self.textField resignFirstResponder];
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
// Extra code to be added here to notify a delegate that text has changed since cell is holding the value temporarily
// .....
UITableView *tableView = (UITableView *)self.superview;
[tableView deselectRowAtIndexPath:[tableView indexPathForCell:self] animated:YES];
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect editFrame = CGRectInset(self.contentView.frame, 10, 10);
if (self.textLabel.text && [self.textLabel.text length] != 0) {
CGSize textSize = [self.textLabel sizeThatFits:CGSizeZero];
editFrame.origin.x += textSize.width + 10;
editFrame.size.width -= textSize.width + 10;
self.textField.textAlignment = UITextAlignmentRight;
} else {
self.textField.textAlignment = UITextAlignmentLeft;
}
self.textField.frame = editFrame;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
self.textField.enabled = editing;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
[self.textField becomeFirstResponder];
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (selected) {
[self.textField becomeFirstResponder];
}
}
#end
And with the setup on the table view you have the expected behavior of having a string displayed in normal mode and editable when in edit mode
self.tableView.allowsSelection = NO;
self.tableView.allowsSelectionDuringEditing = YES;
I am using the 'swipe to delete' functionality of the UITableView.
The problem is I am using a customised UITableViewCell which is created on a per item basis in
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
I need to alter the position of the delete button (simply to move it around 10px to the left), how would I go about doing this?
Here is my existing code for creating the cell:
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"cellForRowAtIndexPath");
#if USE_CUSTOM_DRAWING
const NSInteger TOP_LABEL_TAG = 1001;
const NSInteger BOTTOM_LABEL_TAG = 1002;
UILabel *topLabel;
UILabel *bottomLabel;
#endif
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
//
// Create the cell.
//
cell =
[[[UITableViewCell alloc]
initWithFrame:CGRectZero
reuseIdentifier:CellIdentifier]
autorelease];
#if USE_CUSTOM_DRAWING
const CGFloat LABEL_HEIGHT = 20;
UIImage *image = [UIImage imageNamed:#"trans_clock.png"];
//
// Create the label for the top row of text
//
topLabel =
[[[UILabel alloc]
initWithFrame:
CGRectMake(
image.size.width + 2.0 * cell.indentationWidth,
0.5 * (aTableView.rowHeight - 2 * LABEL_HEIGHT),
aTableView.bounds.size.width -
image.size.width - 4.0 * cell.indentationWidth
,
LABEL_HEIGHT)]
autorelease];
[cell.contentView addSubview:topLabel];
//
// Configure the properties for the text that are the same on every row
//
topLabel.tag = TOP_LABEL_TAG;
topLabel.backgroundColor = [UIColor clearColor];
topLabel.textColor = fontColor;
topLabel.highlightedTextColor = [UIColor colorWithRed:1.0 green:1.0 blue:0.9 alpha:1.0];
topLabel.font = [UIFont systemFontOfSize:[UIFont labelFontSize]];
//
// Create the label for the top row of text
//
bottomLabel =
[[[UILabel alloc]
initWithFrame:
CGRectMake(
image.size.width + 2.0 * cell.indentationWidth,
0.5 * (aTableView.rowHeight - 2 * LABEL_HEIGHT) + LABEL_HEIGHT,
aTableView.bounds.size.width -
image.size.width - 4.0 * cell.indentationWidth
,
LABEL_HEIGHT)]
autorelease];
[cell.contentView addSubview:bottomLabel];
//
// Configure the properties for the text that are the same on every row
//
bottomLabel.tag = BOTTOM_LABEL_TAG;
bottomLabel.backgroundColor = [UIColor clearColor];
bottomLabel.textColor = fontColor;
bottomLabel.highlightedTextColor = [UIColor colorWithRed:1.0 green:1.0 blue:0.9 alpha:1.0];
bottomLabel.font = [UIFont systemFontOfSize:[UIFont labelFontSize] - 2];
//
// Create a background image view.
//
cell.backgroundView =
[[[UIImageView alloc] init] autorelease];
cell.selectedBackgroundView =
[[[UIImageView alloc] init] autorelease];
#endif
}
#if USE_CUSTOM_DRAWING
else
{
topLabel = (UILabel *)[cell viewWithTag:TOP_LABEL_TAG];
bottomLabel = (UILabel *)[cell viewWithTag:BOTTOM_LABEL_TAG];
}
topLabel.text = #"Example Text";
topLabel.textColor = fontColor;
bottomLabel.text = #"More Example Text";
bottomLabel.textColor = fontColor;
//
// Set the background and selected background images for the text.
// Since we will round the corners at the top and bottom of sections, we
// need to conditionally choose the images based on the row index and the
// number of rows in the section.
//
UIImage *rowBackground;
UIImage *selectionBackground;
NSInteger sectionRows = [aTableView numberOfRowsInSection:[indexPath section]];
NSInteger row = [indexPath row];
if (row == 0 && row == sectionRows - 1)
{
rowBackground = [UIImage imageNamed:#"topAndBottomRow.png"];
selectionBackground = [UIImage imageNamed:#"topAndBottomRowSelected.png"];
}
else if (row == 0)
{
rowBackground = [UIImage imageNamed:#"topRow.png"];
selectionBackground = [UIImage imageNamed:#"topRowSelected.png"];
}
else if (row == sectionRows - 1)
{
rowBackground = [UIImage imageNamed:#"bottomRow.png"];
selectionBackground = [UIImage imageNamed:#"bottomRowSelected.png"];
}
else
{
rowBackground = [UIImage imageNamed:#"middleRow.png"];
selectionBackground = [UIImage imageNamed:#"middleRowSelected.png"];
}
((UIImageView *)cell.backgroundView).image = rowBackground;
((UIImageView *)cell.selectedBackgroundView).image = selectionBackground;
cell.imageView.image = [UIImage imageNamed:#"Example_Image.png"];
#else
cell.text = #"Example";
#endif
return cell;
}
For me the best way to solve this was overriding -(void)layoutSubviews in MyCell:UITableViewCell
Here you can see not only the Delete button custom position, but also repositioning the Edit and Reorder controls for Edit mode
- (void)layoutSubviews
{
[super layoutSubviews];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.0f];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationControl"]) {
CGRect newFrame = subview.frame;
newFrame.origin.x = 200;
subview.frame = newFrame;
}
else if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellEditControl"]) {
CGRect newFrame = subview.frame;
newFrame.origin.x = 100;
subview.frame = newFrame;
}
else if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellReorderControl"]) {
CGRect newFrame = subview.frame;
newFrame.origin.x = 200;
subview.frame = newFrame;
}
}
[UIView commitAnimations];
}
Iwat's code doesn't work for me. But this works.
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if ((state & UITableViewCellStateShowingDeleteConfirmationMask) == UITableViewCellStateShowingDeleteConfirmationMask) {
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationControl"]) {
subview.hidden = YES;
subview.alpha = 0.0;
}
}
}
}
- (void)didTransitionToState:(UITableViewCellStateMask)state {
[super didTransitionToState:state];
if (state == UITableViewCellStateShowingDeleteConfirmationMask || state == UITableViewCellStateDefaultMask) {
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationControl"]) {
UIView *deleteButtonView = (UIView *)[subview.subviews objectAtIndex:0];
CGRect f = deleteButtonView.frame;
f.origin.x -= 20;
deleteButtonView.frame = f;
subview.hidden = NO;
[UIView beginAnimations:#"anim" context:nil];
subview.alpha = 1.0;
[UIView commitAnimations];
}
}
}
}
I haven't been able to change the actual look of the delete button, but you can change the text. Perhaps you can use this to get the effect you are looking for?
Check out the following member of the UITableViewDelegate protocol:
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
Additionally, you can detect when the delete button is being displayed by subclassing UITableViewCell and overriding the the following:
- (void)willTransitionToState:(UITableViewCellStateMask)state
- (void)didTransitionToState:(UITableViewCellStateMask)state
For either of these, the state mask will be UITableViewCellStateShowingDeleteConfirmationMask when the delete button is being displayed.
Hopefully these clues will point you in the right direction.
I use a solution that on first sight looks kind of hacky but does the trick and is not relying on undocumented apis:
/**
* Transition to state
*/
-(void)willTransitionToState:(UITableViewCellStateMask)state {
if(state & UITableViewCellStateShowingDeleteConfirmationMask)
_deleting = YES;
else if(!(state & UITableViewCellStateShowingDeleteConfirmationMask))
_deleting = NO;
[super willTransitionToState:state];
}
/**
* Reset cell transformations
*/
-(void)resetCellTransform {
self.transform = CGAffineTransformIdentity;
_background.transform = CGAffineTransformIdentity;
_content.transform = CGAffineTransformIdentity;
}
/**
* Move cell around if we are currently in delete mode
*/
-(void)updateWithCurrentState {
if(_deleting) {
float x = -20;
float y = 0;
self.transform = CGAffineTransformMakeTranslation(x, y);
_background.transform = CGAffineTransformMakeTranslation(-x, -y);
_content.transform = CGAffineTransformMakeTranslation(-x, -y);
}
}
-(void)setFrame:(CGRect)frame {
[self resetCellTransform];
[super setFrame:frame];
[self updateWithCurrentState];
}
-(void)layoutSubviews {
[self resetCellTransform];
[super layoutSubviews];
[self updateWithCurrentState];
}
Basically this shifts the cell position and readjusts the visible portions.
WillTransitionToState just sets an instance variable indicating whether the cell is in delete mode. Overriding setFrame was necessary to support rotating the phone to landscape and vice-versa.
Where _background is the background view of my cell and _content is a view added to the contentView of the cell, holding all labels etc.
I don't know of any way to "move the delete button 10px to the left". However, you can animate the custom contents of your table cell around the static position of the unmovable delete button by listening for the willTransitionToState: message from a UITableViewCell sub-class see here.
To quote the docs:
Subclasses of UITableViewCell can
implement this method to animate
additional changes to a cell when it
is changing state. UITableViewCell
calls this method whenever a cell
transitions between states, such as
from a normal state (the default) to
editing mode. The custom cell can set
up and position any new views that
appear with the new state. The cell
then receives a layoutSubviews message
(UIView) in which it can position
these new views in their final
locations for the new state.
Subclasses must always call super when
overriding this method.
What you are looking for is the UITableViewCellStateShowingDeleteConfirmationMask value. This is one of those times where creating UITableViewCell subclass might be better, so from the controller you are just creating an instance of your custom cell. Then the cell can just adjust itself like animateLeft and animateRight and handle the inner workings of adjusting its own subviews.
I don't know the final solution, but as far as I tried the following code might be useful.
// subclass UITableViewCell
- (void)willTransitionToState:(UITableViewCellStateMask)state
{
[super willTransitionToState:state];
if ((state & UITableViewCellStateShowingDeleteConfirmationMask) == UITableCellStateShowingDeleteConfirmationMask)
{
for (UIView *subview in self.subviews)
{
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationControl"])
{
subview.hidden = YES;
subview.alpha = 0;
}
}
}
}
- (void)didTransitionToState:(UITableViewCellStateMask)state
{
[super willTransitionToState:state];
if ((state & UITableViewCellStateShowingDeleteConfirmationMask) == UITableCellStateShowingDeleteConfirmationMask)
{
for (UIView *subview in self.subviews)
{
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationControl"])
{
subview.frame = CGRectMake(subview.frame.origin.x - 10, subview.frame.origin.y, subview.frame.size.width, subview.frame.size.height);
subview.hidden = NO;
[UIView beginAnimations:#"anim" context:nil];
subview.alpha = 1;
[UIView commitAnimations];
}
}
}
}