animating UITableViewHeader to grow/shrink - ios

Im very new to iOS and am trying to figure out how I can animate the tableView header to appear like its sliding down from the top of the view, stay there for 3 seconds, then slide back up and disappear.
I havent done any animation of any kind before so I could really use some help. I dont know where to start. Ive looked at other answers on here but cant seem to figure out how to do it.
ive got my table view appearing just fine with this code in my viewDidLoad of my UITableViewController class
self.headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320,15)];
self.headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 15)];
self.headerLabel.textAlignment = NSTextAlignmentCenter;
self.headerLabel.text = #"some text";
[self.headerView addSubview:self.headerLabel];
self.tableView.tableHeaderView = self.headerView;
self.tableView.tableHeaderView.frame = CGRectMake(0, 0, 320, 15);
I assume I need to animate the height of my frame.
edit: I found this code which seems like it should work, however it crashes my app when the animation runs
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[UIView animateWithDuration:3.0f
delay:0.0f
options: UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.headerView.frame = CGRectMake(0.0f, 0.0f, 320.f, 0.0f);
}
completion:^(BOOL finished) {
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}];
}

I don't know if you can animate a table view's header view like you want to. With a complex UI object like a Table view Apple tends to treat the views that make up the object as private, and bad things tend to happen when you try to manipulate the view hierarch.
If you're not using auto-layout and you want to animate another view that is part of YOUR view hierarchy then your code might be as simple as something like this:
CGPoint center = myView.center;
CGFloat myViewBottom = myView.frame.origin.y + myView.frame.origin.size.height;
//Move the view above the top of it's superview before starting the animation.
center.y -= myViewBottom;
//Animate the view down into position
[UIView animateWithDuration: .2
animations:
^{
myView.center += myViewBottom;
};
completion:
^{
//As the completion block for the first animation, trigger another
//animation to animate the view away again. This time delay
//for 3 second before the 2nd animation.
[UIView animateWithDuration: .2
delay: 3.0
options: 0
animations:
^{
myView.center -= myViewBottom;
};
completion: nil
}
];

Try to use beginUpdates and endUpdates, here is an example:
in your header
#property NSInteger newOffset;
implementation:
- (void) colapseHeader
{
_newOffset = 50;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 50 + _newOffset;
}
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_newOffset = 100;
[self.tableView beginUpdates];
[self.tableView endUpdates];
[self performSelector:#selector(colapseHeader) withObject:nil afterDelay:3];
}

Related

Getting black screen when app returning from background

I'm dealing with a tricky bug on my app. I have a chat view controller where I deal with keyboard showing and hiding. But the weird part is when putting the app on background.
This is the view controller before going to background (tapping the home button)
And this is the app when returning from background
If I put the app on background using the home button, the black screen only appears for a second, but if I block the phone and then unlock, the black screen keeps there.
This is how I deal with the keyboard
- (void)keyboardWillShowWithRect:(CGRect)rect
{
//We adjust inverted insets because tableview will be inverted
CGFloat displacementDistance = rect.size.height - self.composeBarView.bounds.size.height;
self.tableView.contentInset = UIEdgeInsetsMake(self.composeBarView.bounds.size.height, 0, displacementDistance, 0);
[self.view setFrame:CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y - displacementDistance, self.view.frame.size.width, self.view.frame.size.height)];
}
- (void)keyboardWillDismissWithRect:(CGRect)rect
{
//We adjust inverted insets because tableview will be inverted
CGFloat displacementDistance = rect.size.height - self.composeBarView.bounds.size.height;
self.tableView.contentInset = UIEdgeInsetsMake(self.composeBarView.bounds.size.height, 0, 0, 0);
[self.view setFrame:CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y + displacementDistance, self.view.frame.size.width, self.view.frame.size.height)];
}
The tableView used to display the messages is inverted in order to display the first message at the bottom. Here's how I do it:
- (void)setupTableView
{
self.tableView.delegate = self;
self.tableView.dataSource = self;
//We adjust inverted insets because tableview will be inverted
self.tableView.contentInset = UIEdgeInsetsMake(self.composeBarView.bounds.size.height, 0, 0, 0);
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 500.0;
self.tableView.sectionFooterHeight = 30.0;
self.tableView.showsVerticalScrollIndicator = NO;
[self.tableView registerNib:[UINib nibWithNibName:#"MessagesDateHeader" bundle:nil] forHeaderFooterViewReuseIdentifier:#"MessagesDateHeader"];
// Table View is inverted so we display last messages at the bottom instead of top
self.tableView.transform = CGAffineTransformMakeRotation(-M_PI);
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapOnTableView)];
tap.numberOfTapsRequired = 1;
[self.tableView addGestureRecognizer:tap];
}
Also, I have self.definesPresentationContext = YES; set on viewDidLoad
Anyone can give me a hint of what's happening?
Thanks
I used to have a similar problem. Issue was the resizing of the tableView when keyboard appears/disappears based on its frame.
What I did was set a constraint between the bottom of the tableView and the superView and then change its constant with the appearance/disappearance of the keyboard.
Instead of relying on keyboardWillShowWithRect: and keyboardWillDismissWithRect: you can use listen to UIKeyboardWillChangeFrameNotification and handle it like follows
- (void) keyboardWillChangeFrame:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardFrameEnd = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardFrameEnd = [self.view convertRect:keyboardFrameEnd toView:nil];
[UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]
delay:0.f
options:[userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue] << 16
animations:^{
self.bottomConstraint.constant = CGRectGetHeight(self.view.frame) - keyboardFrameEnd.origin.y;
[self.view layoutIfNeeded];
} completion:nil];
}
[self.view layoutIfNeeded] is used so that view shows the whole animation

