I have a UITableView with custom cells. Naturally, in cellForRowAtIndexPath I try to dequeue an old cell and reuse it. Each cell has a hierarchy of autoresized views: I set the frame of the top view when I set its content in cellForRowAtIndexPath, and its subviews change their frames accordingly. Note that row height is dynamic too.
It all works nice when I just scroll the table (with the rows of different content and frames). But I also have a text field in the same view as this table, so I need to scroll the table's contents to compensate the keyboard being shown/hidden. I animate contentInset property for that:
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
NSTimeInterval animationDuration = [[aNotification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
UIViewAnimationCurve animationCurve = [[aNotification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue];
[UIView setAnimationCurve:animationCurve];
[UIView animateWithDuration:animationDuration animations:^{
messagesTable.contentInset = UIEdgeInsetsMake(0, 0, kHSTableBottomInset, 0);
messagesTable.scrollIndicatorInsets = UIEdgeInsetsZero;
}];
}
It works good as well, but there's an interesting glitch: when keyboard is hidden and contentInset is animated back to normal (kHSTableBottomInset is a small value to keep margin), table reloads the cells that will scroll from above to be displayed. The problem is that this reloading is done inside the animation block too! As a result, when I change the dequeued subview frame (specifically, width) in cellForRowAtIndexPath (which is called as a part or reloading), this change is animated too and it's visible as the cell scrolls down to view.
Can I avoid such behavior?
I finally found the solution: frame-setting code can be excluded from animation like this:
[CATransaction begin];
[CATransaction setDisableActions:YES];
mainDataView.frame = rect;
[CATransaction commit];
or
[UIView setAnimationsEnabled:NO];
mainDataView.frame = rect;
[UIView setAnimationsEnabled:YES];
Found the answer here: How can I exclude a piece of code inside a core animation block from being animated?
Solved this issue by setting clipsToBounds YES to tableview.
tableView.clipsToBounds = YES:
And if you still have issues, set clipsToBounds to footerview also.
Related
I have an app which intakes client details, built for iPad. When the user taps a UITextField towards the bottom half of the ViewController, the frame programatically shifts upwards so that the fields aren't hidden behind the keyboard. (I tried to implement a UIScrollView but just cannot seem to get it working.) The slight issue I'm having now is when the frame shifts up, you can vaguely see the black behind it. This isn't a huge issue because I have changed the animation time and the black background is barely visible, but I have a feeling there is a more elegant solution.
Here is my code to shift the frame:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
// Animate frame up if lower textfields are tapped
if (([textField isEqual:_emailField]) || ([textField isEqual:_dobField]) || ([textField isEqual:_niNumField])) {
[UIView beginAnimations:nil
context:NULL];
[UIView setAnimationDuration:0.35f];
CGRect frame = self.view.frame;
frame.origin.y = -210;
[self.view setFrame:frame];
[UIView commitAnimations];
} else {
// Return frame to original position if other textfields are tapped
[self dismissKeyboard];
}
return YES;
}
- (void)dismissKeyboard {
[UIView beginAnimations:nil
context:NULL];
[UIView setAnimationDuration:0.15f];
CGRect frame = self.view.frame;
frame.origin.y = 0;
[self.view setFrame:frame];
[UIView commitAnimations];
}
Here is a brief screenshot of what I'm trying to describe. Left picture is the ViewController normally, right picture is when the frame has been shifted upwards. You can see (vaguely) almost in line with the second row of characters is where the frame stops.
I realise this doesn't seem like an issue because it is barely visible at all, but I have had to speed up the animation hiding the keyboard or else the frame drags behind and the black background becomes visible. I am wondering: is there a way to change this colour? Or is that something we don't have access to? If anyone can suggest a more elegant method for what I am trying to do, I'd gladly take a better solution. Thanks in advance.
Your UIViewController's view is added to the main UIWindow. So you should be able to achieve what you want by changing the UIWindow's background color.
In the UIApplicationDelegate:
self.window.backgroundColor = [UIColor whiteColor];
However, what you're doing isn't the best way to solve the problem of the keyboard covering up the text fields. You should use a UIScrollView or a UITableView to manage this view and use content insets to shift the view up or down.
I have two IBOutlet variables connected to two instances of UIView in Interface Builder, called blankView and fadedBGView in my ViewController.
How can I set up this code so that the blankView instance will fade into fadedBGView, using the UIViewAnimationOptionTransitionCrossDissolve transition, and the two UIViews can simultaneously transform (move) from their current position (they have equal positions) to 0,0?
What's happening is the fade occurs, and then the fadedBGView view abruptly moves to 0,0.
In other words, I'd like to have the first UIView fade into the second, while simultaneously moving up the screen.
Hopefully this is clear enough to answer.
Best...SL
[UIView transitionFromView:blankView
toView:fadedBGView
duration:1.0
options:UIViewAnimationOptionTransitionCrossDissolve
completion:^(BOOL finished) {
[blankView removeFromSuperview];
}];
[UIView commitAnimations];
CGRect frame = fadedBGView.frame;
frame.origin.x = 0;
frame.origin.y = 0;
[UIView animateWithDuration:1.0 animations:^{
NSLog(#"UIView Transition/Move Called");
fadedBGView.frame = frame;
}];
iOS 4 and newer provides block-based animations, which your code is already using:
+[UIView animateWithDuration:animations:];
+[UIView animateWithDuration:animations:completion:];
Within the animation block, you can set multiple destination values. See Apple's UIView documentation for a reference to the animatable properties. So within one block, you can modify frames, and alpha (transparency) values of views. I'm not sure how the cross dissolve animation works, but if it's simply one view going from 0 to 1, and another view going from 1 to 0, that's easy to implement.
Instead of
UIViewAnimationOptionTransitionCrossDissolve
use
UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent
UIViewAnimationOptionAllowAnimatedContent option allows additional animations during transition. Make sure your fadedBGView has correct starting frame.
I am trying to move a UIView programmatically using the following method:
-(void)animatePlaybackLine: (int) currentBeat{
CGRect newFrame = CGRectMake(currentBeat*eighthNoteWidth, 0, eighthNoteWidth, 320);
[playbackLine setFrame:newFrame];
NSLog(#"Line animated to %i", currentBeat);
NSLog(#"New frame origin %f", playbackLine.frame.origin.x);
}
When the method is called, the NSLogs show that the variable currentBeat is incrementing as it should, and the frame of playbackLine (my UIView) appears to be moving as it should. However, the object on the screen doesn't move. I have also tried setting the bounds and the center instead of the frame, and all of them have similar results. I also tried using an animation instead of setting the frame, to no avail.
Is there something else I should be doing to make the image onscreen show the changing frame?
I'm guessing you're calling that method in a loop? iOS isn't going to redraw the screen until your code finishes executing. You can't loop like this and see iterative results.
To animate views, you should use the native support UIKit gives you. In particular, check out the Animations section in the UIView class reference.
Try this
[UIView animateWithDuration:0.3
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
datePickerView.frame = imageFrame;
}
completion:^(BOOL finished){
self.datePickerViewFlag = NO;
}];
I am using a custom controller for transitions (could not use navigation controller due to inherent cycles in project, which would allow the navigation controllers stack to grow unbounded [which I assume would cause memory issues]). I am emulating a navigation controllers sliding animation (when transitioning to new screens) using UIView animateWithDuration:animations:completion:
When a button triggering the transition is pressed, the frame of the new view I want to transition to is set to an offscreen position. In the animation for the transition (UIView animateWithDuration:animations:completion:), the view currently on screen has its frame set to an offscreen position, and the new view is set to an onscreen position.
This is inside my custom controller for transitions:
CGFloat windowWidth = self.mainView.frame.size.width;
CGFloat windowHeight = self.mainView.frame.size.height;
CGRect offScreenLeft = CGRectMake(-1*windowWidth, 0.0, windowWidth, windowHeight);
CGRect onScreen = self.mainView.frame;
CGRect offScreenRight = CGRectMake(windowWidth, 0.0, windowWidth, windowHeight);
if (direction == TransitionDirectionForward)
{
if (dragBackgroundOnscreen){
[self.mainView addSubview:self.backgroundView];
[self.mainView sendSubviewToBack:self.backgroundView];
self.backgroundView.frame = offScreenRight;
}
self.currentViewController.view.frame = offScreenRight;
[UIView animateWithDuration:0.65
animations:^{
oldViewController.view.frame = offScreenLeft;
if (dragBackgroundOffscreen)
self.backgroundView.frame = offScreenLeft;
else if (dragBackgroundOnscreen)
self.backgroundView.frame = onScreen;
self.currentViewController.view.frame = onScreen;
}
completion:^(BOOL finished){
[oldViewController.view removeFromSuperview];
if (dragBackgroundOffscreen)
[self.backgroundView removeFromSuperview];
[oldViewController willMoveToParentViewController:nil];
[oldViewController removeFromParentViewController];
[self.currentViewController didMoveToParentViewController:self];
}];
}
else if (direction == TransitionDirectionBackward)
{
if (dragBackgroundOnscreen){
[self.mainView addSubview:self.backgroundView];
[self.mainView sendSubviewToBack:self.backgroundView];
self.backgroundView.frame = offScreenLeft;
}
self.currentViewController.view.frame = offScreenLeft;
[UIView animateWithDuration:0.65
animations:^{
oldViewController.view.frame = offScreenRight;
if (dragBackgroundOffscreen)
self.backgroundView.frame = offScreenRight;
else if (dragBackgroundOnscreen)
self.backgroundView.frame = onScreen;
self.currentViewController.view.frame = onScreen;
}
completion:^(BOOL finished){
[oldViewController.view removeFromSuperview];
if (dragBackgroundOffscreen)
[self.backgroundView removeFromSuperview];
[oldViewController willMoveToParentViewController:nil];
[oldViewController removeFromParentViewController];
[self.currentViewController didMoveToParentViewController:self];
}];
}
I also want the background (self.backgroundView) to remain static unless moving to a screen that has its own background (i.e. I dont want the background to slide if the new views background is the same background).
I am using TransitionDirectionBackward and TransitionDirectionForward just to differentiate between sliding left and sliding right.
Everything works great, except when transitioning involves a GMGridView. the problem when the Gridviews frame is set to an offscreen frame its really setting its currently selected page's frame to that offscreen frame. Other pages of the gridview are not bounded by this frame, so they can appear on screen even before the transition. I tried setting the frame and bounds property on the GridView's viewcontroller's view, but I can still get a page of the gridview appearing onscreen before the transition animation.
Anyone see a solution to this? I was trying to find a way to clip the view of the GridView during the transition so pages dont appear except for the currently selected page, but havent found anything useful.
UPDATE: I found a possible fix by setting alpha = 0.0 for cells that are visible but shouldnt be (later setting alpha = 1.0 when the transition animation is complete). However, I need to know which cells to do this for. I need a way to access the page that the GMGridView is currently on so I can set the adjacent page's cells to have an alpha of 0.0.
UPDATE: Found a way to get it to work by using myGridView convertPoint:(A cgpoint i found by trial and error to be on the first cell of a page.) fromView:myGridView.window. NOTE: I needed an if/else if to check if i was in lanscape left or landscape right since the window coordinates do not rotate when the device is rotated. with this i was able to get the index of the cell at the point on the screen i had specified and then set the previous page to be transparent until after the transition animation.
I would still like to know if there is a way of "clipping" the gridview so that if the cells could be opaque, but just never displayed....?
I think I over complicated the problem. I was looking for the clipsToBounds method of a UIView (although I could have sworn I tried that before). In any case, its working now!
Im currently working on an iPhone project. I want to enlarge dynamically an UILabel in Objective-C like this:
alt text http://img268.imageshack.us/img268/9683/bildschirmfoto20100323u.png
How is this possible? I thought I have to do it with CoreAnimation, but I didn't worked. Here is the code I tried:
UILabel * fooL = //[…]
fooL.frame = CGRectMake(fooL.frame.origin.x, fooL.frame.origin.y, fooL.frame.size.width, fooL.frame.size.height);
fooL.font = [UIFont fontWithName:#"Helvetica" size:80];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.5];
[UIView setAnimationBeginsFromCurrentState:YES];
fooL.font = [UIFont fontWithName:#"Helvetica" size:144]; //bigger size
fooL.frame = CGRectMake(20 , 44, 728, 167); //bigger frame
[UIView commitAnimations];
The problem with this code is that it doesn't change the fontsize dynamically.
All you need to do is apply an affine transform to the UILabel object.
CGFloat scaleFactor = 2.0f;
label.transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor); // Enlarge by a factor of 2.
Scaling your label as suggested by others using the transform property will work great. One thing to keep in mind is that as the label gets larger, the font is not increasing, but just the rendered text, which means it will appear "fuzzier" as it gets larger.
Just scale your Label instead of changing the fontSize.
Try this method:
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition
forView:(UIView *)view
cache:(BOOL)cache
Parameters:
transition
A transition to apply to view. Possible values are described in UIViewAnimationTransition.
view
The view to apply the transition to.
cache
If YES, the before and after images of view are rendered once and used to create the frames in the animation. Caching can improve performance but if you set this parameter to YES, you must not update the view or its subviews during the transition. Updating the view and its subviews may interfere with the caching behaviors and cause the view contents to be rendered incorrectly (or in the wrong location) during the animation. You must wait until the transition ends to update the view.
If NO, the view and its contents must be updated for each frame of the transition animation, which may noticeably affect the frame rate.
Discussion
If you want to change the appearance of a view during a transition—for example, flip from one view to another—then use a container view, an instance of UIView, as follows:
Begin an animation block.
Set the transition on the container view.
Remove the subview from the container view.
Add the new subview to the container view.
Commit the animation block.