Swipe to delete row without having to hit the delete button - objective-c

I know how the swipe to delete function works, though I'm not quite sure how to make it so that rather than having the delete button come up to delete I'd rather make it so that the cell is deleted upon swiping the cell to left or right.
Here's where I make the cell register the left swipe gesture to the left:
UISwipeGestureRecognizer* swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeLeft:)];
[swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[cell addGestureRecognizer:swipeLeft];
And here is the method for the selector:
- (void)swipeLeft:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
UITableViewCell *cell = (UITableViewCell *)gestureRecognizer.view;
NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
}
}
Now it works just fine when deleting cells from UITableview; however, I need an animation to show the cell moving to the left a certain distance and then deleting. I do not what a delete button.
edit: Here's the delete row with animation:
[self.tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView reloadData];
This deletes just fine, but can I make it so that it creates like a fade animation or can I modify this so after after swiping a specified distance will commit the delete?

This is a custom swipe I had made for a static table view, however should work for a dynamic table view. It will allow you to swipe to a set max x postition. If the user releases it will animate back to the original location.
I've tweaked the code so it should work with a dynamic cell. But not tested yet.
Add this local var
#property (nonatomic) CGFloat changeX;
Just connect a pan GR to the storyboard and connect to this action.
- (IBAction)dragCell:(UIPanGestureRecognizer *) gestureRecognizer
{
CGPoint swipeLocation = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *swipedIndexPath = [self.tableView indexPathForRowAtPoint:swipeLocation];
UITableViewCell*swipedCell = [self.tableView cellForRowAtIndexPath:swipedIndexPath];
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.changeX = swipeLocation.x;
}
if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
CGFloat maxRight = 100; // change this to increase distance of hit point
CGFloat newX = swipeLocation.x - self.changeX; // relative from first touch location
if (newX <= 0) newX = 0;
if (newX >= maxRight) {
newX = maxRight;
NSLog(#"Hit the point where I'll allow delete");
}
swipedCell.frame = CGRectMake(newX, swipedCell.frame.origin.y, swipedCell.frame.size.width, swipedCell.frame.size.height);
}
if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
[UIView animateWithDuration:0.5 animations:^{
swipedCell.frame = CGRectMake(0, swipedCell.frame.origin.y, swipedCell.frame.size.width, swipedCell.frame.size.height);
}
completion:^(BOOL finished) {
//
}];
}
}

Don't call reloadData. It kills the animation.

Related

Scroll Table View with a cell that was selected with a long press

