UIView animation flickering with autoreverse - animation

I'm trying to animate an indicator to an empty form field so I'm using the method below to animate to a position, reverse the animation, and repeat. In the simulator this works fine, on my 3GS it looks like there is a flicker right when the completion block is called. The indicator is briefly shown at the middle position rather than back at it's origin.
Any thoughts on why this is happening? Thanks.
- (void)bounceFormIndicator {
if (formIndicator.superview == nil) {
return;
}
int bounceDistance = 24;
[UIView animateWithDuration:0.6
delay:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction
animations:^{
CGRect indicatorFrame = formIndicator.frame;
indicatorFrame.origin.x += bounceDistance;
formIndicator.frame = indicatorFrame;
}completion:^(BOOL finished){
CGRect indicatorFrame = formIndicator.frame;
indicatorFrame.origin.x -= bounceDistance;
formIndicator.frame = indicatorFrame;
[self bounceFormIndicator];
}];
}

I had the same problem, and went to Apple DTS to help with a workaround.
As per DTS, this 'flickering' effect, or snap-back effect is the expected behaviour... I thought that I was doing something wrong with my project for a long time.
In particular it is this way because the documentation states, for
UIViewAnimationOptionAutoreverse Run the animation backwards and
forwards.
Must be combined with the UIViewAnimationOptionRepeat option.
In order to get the flicker to go away, I had to do 2 things.
My implementation was dynamic, so you might not have to implement the first step, but I'll keep it in here just for reference.
First, I checked to see if UIViewAnimationOptionAutoreverse was part of the options I was going to pass into my animation, and UIViewAnimationOptionRepeat was not... If so, I stripped it from the options by adding a line like:
animationOptions &= ~UIViewAnimationOptionAutoreverse;
To create the reversing animation without repeating, I added an opposite UIView animation as my completion block. I also inverted the easing if it was either UIViewAnimationOptionCurveEaseIn or UIViewAnimationOptionCurveEaseOut...
The code from my project follows:
The statement that strips the autoreverse option from an object's animationOptions:
if ((animationOptions & AUTOREVERSE) == AUTOREVERSE) {
self.shouldAutoreverse = YES;
animationOptions &= ~AUTOREVERSE;
}
An example of an overridden property setter that handles an animation:
-(void)setCenter:(CGPoint)center {
CGPoint oldCenter = CGPointMake(self.center.x, self.center.y);
void (^animationBlock) (void) = ^ { super.center = center; };
void (^completionBlock) (BOOL) = nil;
BOOL animationShouldNotRepeat = (self.animationOptions & REPEAT) != REPEAT;
if(self.shouldAutoreverse && animationShouldNotRepeat) {
completionBlock = ^ (BOOL animationIsComplete) {
[self autoreverseAnimation:^ { super.center = oldCenter;}];
};
}
[self animateWithBlock:animationBlock completion:completionBlock];
}
The completion method called for in the case of reversing without repeating:
-(void)autoreverseAnimation:(void (^)(void))animationBlock {
C4AnimationOptions autoreverseOptions = BEGINCURRENT;
if((self.animationOptions & LINEAR) == LINEAR) autoreverseOptions |= LINEAR;
else if((self.animationOptions & EASEIN) == EASEIN) autoreverseOptions |= EASEOUT;
else if((self.animationOptions & EASEOUT) == EASEOUT) autoreverseOptions |= EASEIN;
[UIView animateWithDuration:self.animationDuration
delay:0
options:autoreverseOptions
animations:animationBlock
completion:nil];
}

Related

UIView.animateWithDuration on UITextfield's contentoffset: It clips the text (Swift)