Animating custom UIView from off screen make its subviews not responding

I created a small custom UIView with a UILabel and a UIButton, this custom view is a banner to display at the top of the current view controller.
I load the view layout from a nib file and use a method from the custom view to display it with an animation, and the view will hide after a specific amount of time. Like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 delay:self.duration options:0 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}];
}
I set the action for the button inside the banner with this
[self.actionButton addTarget:self
action:#selector(executeActionBlock)
forControlEvents:UIControlEventTouchUpInside];
After animation showing the banner, and before is hidden, no matter how many times I tap on the button, the executeActionBlock method is never called.
I made a test setting the initial frame of the banner to origin (0, 0) and without animation and then the button worked fine. So, I don't know if a problem of the animation or because the original frame of the banner is in a non visible position. BTW, is important for the banner to not be visible because on the app is showing from under the navigation bar.
Thanks
You've written your code so that you run a series of animations, where the view is on-screen during the delay of the last animation. By default user interaction is disabled on views while they are being animated. Try passing UIViewAnimationOptionAllowUserInteraction in options for all your nested animations.
https://developer.apple.com/reference/uikit/uiviewanimationoptions/uiviewanimationoptionallowuserinteraction?language=objc
Well, it seems I should keep looking more because I found this question with the same problem. I tried the solution there and now is working.
The problem is that linking animations is not a good idea for user interaction so I used a NSTimer instead, like this.
- (void)displayBannerInViewController:(UIViewController *)vc
{
CGFloat originY = 0;
if (vc.navigationController != nil) {
originY += 20 + vc.navigationController.navigationBar.bounds.size.height - self.bounds.size.height;
}
self.frame = CGRectMake(0,
originY,
[UIScreen mainScreen].bounds.size.width,
self.bounds.size.height);
if (vc.navigationController != nil) {
[vc.navigationController.view insertSubview:self atIndex:1];
} else {
[vc.view.window insertSubview:self atIndex:1];
}
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, self.bounds.size.height);
} completion:^(BOOL finished) {
[NSTimer scheduledTimerWithTimeInterval:self.duration target:self selector:#selector(hideBanner) userInfo:nil repeats:NO];
}];
}
- (void)hideBanner
{
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectOffset(self.frame, 0, -self.bounds.size.height);
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}
And now works like a charm.

Bounce Animation on UITableViewCell

When the UITableView loads I want its first cell to bounce from the Right side, so It indicates the User that they can swipe right to delete a cell.
How can I do this ?
My code so far:
note: In the following code I am just making the cell to blink, but what I actually want is the cell to bounce.
-(void) tableView:(UITableView *) tableView willDisplayCell:(UITableViewCell *) cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
//row number on which you want to animate your view
//row number could be either 0 or 1 as you are creating two cells
//suppose you want to animate view on cell at 0 index
if(indexPath.row == 0) //check for the 0th index cell
{
// access the view which you want to animate from it's tag
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:1];
UIView *myView = [self.tableView cellForRowAtIndexPath:indexPath];
NSLog(#"row %ld",(long)indexPath.row);
// apply animation on the accessed view
[UIView animateWithDuration:5
delay:2
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut animations:^
{
[myView setAlpha:0.0];
} completion:^(BOOL finished)
{
[myView setAlpha:1.0];
}];
}
}
If I understand this correctly, you wish to bounce the cell from left to right?
Get a reference to the cell's contentView:
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:1];
UIView *contentView = [self.tableView cellForRowAtIndexPath:indexPath].contentView;
Now there are many ways to 'bounce' this contentView. One way would be to use an animation block like this:
CGRect original = contentView.frame;
CGRect bounce_offset = original;
original.origin.x -= 100.0f;
What we do here, is remember the original frame, and decide how far we want our bounce to reach in the animation block. Then we can animate for example doing something like this:
[UIView animateWithDuration:0.5f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
contentView.frame = bounce_offset;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0f delay:0.0f usingSpringWithDamping:0.75f initialSpringVelocity:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{
contentView.frame = original;
} completion:^(BOOL finished) {
}];
}]
You could also use autoreverse options, this 'a' way to do it though. Let me know what your thoughts are!