I have a table view that I would like to scroll when a selected a cell and "pop" it out and move it around the table view. The table view scrolls just fine and the long press to select a cell works, but I'm not able to scroll the table view when I have a cell selected.
I read up on UIGestureRecognizerDelegate but I'm not sure that is making a difference. I also call these method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Here is part of my code:
- (IBAction)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
//longPress.delegate = self;
CGPoint location = [longPress locationInView:self.personTableView];
NSIndexPath *indexPath = [self.personTableView indexPathForRowAtPoint:location];
static UIView *snapshot = nil; ///< A snapshot of the row user is moving.
static NSIndexPath *sourceIndexPath = nil; ///< Initial index path, where gesture begins.
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
sourceIndexPath = indexPath;
UITableViewCell *cell = [self.personTableView cellForRowAtIndexPath:indexPath];
// capture the color of the cell before blacking out
savedTextColor = cell.detailTextLabel.textColor;
// Take a snapshot of the selected row using helper method.
snapshot = [self customSnapshotFromView:cell];
// Add the snapshot as subview, centered at cell's center...
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.0;
[self.personTableView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
// Offset for gesture location.
center.y = location.y;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
// Black out.
cell.detailTextLabel.alpha = 0;
cell.textLabel.alpha = 0;
} completion:nil];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.y = location.y;
snapshot.center = center;
// Is destination valid and is it different from source?
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
// ... update data source.
[dataSingelton saveUpdatedPersonList:(int)indexPath.row sourceIndex:(int)sourceIndexPath.row];
//[[dataSingelton mutableDataArray] exchangeObjectAtIndex:indexPath.row withObjectAtIndex:sourceIndexPath.row];
// ... move the rows.
[self.personTableView moveRowAtIndexPath:sourceIndexPath toIndexPath:indexPath];
// ... and update source so it is in sync with UI changes.
sourceIndexPath = indexPath;
}
break;
}
default: {
// Clean up.
UITableViewCell *cell = [self.personTableView cellForRowAtIndexPath:sourceIndexPath];
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.1;
cell.detailTextLabel.alpha = 1;
cell.textLabel.alpha = 1;
} completion:^(BOOL finished) {
[snapshot removeFromSuperview];
snapshot = nil;
cell.hidden = NO;
}];
sourceIndexPath = nil;
break;
}
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (UIView *)customSnapshotFromView:(UIView *)inputView {
UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:YES];
snapshot.alpha = 0.1;
snapshot.layer.masksToBounds = NO;
snapshot.layer.cornerRadius = 0.0;
snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
snapshot.layer.shadowRadius = 5.0;
snapshot.layer.shadowOpacity = 0.4;
return snapshot;
}
When I set the delegate to self like this:
longPress.delegate = self;
The able view scrolls, but then my selected cell doesn't move. What am I missing to make this work?
* Updated *
After doing some more reading/research I think in order for me to achieve the effect I want (the scrolling effect on the TableView in Apple's weather app) I think that my long press gesture will also have to handle the scrolling of my table view. The default scrolling starts to scroll the entire table instantly, where as I need the table to scroll only when the long pressed cell is at the top or the bottom of my TableView. Am I correct in thinking like this? I've been trying to find an example to break down and study, but haven't had any luck finding anything like that.
** Updated **
So I'm trying to figure out how to trigger the scroll up timer when the user drags a cell up towards the top. The problem I'm having right now is figuring out when that cell is close to the top, when its at the very top of the tableview, thats easy I could use something like tableViewHight + 100, but this code wont work when the tableview is scrolled down. Here is what I have so far:
NSLog(#"This is table view hight %.2f", tableViewHight);
NSLog(#"This is content offset %f", (personTableView.contentOffset.y));
NSLog(#"Should be 100 place holder %.2f", (tableViewHight - tableViewHight) + personTableView.contentOffset.y + 100);
NSLog(#"location y %f", location.y);
// gets called repeatedly
// Step 1: check to see if we need to start scrolling
if (location.y < tableViewHight - tableViewHight + 100) {
// ** top **
// Step 2: make sure the current y postoin isn't greater then the previous y postion
if (location. y <= originalLocation.y) {
// NSLog(#"This is the offset y %f", self.personTableView.contentOffset.y);
// Step 4: check to see if we have a timer and if not create one
[self startTimer];
}
else{
//Step 3: they started to scroll up so end the timer
[self endTimer];
}
}
else if(location.y > tableViewHight - 100)
{
// ** bottom **
// Step 2: make sure the current y postoin isn't less then the previous y postion
if (location. y >= originalLocation.y) {
NSLog(#"its less then 0");
if (!timer) {
// Step 4: check to see if we have a timer and if not create one
[self startTimer];
}
}
else{
//Step 3: they started to scroll up so end the timer
[self endTimer];
}
}
else{
// ** middle **
// Step 2: check to see if we have a timer and if so destory it
[self endTimer];
}
Basically I want to trigger that if statement when the cell is less then 100px from the hight of the scrollable table view.
From the UILongPressGestureRecognizer you can get the current y position of the touch. From that you can calculate how close to the bottom or top of the UITableViewthe touch is. If you determine that it's close enough to begin scrolling, then start a repeating NSTimer that increments or decrements the contentOffset of your UITableView by 1 pixel, scrolling your table. Change how fast it scrolls by changing the timeInterval of the timer.
When the UILongPressGestureRecognizer ends, or if the y position of the touch moves outside of the scrolling zone, then invalidate your timer, which will stop the scrolling.
If you felt adventurous you could also increase the speed of the scrolling dynamically as the user gets closer to the edge of the UITableView by changing the timeInterval as the user moves their touch up or down, as that is generally how this type of functionality works.
Good luck!

The UICollectionView "swipe-away" in iOS7 app manager?

On any 2014+ iPhone or iPad, double-click the home button to see the "app manager"
This is a left-right UICollectionView BUT it has a "swipe-away" gesture .. swipe up. How is it done? It's not so easy to "remove" a cell from a UICollectionView.
Footnote for googlers .. for the general problem of "peeling off", "tearing away", one cell from a collection view, here's a full tidy explanation: https://stackoverflow.com/a/24339705/294884 Hope it helps someone.
It can be much simpler than the comments on your question are suggesting.
Your cell should contain a view (the thing that you're going to drag off) and you add a UIPanGestureRecognizer to that view.
In the gesture's action method, you move the view up or down, and when it gets far enough off that you want to delete it, you just animate it off. There are plenty of questions here dealing with this part.
This leaves a gap in your collection and now you need to move things around. It turns out this is quite simple:
[_collectionView performBatchUpdates:^{
[_collectionView deleteItemsAtIndexPaths:#[indexPath]];
} completion:^(BOOL finished) {
// you might want to remove the data from the data source here so the view doesn't come back to life when the collection view is reloaded.
}];
The stuff to the right of the removed cell slides over and we're all good.
Another problem to get over: making sure your gesture recognizer and the collection view's one play nice together. Thankfully, that's not too tricky either.
[_collectionView.panGestureRecognizer requireGestureRecognizerToFail:pgr]; //where pgr is the recognizer you made for dragging the view off
This means in order for the collection view's pan gesture to do its thing, your one has to fail. So you'll want to set yours up so that it only works when panning up and down, and let the collection view still do its thing for left to right pans. In your gesture recognizers's delegate, implement the following method which simply checks if you're moving more on the x-axis or y-axis.
-(BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint translation =[gestureRecognizer translationInView:self.view];
return(translation.x * translation.x > translation.y * translation.y);
}
I was looking for this functionality and using #mbehan suggestion i faked this functionality using UICollectionView.
What i did is i added a view of smaller size on a collection cell(Transparent background) and added a single pan gesture on CollectionView(not on each cell) then on pan gesture i move the view and it looks like the cell is moving. After view reaches some point i first hide it and then deletes the collection view cell.
Cell Hierarchy : collectionViewCell -> View(tag value==2) -> UILabel(tag Value == 1)
Label is just used for placeholder purpose.
i am posting my code below:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = (UICollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"Cards" forIndexPath:indexPath];
UILabel *lblNumber = (UILabel*)[cell.contentView viewWithTag:1];
UIView *viewTouch = (UIView*)[cell.contentView viewWithTag:2];
[viewTouch setHidden:NO];
[lblNumber setText:arrCards[indexPath.row]];
return cell;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 50, 0, 30);
}
-(BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
if([gestureRecognizer isEqual:panGesture]) {
CGPoint point = [(UIPanGestureRecognizer*)gestureRecognizer translationInView:collectionView_];
if(point.x != 0) { //adjust this condition if you want some leniency on the X axis
//The translation was on the X axis, i.e. right/left,
//so this gesture recognizer shouldn't do anything about it
return NO;
}
}
return YES;
}
- (IBAction)panGestureCalled:(UIPanGestureRecognizer *)sender {
yFromCenter = [sender translationInView:collectionView_].y; //%%% positive for up, negative for down
UIView *view = sender.view;
CGPoint location = [view.superview convertPoint:view.center toView:collectionView_];
NSIndexPath *indexPath = [collectionView_ indexPathForItemAtPoint:location];
UICollectionViewCell *cell = [collectionView_ cellForItemAtIndexPath:indexPath];
UIView *touchView = (UIView*)[cell.contentView viewWithTag:2];
switch (sender.state) {
case UIGestureRecognizerStateBegan:{
originalPoint = touchView.center;
break;
};
case UIGestureRecognizerStateChanged:{
touchView.center = CGPointMake(originalPoint.x , originalPoint.y + yFromCenter);
break;
};
//%%% let go of the card
case UIGestureRecognizerStateEnded: {
CGFloat velocityY = (0.2*[(UIPanGestureRecognizer*)sender velocityInView:collectionView_].y);
if (velocityY < -30 && yFromCenter<0) {
[self hideView:touchView withDuration:0.2 andIndexPath:indexPath];
}else if ((yFromCenter< 0 && yFromCenter > -200) || yFromCenter > 0){
CGFloat animationDuration = (ABS(velocityY)*.0002)+.2;
[self resettleViewToOriginalPosition:touchView andDuration:animationDuration];
}else
[self hideView:touchView withDuration:0.2 andIndexPath:indexPath];
};
break;
case UIGestureRecognizerStatePossible:break;
case UIGestureRecognizerStateCancelled:break;
case UIGestureRecognizerStateFailed:break;
}
}
-(void)resettleViewToOriginalPosition:(UIView*)view andDuration:(float)duration{
[UIView animateWithDuration:duration
delay:0.0f
options: UIViewAnimationOptionCurveEaseOut
animations:^
{
[view setCenter:originalPoint];
}
completion:^(BOOL finished)
{
}];
}
- (void)hideView:(UIView*)view withDuration:(float)duration andIndexPath:(NSIndexPath*)indexPath
{
[UIView animateWithDuration:duration
delay:0.0f
options: UIViewAnimationOptionCurveEaseOut
animations:^
{
CGRect frame = view.frame;
frame.origin.y = -300;
view.frame = frame;
}
completion:^(BOOL finished)
{
[view setHidden:YES];
CGRect frame = view.frame;
frame.origin.y = 39;
view.frame = frame;
NSLog(#"View is hidden.");
[arrCards removeObjectAtIndex:indexPath.row];
[collectionView_ performBatchUpdates:^{
[collectionView_ deleteItemsAtIndexPaths:#[indexPath]];
} completion:^(BOOL finished) {
// you might want to remove the data from the data source here so the view doesn't come back to life when the collection view is reloaded.
}];
}];
}
and keep pagingEnabled of CollectionView to NO and then it should be good to go.

How do I use two actions in a UIPanGestureRecognizer?

I am working with two subviews. Each will be unique and have it's own "action".
Subview 1 = User can drag around the view, rotate, and zoom it
Subview 2 = When user moves finger across their screen an image is added at each point their finger touches.
I have both of these completed by using UIPanGestureRecognizer. My question is, how can I separate these two actions? I want to be able to add one subview, do what is required, and then when I add the other subview, prevent the previous actions from occurring.
Here is what I have tried, this is done in my panGesture method:
for (UIView * subview in imageView.subviews)
{
if ([subview isKindOfClass:[UIImageView class]])
{
if (subview == _aImageView)
{
CGPoint translation = [panRecognizer translationInView:self.view];
CGPoint imageViewPosition = _aImageView.center;
imageViewPosition.x += translation.x;
imageViewPosition.y += translation.y;
_aImageView.center = imageViewPosition;
[panRecognizer setTranslation:CGPointZero inView:self.view];
}
else if (subview == _bImageView)
{
currentTouch = [panRecognizer locationInView:self.view];
CGFloat distance = [self distanceFromPoint:currentTouch ToPoint:prev_touchPoint];
accumulatedDistance += distance;
CGFloat fixedDistance = 60;
if ([self distanceFromPoint:currentTouch ToPoint:prev_touchPoint] > fixedDistance)
{
[self addbImage];
prev_touchPoint = currentTouch;
}
}
}
}
If you want different gesture recognition in two different views, put separate recognizers on each view.
Usually, you want to have your view controller own and manage gesture recognizers, e.g.
- (void)viewDidLoad {
self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
self.panGesture.delegate = self;
[self.viewX addGestureRecognizer:self.panGesture];
// repeat with other recognisers...
}
Note that setting your controller as delegate of the gestureRecognizer is important: this enables you to handle the following delegate method from the view controller (which was the main question):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// handle your logic, which gestureRecognizer should proceed...
return NO;
}
The handler method is the same is this example, but you can set up your own handlers as you like:
- (void)handleGesture:(UIGestureRecognizer*)gestureRecognizer {
// handle gesture (usually sorted by state), e.g.
// if(gesture.state == UIGestureRecognizerStateEnded) { ... }
}

hide UITableView header section

I'm using the following code to hide a view and the space taken by the view based on a condition in viewWillAppear:
- (void)viewWillAppear:(BOOL)animated {
Data* data = [Data shared];
if (data.something == 0) {
CGRect frame = self.tableView.tableHeaderView.frame;
frame.size.height = 0;
self.tableView.tableHeaderView.frame = frame;
self.tableView.tableHeaderView.hidden = YES;
} else {
CGRect frame = self.tableView.tableHeaderView.frame;
frame.size.height = 44;
self.tableView.tableHeaderView.frame = frame;
self.tableView.tableHeaderView.hidden = NO;
}
}
The above code works, but I'm pretty sure that is not the right way to do that. I tried to set the tableHeaderView to nil, but once the code is called, the headerView is gone until the UITableView is destroyed (I think I can fix it using a IBOutlet to the tableHeader, but doesn't sounds right too.
UPDATE1: another try, but the code doesn't work:
- (CGFloat)tableView:(UITableView*)tableView heightForHeaderInSection:(NSInteger)section {
self.tableView.tableHeaderView.hidden = YES;
return 0;
}
The data source method tableView:heightForHeaderInSection: actually has nothing to do with the view that is associated with the table view's tableViewHeader property. There are two different types of headers here, the one header at the top of the tableView, in which can be placed things like a search bar, and the multiple headers that can be made to occur one per section within the table view.
To my knowledge, the tableViewHeader view is typically configured in the nib file, and I don't know that the table view calls any data source methods that allow any configuration for it, so you would have to do it manually. Frankly, if your code works, that would be a good way to do it. Hiding it would make the table view still act as if it's there...removing it entirely makes it so you can't get it back because it gets deallocated.
(However, as you said, you could use an IBOutlet pointing to the header view, as long as you make it a strong reference, and then you could somehow reinsert it into the table later. ...Hm, although the mechanics of how you add a view into the table view's scroll view, and position it correctly, is probably just annoying.)
My only suggestion would be animating the frame height to zero so you get a nice transition effect, something like animateWithDuration. But yeah, I would say you have the best method figured out already.
EDIT:
Code, you say? I take that as a challenge :)
- (void)setTableViewHeaderHidden:(BOOL)hide
{
// Don't want to muck things up if we are mid an animation.
if (self.isAnimatingHeader) {
return;
}
// This is our IBOutlet property, I am just saving a bit of typing.
UIView *theHeader = self.theHeaderView;
if (hide) {
// Save the original height into the tag, should only be done once.
if (!theHeader.tag) {
theHeader.tag = theHeader.frame.size.height;
}
// Transform and hide
if (theHeader.frame.size.height > 0) {
self.isAnimatingHeader = YES;
// New frame...
CGRect frame = theHeader.frame;
frame.size.height = 0;
// Figure out some offsets here so we prevent jumping...
CGPoint originalOffset = self.tableView.contentOffset;
CGPoint animOffset = originalOffset;
animOffset.y += MAX(0, theHeader.tag - animOffset.y);
CGPoint newOffset = originalOffset;
newOffset.y = MAX(0, newOffset.y - theHeader.tag);
// Perform the animation
[UIView animateWithDuration:0.35
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
theHeader.frame = frame;
self.tableView.contentOffset = animOffset;
}
completion:^(BOOL finished){
if (finished) {
// Hide the header
self.tableView.tableHeaderView = nil;
theHeader.hidden = YES;
// Shift the content offset so we don't get a jump
self.tableView.contentOffset = newOffset;
// Done animating.
self.isAnimatingHeader = NO;
}
}
];
}
} else {
// Show and transform
if (theHeader.frame.size.height < theHeader.tag) {
self.isAnimatingHeader = YES;
// Set the frame to the original before we transform, so that the tableview corrects the cell positions when we re-add it.
CGRect originalFrame = theHeader.frame;
originalFrame.size.height = theHeader.tag;
theHeader.frame = originalFrame;
// Show before we transform so that you can see it happen
self.tableView.tableHeaderView = theHeader;
theHeader.hidden = NO;
// Figure out some offsets so we don't get the table jumping...
CGPoint originalOffset = self.tableView.contentOffset;
CGPoint startOffset = originalOffset;
startOffset.y += theHeader.tag;
self.tableView.contentOffset = startOffset; // Correct for the view insertion right off the bat
// Now, I don't know if you want the top header to animate in or not. If you think about it, you only *need* to animate the header *out* because the user might be looking at it. I figure only animate it in if the user is already scrolled to the top, but hey, this is open to customization and personal preference.
if (self.animateInTopHeader && originalOffset.y == 0) {
CGPoint animOffset = originalOffset;
// Perform the animation
[UIView animateWithDuration:0.35
delay:0.0
options: UIViewAnimationCurveEaseIn
animations:^{
self.tableView.contentOffset = animOffset;
}
completion:^(BOOL finished){
// Done animating.
self.isAnimatingHeader = NO;
}
];
} else {
self.isAnimatingHeader = NO;
}
}
}
}
Built this in the table view template that comes with Xcode. Just to throw it together I used a UILongPressGestureRecognizer with the selector outlet pointing to this method:
- (IBAction)longPress:(UIGestureRecognizer *)sender
{
if (sender.state != UIGestureRecognizerStateBegan) {
return;
}
if (self.hidingHeader) {
self.hidingHeader = NO;
[self setTableViewHeaderHidden:NO];
} else {
self.hidingHeader = YES;
[self setTableViewHeaderHidden:YES];
}
}
And, I added these to my header:
#property (strong, nonatomic) IBOutlet UIView *theHeaderView;
#property (nonatomic) BOOL hidingHeader;
#property (nonatomic) BOOL isAnimatingHeader;
#property (nonatomic) BOOL animateInTopHeader;
- (IBAction)longPress:(id)sender;
Anyway, it works great. What I did discover is that you definitely have to nil out the table view's reference to the header view or it doesn't go away, and the table view will shift the cells' position based on the height of the frame of the header when it is assigned back into its header property. Additionally, you do have to maintain a strong reference via your IBOutlet to the header or it gets thrown away when you nil out the table view's reference to it.
Cheers.
Instead of,
if (1 == 1) {
CGRect frame = self.viewHeader.frame;
frame.size.height = 0;
self.viewHeader.frame = frame;
self.viewHeader.hidden = YES;
}
use it as,
if (1 == 1) {
self.viewHeader.hidden = YES;
}
If you do not want the view anymore instead of just hiding, use [self.viewHeader removeFromSuperview];
And if you want to add it after removing [self.view addSubview:self.viewHeader]; All these depends on your requirement.
Update:
for eg:-
if (data.something == 0) {
//set frame1 as frame without tableHeaderView
self.tableView.frame = frame1;
self.tableView.tableHeaderView.hidden = YES;
} else {
//set frame2 as frame with tableHeaderView
self.tableView.frame = frame2;
self.tableView.tableHeaderView.hidden = NO;
}
or,
if (data.something == 0) {
//set frame1 as frame without tableHeaderView
self.tableView.frame = frame1;
self.tableView.tableHeaderView = nil;
} else {
//set frame2 as frame with tableHeaderView
self.tableView.frame = frame2;
self.tableView.tableHeaderView = self.headerView; //assuming that self.headerview is the tableHeaderView created while creating the tableview
}
Update2: Here is a very simple version of animation block.
if (data.something == 0) {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationCurveEaseOut
animations:^{
//set frame1 as frame without tableHeaderView
self.tableView.frame = frame1;
self.tableView.tableHeaderView.hidden = YES; // or self.tableView.tableHeaderView = nil;
}
completion:^(BOOL finished){
//if required keep self.tableView.frame = frame1;
}
];
} else {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationCurveEaseIn
animations:^{
//set frame2 as frame with tableHeaderView
self.tableView.frame = frame2;
self.tableView.tableHeaderView.hidden = NO;// or self.tableView.tableHeaderView = self.headerView;
}
completion:^(BOOL finished){
//if required keep self.tableView.frame = frame2;
}];
}

Tab-like-thing with tab-switch animation in a section of UITableView

I have an UITableView with sections in my iPhone application where it looks as something similar to viewing a contact details in standard contacts app. In one of the sections I have a UISegmentedControl set as a header of the section and the items in that section depend on what segment is selected in the segmented control. What I wanted to do is to have animations shifting the rows of one tab out of the view to one side and shifting the rows of another tab in from the other side. I have managed to achieve this by inserting/deleting the respective rows with default UITableView's animations for that but it looks a bit weird when the number of rows in one tab is different from the other one. Also the code for that is quite nasty.
Here's the code I have (simplified a bit):
[self.tableView beginUpdates];
UITableViewRowAnimation delAnim = UITableViewRowAnimationRight;
UITableViewRowAnimation insAnim = UITableViewRowAnimationLeft;
NSUInteger numdel = [self.list count];
NSUInteger numins = [newlist count];
NSMutableArray* indices = [NSMutableArray arrayWithCapacity: numdel];
for (NSUInteger i = 0; i < numins; i++)
{
[indices addObject: [NSIndexPath indexPathForRow: i inSection: SECT]];
}
[self.tableView insertRowsAtIndexPaths: indices withRowAnimation: insAnim];
[indices removeAllObjects];
for (NSUInteger i = 0; i < numdel; i++)
{
[indices addObject: [NSIndexPath indexPathForRow: i inSection: SECT]];
}
[self.tableView deleteRowsAtIndexPaths: indices withRowAnimation: delAnim];
self.list = newlist;
[self.tableView endUpdates];
I'm fairly new to Objective C and Cocoa, so any advices are very welcome.
You could try using a separate table view which you animate in directly. The snippet here is just typed off-the-cuff, so might need some work, but it should give you some pointers at least:
- (void) switchToNewTableFromRight
{
UITableView * newTableView = [[UITableView alloc] initWithFrame: self.tableView.frame style: self.tableView.style];
// put it off to the right of the existing table
CGRect frame = newTableView.frame;
frame.origin.x += frame.size.width;
newTableView.frame = frame;
// set data for new table
// you should ensure you're setup to supply data for the new table here, btw
newTableView.delegate = self;
newTableView.dataSource = self;
[newTableView reloadData];
// add to parent of current table view at this (offscreen) location
[self.tableView.superview addSubview: newTableView];
// now we animate
[UIView beginAnimations: #"TableFromRight" context: newTableView];
// set the function it should call when the animation completes
[UIView setAnimationDelegate: self];
[UIView setAnimationDidStopSelector: #selector(animation:finished:context:)];
// set new table's frame to current table's frame
newTableView.frame = self.tableView.frame;
// set current table's frame to be offscreen to the left
frame = self.tableView.frame;
frame.origin.x -= frame.size.width;
self.tableView.frame = frame;
// commit the animations to start them going
[UIView commitAnimations];
}
- (void) animation: (NSString *) animationID finished: (BOOL) finished context: (void *) context
{
// could be a good idea to check that finished == YES here
UITableView * newTableView = (UITableView *) context;
self.tableView = newTableView;
// newTableView has been inited but not autoreleased, etc.
// now the controller (self) owns it, so release that first reference
[newTableView release];
}
The idea here is that you setup the new table (making it the same size as the existing one), place it offscreen to the right of the existing table, and then animate the movement of both tables left by their width. This way the existing table will shift offscreen while the new one will shift onscreen. When the animation completes it will call the supplied method, giving you the opportunity to make the new table view the official table view.
Another option would be to use a flip transition, which might go something like this:
// setup new table
UITableView * newTableView = [[UITableView alloc] initWithFrame: self.tableView.frame style: self.tableView.style];
newTableView.delegate = self;
newTableView.dataSource = self;
[newTableView reloadData];
[UIView beginAnimations: nil context: NULL];
[UIView setAnimationDuration: 1.0];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView: self.tableView.superview cache: YES];
// generally here you'd remove the old view and add the new view
// I'm *assuming* that UITableViewController's -setTableView: will do the same thing
self.tableView = newTableView;
[UIView commitAnimations];
Hopefully one of those will have the desired effect.