I use the following code:
UIView.animateWithDuration(30, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.textView.contentOffset = CGPointMake(0, 700)
}, completion: nil)
If the contentOffset is up to about 100, the animation works correctly and all text is visible. Everything higher than that leads to the disappearance of text at the beginning of the textlabel during the animation. The higher the contentOffset, the more text disappears during the animation. So I see white space for a while and then the remaining text comes into animation. Also: Once the animation is done, all text is visible again when I scroll up.
I have tried multiple UITextviews in various superviews. The general idea is a kind of Credits like animation where all text moves slowly upwards for about 30 seconds.
You can try looping small animations together like the following.
func animate(count:Int)
{
if (count > 100)
{
return
}
UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.textView.contentOffset = CGPointMake(0, self.textView.contentOffset.y + 10)
}, completion: { finished in
dispatch_async(dispatch_get_main_queue(),{
self.animate(count+1)
})
})
}
This still appears to be an issue even in iOS 10 SDK.
To work around it, I manually animated the scrolling using setContentOffset:, delaying each step using performSelector: withObject: afterDelay: and implementing a method that scrolls one increment at a time (in my case 1/2 point).
To prevent users from manually overriding the scroll animation, you have to manually disable user interaction (animateWithDuration: automatically disables user scrolling while the animation is playing), you also must have scrolling enabled. I also disabled selection and editing, because that fit my use:
note: Objective-C code follows
textBox.editable = NO;
textBox.selectable = NO;
textBox.scrollEnabled = YES;
textBox.userInteractionEnabled = NO;
float contentLength = textBox.contentSize.height - textBox.frame.size.height;
float timePerLine = audioLength / contentLength;
for (float i = 0; i < contentLength; i+=0.5){
[self performSelector:#selector(scrollOneLine) withObject:nil afterDelay:timePerLine * i];
}
And then implemented a method to be used as a selector to scroll one increment at a time
-(void) scrollOneLine {
float currentPosition = textBox.contentOffset.y;
[textBox setContentOffset:CGPointMake(0, currentPosition + 0.5) animated:NO];
}

How to collapse an NSSplitView pane with animation while using Auto Layout?

I've tried everything I can think of, including all the suggestions I've found here on SO and on other mailing lists, but I cannot figure out how to programmatically collapse an NSSplitView pane with an animation while Auto Layout is on.
Here's what I have right now (written in Swift for fun), but it falls down in multiple ways:
#IBAction func toggleSourceList(sender: AnyObject?) {
let isOpen = !splitView.isSubviewCollapsed(sourceList.view.superview!)
let position = (isOpen ? 0 : self.lastWidth)
if isOpen {
self.lastWidth = sourceList.view.frame.size.width
}
NSAnimationContext.runAnimationGroup({ context in
context.allowsImplicitAnimation = true
context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
context.duration = self.duration
self.splitView.setPosition(position, ofDividerAtIndex: 0)
}, completionHandler: { () -> Void in
})
}
The desired behavior and appearance is that of Mail.app, which animates really nicely.
I have a full example app available at https://github.com/mdiep/NSSplitViewTest.
Objective-C:
[[splitViewItem animator] setCollapse:YES]
Swift:
splitViewItem.animator().collapsed = true
From Apple’s help:
Whether or not the child ViewController corresponding to the
SplitViewItem is collapsed in the SplitViewController. The default is
NO. This can be set with the animator proxy to animate the collapse or
uncollapse. The exact animation used can be customized by setting it
in the -animations dictionary with a key of "collapsed". If this is
set to YES before it is added to the SplitViewController, it will be
initially collapsed and the SplitViewController will not cause the
view to be loaded until it is uncollapsed. This is KVC/KVO compliant
and will be updated if the value changes from user interaction.
I was eventually able to figure this out with some help. I've transformed my test project into a reusable NSSplitView subclass: https://github.com/mdiep/MDPSplitView
For some reason none of the methods of animating frames worked for my scrollview. I didn't try animating the constraints though.
I ended up creating a custom animation to animate the divider position. If anyone is interested, here is my solution:
Animation .h:
#interface MySplitViewAnimation : NSAnimation <NSAnimationDelegate>
#property (nonatomic, strong) NSSplitView* splitView;
#property (nonatomic) NSInteger dividerIndex;
#property (nonatomic) float startPosition;
#property (nonatomic) float endPosition;
#property (nonatomic, strong) void (^completionBlock)();
- (instancetype)initWithSplitView:(NSSplitView*)splitView
dividerAtIndex:(NSInteger)dividerIndex
from:(float)startPosition
to:(float)endPosition
completionBlock:(void (^)())completionBlock;
#end
Animation .m
#implementation MySplitViewAnimation
- (instancetype)initWithSplitView:(NSSplitView*)splitView
dividerAtIndex:(NSInteger)dividerIndex
from:(float)startPosition
to:(float)endPosition
completionBlock:(void (^)())completionBlock;
{
if (self = [super init]) {
self.splitView = splitView;
self.dividerIndex = dividerIndex;
self.startPosition = startPosition;
self.endPosition = endPosition;
self.completionBlock = completionBlock;
[self setDuration:0.333333];
[self setAnimationBlockingMode:NSAnimationNonblocking];
[self setAnimationCurve:NSAnimationEaseIn];
[self setFrameRate:30.0];
[self setDelegate:self];
}
return self;
}
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
[super setCurrentProgress:progress];
float newPosition = self.startPosition + ((self.endPosition - self.startPosition) * progress);
[self.splitView setPosition:newPosition
ofDividerAtIndex:self.dividerIndex];
if (progress == 1.0) {
self.completionBlock();
}
}
#end
I'm using it like this - I have a 3 pane splitter view, and am moving the right pane in/out by a fixed amount (235).
- (IBAction)togglePropertiesPane:(id)sender
{
if (self.rightPane.isHidden) {
self.rightPane.hidden = NO;
[[[MySplitViewAnimation alloc] initWithSplitView:_splitView
dividerAtIndex:1
from:_splitView.frame.size.width
to:_splitView.frame.size.width - 235
completionBlock:^{
;
}] startAnimation];
}
else {
[[[MySplitViewAnimation alloc] initWithSplitView:_splitView
dividerAtIndex:1
from:_splitView.frame.size.width - 235
to:_splitView.frame.size.width
completionBlock:^{
self.rightPane.hidden = YES;
}] startAnimation];
}
}
/// Collapse the sidebar
func collapsePanel(_ number: Int = 0){
guard number < self.splitViewItems.count else {
return
}
let panel = self.splitViewItems[number]
if panel.isCollapsed {
panel.animator().isCollapsed = false
} else {
panel.animator().isCollapsed = true
}
}
I will also add, because it took me quite a while to figure this out, that setting collapseBehavior = .useConstraints on your NSSplitViewItem (or items) may help immensely if you have lots of constraints defining the layouts of your subviews. My split view animations didn't look right until I did this. YMMV.
If you're using Auto-Layout and you want to animate some aspect of the view's dimensions/position, you might have more luck animating the constraints themselves. I've had a quick go with an NSSplitView but have so far only met with limited success. I can get a split to expand and collapse following a button push, but I've ended up having to try to hack my way around loads of other problems caused by interfering with the constraints. In case your unfamiliar with it, here's a simple constraint animation:
- (IBAction)animate:(NSButton *)sender {
/* Shrink view to invisible */
NSLayoutConstraint *constraint = self.viewWidthConstraint;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[[NSAnimationContext currentContext] setDuration:0.33];
[[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
[[constraint animator] setConstant:0];
} completionHandler:^{
/* Do Some clean-up, if required */
}];
Bear in mind you can only animate a constraints constant, you can't animate its priority.
NSSplitViewItem (i.e. arranged subview of NSSplitView) can be fully collapsed, if it can reach Zero dimension (width or height). So, we just need to deactivate appropriate constrains before animation and allow view to reach Zero dimension. After animation we can activate needed constraints again.
See my comment for SO question How to expand and collapse NSSplitView subviews with animation?.
This is a solution that doesn't require any subclasses or categories, works without NSSplitViewController (which requires macOS 10.10+), supports auto layout, animates the views, and works on macOS 10.8+.
As others have suggested, the solution is to use an NSAnimationContext, but the trick is to set context.allowsImplicitAnimation = YES (Apple docs). Then just set the divider position as one would normally.
#import <Quartz/Quartz.h>
#import <QuartzCore/QuartzCore.h>
- (IBAction)toggleLeftPane:(id)sender
{
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
context.allowsImplicitAnimation = YES;
context.duration = 0.25; // seconds
context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
if ([self.splitView isSubviewCollapsed:self.leftPane]) {
// -> expand
[self.splitView setPosition:self.leftPane.frame.size.width ofDividerAtIndex:0];
} else {
// <- collapse
_lastLeftPaneWidth = self.leftPane.frame.size.width;
// optional: remember current width to restore to same size
[self.splitView setPosition:0 ofDividerAtIndex:0];
}
[self.splitView layoutSubtreeIfNeeded];
}];
}
Use auto layout to constrain the subviews (width, min/max sizes, etc.). Make sure to check "Core Animation Layer" in Interface Builder (i.e. set views to be layer backed) for the split view and all subviews — this is required for the transitions to be animated. (It will still work, but without animation.)
A full working project is available here: https://github.com/demitri/SplitViewAutoLayout.