UISegementedControl reverts back to original frame after touch

Every time I click on my UISegementedControl it snaps back to its original frame. I can see it barely through my translucent toolbar.
I have a UIViewController with a UITableView, and UIToolBar like this:
There is a UISegmentedControl hidden just below the table view, behind the toolbar:
The Filter button calls the 'onFilterButtonPressed' method
- (IBAction)onFilterButtonPressed:(id)sender
{
if(self.filterBar.hidden){
[self showFilterBar];
} else {
[self hideFilterBar];
}
}
- (void)hideFilterBar
{
CGRect filterBarFrame = CGRectMake(0, self.view.frame.size.height+(self.filterBar.frame.size.height+1), self.filterBar.frame.size.width, self.filterBar.frame.size.height);
CGRect tableViewFrame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y,self.tableView.frame.size.width, self.tableView.frame.size.height+(self.filterBar.frame.size.height+1));
[UIView animateWithDuration:0.3 animations:^{
[self.filterBar setFrame:filterBarFrame];
[self.tableView setFrame:tableViewFrame];
} completion:^(BOOL finished) {
self.filterBar.hidden = YES;
}];
}
- (void)showFilterBar
{
CGRect filterBarFrame = CGRectMake(0, self.view.frame.size.height-(self.filterBar.frame.size.height+1), self.filterBar.frame.size.width, self.filterBar.frame.size.height);
CGRect tableViewFrame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y,self.tableView.frame.size.width, self.tableView.frame.size.height-(self.filterBar.frame.size.height+1));
self.filterBar.hidden = NO;
[UIView animateWithDuration:0.3 animations:^{
[self.tableView setFrame:tableViewFrame];
[self.filterBar setFrame:filterBarFrame];
}];
}
This is because of auto layout. With that turned on (which it is by default), you should do any positioning or resizing of views by modifying constraints, not setting frames.

UIView animation completes immediately

I've been struggling with this for a long and I just can't find out what's wrong here. I actually do have an idea - I think that somehow the view I am adding and trying to animate is not yet included to the view hierarchy and that's why core animation isn't wasting cycles to animate. I did some other animations in the project successfully but I am running them in the ViewController's viewDidLoad method. Here is my code:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.opaque = NO;
self.backgroundColor = [UIColor colorWithWhite: 1.0 alpha: 0.7];
[self addTrack];
}
return self;
}
- (void)addTrack
{
//init the track here
UIView *track = [[UIView alloc] initWithFrame: CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
track.backgroundColor = [UIColor whiteColor];
[self addSubview: track];
//transform the track for its initial scale
float scaleFactorX = 0.01;
track.layer.anchorPoint = CGPointMake(0, 0);
track.layer.position = CGPointMake(0, 0);
track.transform = CGAffineTransformMakeScale(scaleFactorX, 1.0);
//animate the track here
[UIView animateWithDuration:8 delay:0.3 options:UIViewAnimationOptionCurveEaseOut
animations:^{
NSLog(#"animation is running.");
track.transform = CGAffineTransformIdentity;
}
completion:^(BOOL finished) {
NSLog(#"animation finished.");
}];
}
So the result is that the "track" is scaling to its original size immediately. I also tried to call addTrack inside the didMoveToSuperview method so I can make sure that the containing view is added to the view hierarchy but with the same result. I tried other animations inside that class like adding alpha animation to the containing view but with no success again ..
Try calling your animations after loading the view so that you can see it.
-(void)viewDidAppear:(BOOL)animated
{
[self addTrack];
}
You shouldn't have put animations in the init method to start with. Try calling them immediately after your view was actually displayed (added on a superview) and see what happens then.

Resources