iOS8 keyboard confusion, why root view's hitTest method be triggered?

When touches on the keyboard area, the root view's method be triggered:
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
I am very confused,anyone can help me?
In your applicationWillEnterForeground in AppDelegate, put this code.
It works for me, specially with KLCPopup
- (void)applicationWillEnterForeground:(UIApplication *)application{
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
if (!IS_OS_8_OR_LATER) return;
[UIApplication.sharedApplication.windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *w, NSUInteger idx, BOOL *stop) {
if (!w.opaque && [NSStringFromClass(w.class) hasPrefix:#"UIText"]) {
// The keyboard sometimes disables interaction. This brings it back to normal.
BOOL wasHidden = w.hidden;
w.hidden = YES;
w.hidden = wasHidden;
*stop = YES;
}
}];}
This problem will appear in:
1、you changed keywindow‘s rootViewController;
2、enter background and return to the foreground;
So,restore UITextEffectsWindow can fixed it after your change everytime.
void TSRestoreKeyboardWindow(void)
{
if (!TSSystemVersionGreaterThanIOS8()) return;
[UIApplication.sharedApplication.windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *w, NSUInteger idx, BOOL *stop) {
if (!w.opaque && [NSStringFromClass(w.class) hasPrefix:#"UIText"]) {
// The keyboard sometimes disables interaction. This brings it back to normal.
BOOL wasHidden = w.hidden;
w.hidden = YES;
w.hidden = wasHidden;
*stop = YES;
}
}];
}

iCarousels Getting Stuck in iOS 7

I have implemented the iCarousels in my project. But after updating the app for iOS 7, my iCarousels are getting stuck in between. Its working fine in iOS 6 and 5. the problem in iOS 7 is that my scroll view below the iCarousel view is getting called first sometimes when I touch my carousel view. Can anyone help me out here?
Is the solution in the following method's return value:
- (CGFloat)carouselItemWidth:(iCarousel *)carousel
I tried many things here, and it works fine for few times, and again after some time it start getting stuck because the scroll view takes the touch from its subview(iCarousel view) and call its own delegate methods before the iCarousel's delegate method.
I am not using any gesture recognizer. I am using scroll view because i have iCarousel view and another view resting on UIScrollView, so that I can use pull to refresh as well.
The following delegate methods I am using, and changing in carouselItemWidth has decreased the stuck problem, but it still persists
- (CATransform3D)carousel:(iCarousel *)carousel itemTransformForOffset:(CGFloat)offset baseTransform:(CATransform3D)transform
{
CGFloat tilt = 0.65f;
CGFloat spacing = 0.28f; // should be ~ 1/scrollSpeed;
CGFloat clampedOffset = fmaxf(-1.0f, fminf(1.5f, offset));
CGFloat itemWidth = 320;
CGFloat x = (clampedOffset * 0.5f * tilt + offset * spacing) * itemWidth;
CGFloat z = fabsf(clampedOffset) * -itemWidth * 0.5f;
transform = CATransform3DTranslate(transform, 0.0f, x, z);
transform = CATransform3DRotate(transform, -clampedOffset * M_PI_2 * tilt, -1.0f, 0.0f, 0.0f);
//DLog(#"offset: %f, %#", offset, [NSValue valueWithCATransform3D:transform]);
return transform;
}
- (NSUInteger)numberOfPlaceholdersInCarousel:(iCarousel *)carousel
{
//note: placeholder views are only displayed on some carousels if wrapping is disabled
return 0;
}
- (CGFloat)carouselItemWidth:(iCarousel *)carousel
{
//usually this should be slightly wider than the item views
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
{
return 270;
}
else
{
return 250;
}
}
- (BOOL)carouselShouldWrap:(iCarousel *)carousel
{
return NO;
}
The problem seems to be that when a scrollview receives a touch, it waits a second to see if it should handle it before passing it to the carousel.
You can (mostly) fix this by setting scrollView.delaysContentTouches = NO;
It's still a bit clunky if you try to swipe the carousel when the scrollview is moving/decelerating however. You will have to wait until it stops moving to interact with the carousel.
I'm investigating if there's a better way to fix this.
UPDATE:
I still don't have a proper general-purpose fix for this yet, but as a workaround, you can add this method to your local copy of iCarousel:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [gesture isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}
This forces iCarousel's pan gesture recogniser to take precedence over the one in the scrollView. If you combine this with the delaysContentTouches fix above, you shouldn't have any issues scrolling the carousel when it's inside a tableview or scrollview.

Changing Dynamic UIView subviews with single Tap in UIGestureRecognizer Method

UPDATE:Solved issue, see below!
The situation: I have several dynamically loaded UIViews on a UIScrollView in a nib.
Expected behavior: I want to single TAP any one of the UIViews and it will change background color to indicate it was tapped. If it was already tapped it should then change back to its initial look.
I have set up a UITapGesture recognizer on each of the UIViews and here is the selector method where I am doing the behavior. I have confused myself. I apologize for the sketchy logic here (it is a ruff draft). I have set up a isTapped BOOL set to "NO" initially in the init in the file.
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer {
isTapped = !isTapped;
UIView *v = gestureRecognizer.view;
NSInteger currentIndex = [studentCellArray indexOfObjectIdenticalTo:v];
if (oldIndex != currentIndex) {
isTapped = YES;
}
//check to see if obj in array then switch on/off
if ([tappedViewArray indexOfObjectIdenticalTo:v] != NSNotFound) {
oldIndex = currentIndex;
}
if (currentIndex == v.tag) {
isTapped = !isTapped;
}
if (isTapped) {
[tappedViewArray addObject:v];
[super formatViewTouchedNiceGrey:v];
}else{
[tappedViewArray removeObject:v];
[super formatViewBorder:v];
}
if (currentIndex == oldIndex) {
isTapped = !isTapped;
}
}
Actual Behavior: After Tapping the First UIView it selects fine and changes, a second tap will change it back, however after successive taps it stays selected. Also, if you select a UIView and go to another view - you have to double tap the successive views.
I would like to just tap once to turn off or on any of the UIViews in the scrollview.
UPDATE: Well, after some Hand writing and other vain attempts at trying to focus on this issue ---- I have solved it this way and it BEHAVES properly!
here is my solution:
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer {
isTapped = !isTapped;
UIView *v = gestureRecognizer.view;
NSInteger currentIndex = [studentCellArray indexOfObjectIdenticalTo:v];
if (((isTapped && currentIndex != oldIndex) || (!isTapped && currentIndex != oldIndex)) && [tappedViewArray indexOfObject:v] == NSNotFound) {
oldIndex = currentIndex;
[tappedViewArray addObject:v];
[super formatCornerRadiusWithGreyBackgrnd:v];
} else {
[super formatViewBorder:v];
[tappedViewArray removeObject:v];
}
}
So I hope this helps someone with this issue.
The key was to check for the isTapped and indexes being not equal AND the view object NOT being in the array I was assembling to indicate items touched/Tapped....

Resources