Key-Frame Animation appears fuzzy when moving Frames? - ios

I use JazzHands to create a key frame based animation in a UIScrollView.
Here is an example. Look at the view at the top. When you move from page to page. While the animation is running the view at the top is slightly moving from left to right. The animation appears a bit fuzzy.
Here is the code taken from the example here:
IFTTTFrameAnimation *titleView1FrameAnimation = [IFTTTFrameAnimation new];
titleView1FrameAnimation.view = self.titleView1;
[self.animator addAnimation:titleView1FrameAnimation];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(1)
andFrame:self.titleView1.frame]];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(2)
andFrame:CGRectOffset(self.titleView1.frame, timeForPage(2), 0)]];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(3)
andFrame:CGRectOffset(self.titleView1.frame, timeForPage(3), 0)]];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(4)
andFrame:CGRectOffset(self.titleView1.frame, timeForPage(4), 0)]];
When running the demo take a look at the part marked with red in the following screenshot:
Edit: Here is the code containing this problem: https://github.com/steffimueller/Intro-Guide-View-for-Talk.ai
How can I make the animation running smooth and less fuzzy?

This is due to the frame rate in the JazzHands IFTTTAnimatedScrollViewController being set for non-retina displays. You need to double the number in timeForPage, and also use double the number of the contentOffset in animate, but use the original non-doubled values of timeForPage in places where you were using that for laying out the positions of views instead of using it for the animation time.
Here's a Gist of the changes you'd have to make to your example to get it working. Fixed Demo Gist
You need this method for setting the animation times:
#define timeForPage(page) (NSInteger)(self.view.frame.size.width * (page - 1) * 2.0)
And this one for setting the centers of your views based on the page dimensions:
#define centerForPage(page) (NSInteger)(self.view.frame.size.width * (page - 1))
Then you need to override the scrollViewDidScroll: method in IFTTTAnimatedScrollViewController to use double the numbers it's currently using.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self.animator animate:(NSInteger)(scrollView.contentOffset.x * 2)];
}
We'll work on getting the JazzHands demo updated!

Before you start your animation call:
view.layer.shouldRasterize = YES; (seems that you are using objective-c so I put YES here, for swift should be true)
As soon as the animation is finished call
view.layer.shouldRasterize = NO; (seems that you are using objective-c so I put NO here, for swift should be false)
You should always use it when you animating a view
You can see more details about it in the WWDC 2012 Polishing Your Interface Rotations video (paid developer subscription needed)
I hope that helps you!
[EDIT]
Every Time you call the method animate set shouldRasterize to YES before it, like in the example bellow:
titleView1FrameAnimation.view.layer.shouldRasterize = YES;
[self. titleView1FrameAnimation animate:scrollView.contentOffset.x];

I've messed around a bit with the code and it seems that keeping those top views in place while the scrollview is scrolling made it jiggle left and right.
What I did is take out the title view from the scroll view and add it to the view controller view.
You can see it in action here
Later edit:
To actually see what I've changed you can check the file differences in Git. Basically I moved your titleViews (titleView1, titleView2, etc) from the scrollView to the view controller's view (so basically I've replaced all the lines that were like this:
[self.scrollView addSubView:self.titleView1]
to something like this:
[self.view addSubView:self.titleView1]
After that I've also took out the keyframe animations that were keeping your title views in place since they were not moving with the scrollview anymore. Practically I've deleted all the lines that were adding a frame animation to your titleviews from each configurePageXAnimation.
2nd question answer:
Say your screenshot view is called screenshotView. You can go ahead and create it like this:
UIImageView *screenshotView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ScreenshotImage"]];
[self.view addSubview:screenshotView];
self.screenshotView = screenshotView;
[self.screenshotView setCenter:CGPointMake(self.view.center.x + self.view.bounds.size.width, <#yourDesiredY#>)];
And animate it like this:
//screenshotView frame animation
IFTTTFrameAnimation *screenshotViewFrameAnimation = [IFTTTFrameAnimation new];
screenshotViewFrameAnimation.view = self.screenshotView;
[self.animator screenshotViewFrameAnimation];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(1) andFrame:self.screenshotView.frame]];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(2) andFrame:CGRectOffset(self.screenshotView.frame, -self.scrollView.bounds.size.width, 0.0)]];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(3) andFrame:CGRectOffset(self.screenshotView.frame, -self.scrollView.bounds.size.width, 0.0)]];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(4) andFrame:CGRectOffset(self.screenshotView.frame, -self.scrollView.bounds.size.width * 2, 0.0)]];

Related

Why viewDidLayout only got called once on iOS 7, but several times on iOS 8

I have an project using autolayout,
And I notice that after viewWillAppear, viewWillLayoutSubViews and viewDidLayoutSubViews pair will be called several times on iOS 8, for my case, it is 2-3 times usually.
The fist viewDidLayoutSubViews will get incorrect frame size, so I have to avoid for first viewDidLayoutSubViews, and init my views afterwards.
However, when I tested it on iOS 7, I found that only ONE viewWillLayoutSubViews and viewDidLayoutSubViews pair got called, so my code broke again.
My question is, what is changed on iOS 8 for this behaviour?
EDIT:
I have pasted my demo code here:
In the code, _pieChart will be added to self.ChartViewCanvas, and self.ChartViewCanvas is using autolayout. _pieChart is from old project code, which is drawn without auto layout.
I was required to draw the pie chart before viewDidAppear, because drawing in viewDidAppear will have a 1 sec delay compare to other views in storyboard. This is not allowed for me.
Is there any way to know when is the final viewDidLayoutSubViews? Calling [self.ChartViewCanvas addSubview:_pieChart]; multiple times will lead to lower performance, and sometimes _pieChart's drawInRect will not be called every time, so the chart is not update.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_pieChart.delegate = self;
if (!_pieChart) {
_pieChart = [[PieChartView alloc] initWithFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}else {
[_pieChart setFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}
//_pieChart.translatesAutoresizingMaskIntoConstraints = NO;
if ([_pieChart superview]) {
[_pieChart removeFromSuperview];
}
[self.ChartViewCanvas addSubview:_pieChart];
}
Probably only Apple knows, but I won't deal with that too much if everything is working fine. In iOS8 Apple changed a lot view controllers (again) in they way they are presented from containers VC as for rotation and UITraitCollections.
For instance UIAlertView is now a view controller, when you show one you trigger all the mechanism related to present a VC.
If this fact is creating an issue it must be said that you should not rely on how many times those methods are called because they were always be unpredictable there are too many variables to be taken into account.
A quick and dirty solution could be wrap your code in a dispatch_once if you want that it will be called only one time.
If you add your view using auto layout correctly you won't see any sort of bug.
[EDIT]
Here is a little snippet about how it might look your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
//.. your stuff
//We don't need any frame autolayout wil take care of calculating it on its pass
_pieChart = [[PieChartView alloc]initWithFrame:CGRectZero];
_pieChart.delegate = self;
_pieChart.translatesAutoresizingMaskIntoConstraints = NO;
[self.ChartViewCanvas addSubview:_pieChart];
NSDictionary *bindings = NSDictionaryOfVariableBindings(_pieChart);
// We create constraints to tell the view that it needs to sctretch its bounds to the superview
NSString *formatTemplate = #"%#:|[_pieChart]|";
for (NSString * axis in #[#"H",#"V"]) {
NSString * format = [NSString stringWithFormat:formatTemplate,axis];
NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:bindings];
[_pieChart.superview addConstraints:constraints];
}
// Do any additional setup after loading the view.
}
Of course that is going to call drawRect:, draw rect is called when a view is marked as dirty in the display pass, but before display is usually called the autolayout engine to calculate frames of views in needs for layout.
I tried this out on my application and found the same as you: 1 call on iOS7 and 3 on iOS8. From the stack traces this seems to be down to doing double layout after viewWillAppear and an extra layout following viewDidAppear not seen on iOS7.
My suggestion would be that you add any views in viewDidLoad (or viewWillAppear), then only do layout adjustments in the layout subview runs. Based on your updated post something like:
- (void)viewDidLoad{
[super viewDidLoad];
_pieChart = [[PieChartView alloc] initWithFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
[self.ChartViewCanvas addSubview:_pieChart];
_pieChart.delegate = self;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[_pieChart setFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}
For interest the difference between iOS7 and 8 calling sequence was:
iOS7
i) viewWillAppear is called.
ii) layout of subviews is called. From the stack this seems to relate to the navigation bar and animation.
ii) viewDidAppear is called.
iOS8
i) viewWillAppear is called.
ii) layout of subviews is called. From the stack this seems to relate to the navigation bar and animation.
iii) exact same layout with exact same stack is called again. So something in the stack must request a rerun from some point.
iv) viewDidAppear is called.
v) An extra layout of subviews is called. This seems driven from a transaction pushed onto the run loop.

iOS: understanding frame and views

I am working programmatically an application for iOS based on a ViewController. I am trying to do so programmatically as I want to understand the underlying concepts.
I have created a subclass of UIImageView and initialized this using an image. In the initialization method I added also a second UIImageView as I would like to handle the two differently but be part of the same object. Ultimately I would like to be able to scale the object (and hence the 2 UIImages) according to the device screen resolution (e.g. if resolution is low then I will scale the two images by 50%). I want to do this because I would like to be able to implement a zoom in and zoom out feature as well as supporting multiple resolutions and screen layouts.
Additional information:
The two images have different size (500x500 pixels) and (350x350
pixels).
My questions are:
how do I position the second image exactly in the center of the first? (I used the center property of the main UIImage but I think I got it wrong.. I thought that the center was the exact center of the square but either I am using it incorrectly or there is something I am missing)
are there any negative side effects for using this approach (UIView subclass class containing an additional UIView?) (E.g. Is it going to create confusion when applying transformation algorithms? Does it reduce the randering speed? Or more simply is it a bad design pattern?)
I find it difficult to understand the positioning of the second image. See code snipped below, this is what I use:
CGRect innerButtonFrame = CGRectMake(self.center.x/2, self.center.y/2,innerButtonSelectedImage.size.width,innerButtonSelectedImage.size.height);
Taken from:
-(id) initWithImage:(UIImage *)image
{
if(self = [super initWithImage:image]){
//
self.userInteractionEnabled = true;
// Initialize gesture recognizers
UITapGestureRecognizer *tapInView = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapInImageView:)];
[self addGestureRecognizer:tapInView];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressInView:)];
[self addGestureRecognizer:longPress];
// Initialize labels
..
// Inner circle image
innerButtonView = [[UIImageView alloc] init];
innerButtonSelectedImage = [UIImage imageNamed:#"inner circle.png"];
CGRect innerButtonFrame = CGRectMake(self.center.x/2, self.center.y/2,innerButtonSelectedImage.size.width,innerButtonSelectedImage.size.height);
innerButtonView.frame = innerButtonFrame;
[innerButtonView setImage:innerButtonSelectedImage];
// Add additional ui components to view
[self addSubview:innerButtonView];
..
[self addSubview:descriptionLabel];
}
return self;
}
EDIT: This is how it looks like if I change the positioning code to the following:
CGRect innerButtonFrame = CGRectMake(0, 0,innerButtonSelectedImage.size.width,innerButtonSelectedImage.size.height);
innerButtonView.frame = innerButtonFrame;
I also don't understand why the image is bigger than the screen.. as the blue one should be 500x500 pixel wide and the screen of the iPhone 6 should be 1334 x 750.
How about:
CGRect innerButtonFrame = CGRectMake(0, 0, innerButtonSelectedImage.size.width,innerButtonSelectedImage.size.height);
innerButtonFrame.center = self.center;
If you need 500*500 circle then add the circle half means Replace 500*500 with 250*250 . And small circle replace 350*350 with 175*175 And solve your problem.
I hope your problem will solve..Enjoy
Thanks..

Simple zoom in for UIScrollView inside of UIView

Hi I have a UIScrollView inside of a UIView. I have tried to use code snippets that I found online but they simply don't change anything. Also they are mostly for an image or custom view done within UIView, whereas in my case I have an array of programatically created UILabels. I have tried to change boundary values as well, it simply does not do anything. This is basically how I establish the size of it within viewDidAppear:
[scrollView setContentSize:CGSizeMake([screenView getWidth], [screenView getHeight])];
scrollView.showsHorizontalScrollIndicator = true;
scrollView.showsVerticalScrollIndicator = true;
screenView is a UIView variable.
This is the settings that I use(also in viewDidAppear):
doubleTapRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapResponse:)];
[doubleTapRecogniser addTarget:self action:#selector(doubleTapResponse:)];
doubleTapRecogniser.delegate = self;
doubleTapRecogniser.numberOfTapsRequired = 2;
[self.scrollView addGestureRecognizer:doubleTapRecogniser];
This is how I implemented my double tap method:
- (void) doubleTapResponse:(UITapGestureRecognizer *)recogniser
{
CGFloat newZoomScale = self.scrollView.zoomScale / 1.5f;
newZoomScale = MAX(newZoomScale, self.scrollView.minimumZoomScale);
[self.scrollView setZoomScale:newZoomScale animated:YES];
}
When I use NSLog messages within my doubleTapResponse, I can get responses from my console. However it does not do anything. What could be the problem?I am using iOS6.1
The error clearly says that the run time searched for a method named doubleTapResponse in the scrollview class you are using. Even if changing the target to self doesn't work, its the method definition place you have to change either the scrollview or the viewcontroller.
[doubleTapRecogniser addTarget:scrollView action:#selector(doubleTapResponse:)];
should be
[doubleTapRecogniser addTarget:self action:#selector(doubleTapResponse:)];
because the scrollview does not know what that method doubleTapResponse is.
Currently it is throwing an exception because it is trying to call the target of the UISCrollView with your doubleTapResponse method, you must add the target of self, and implement this method yourself. In here goes the logic for zooming I presume.
You must also define: doubleTapResponse in your viewcontroller (or class that you are using)
see this for more info:
Ray Wenderlich guide
In order to zoom please look at the following article: QUESTION

iOS alert banner/label?

I apologize if this question is very basic. I have been googling around but can't seem to find the api/reference for a drop down alert banner/label (I do not know the proper term for this), therefore I am posting here.
This: The label/banner which has "Please enter valid email address" in it.
So here a my questions:
What is the proper term for this (alert banner? notification? label?)
I am trying accomplish similar functionality to what is shown in the image, so basically if any field is invalid, the "label/banner" expands from underneath the navigation bar with the message in it:
If this is just a UILabel, what is the simplest way of adding the expand animation?
If it is something built in, since I have seen bunch of apps do it for alerting, please let me know what its called.
Have a look here, I'm sure you will be able to find something to suite your needs.
The basic idea is that its simply a UIView that you animate down from the top of the screen (at the very basic). You can get a lot fancier by adding gradients, touch recognizers to dismiss it, etc. But pretty much to get the base line functionality you would just do something like this:
//Create a view to hold the label and add images or whatever, place it off screen at -100
UIView *alertview = [[UIView alloc] initWithFrame:CGRectMake(0, -100, CGRectGetWidth(self.view.bounds), 100)];
//Create a label to display the message and add it to the alertView
UILabel *theMessage = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(alertview.bounds), CGRectGetHeight(alertview.bounds))];
theMessage.text = #"I'm an alert";
[alertview addSubview:theMessage];
//Add the alertView to your view
[self.view addSubview:alertview];
//Create the ending frame or where you want it to end up on screen, in this case 0 y origin
CGRect newFrm = alertview.frame;
newFrm.origin.y = 0;
//Animate it in
[UIView animateWithDuration:2.0f animations:^{
alertview.frame = newFrm;
}];
Check out my project - it might be just the thing you're looking for.
https://github.com/alobi/ALAlertBanner
For easier control over animating the alert, you can embed your custom view in a UIStackView and simply show/hide it in an animation block. That way will significantly reduce the amount of code needed to animate the visibility of the alert.

How to update iOS PhotoScroller sample code

I'm using altered sample code from PhotoScroller within my app. I have a table view of image thumbnails, and I can pass the array of images that populate that table to PhotoViewController. Currently, PhotoViewController starts with the first image in my array and I can scroll back and forth. This works properly as Apple's sample code.
Now What I want to do is tap a table cell with thumbnail, and start scrolling images beginning with the image in my array at that index. Ex: if I have 5 images in a table and I tap image #3, I want the first image in PhotoViewController to be that third image, and able to scroll left or right to #2 or #4. Hope this makes sense.
I see in PhotoViewController that sub views are being added per image. Any way I can tell it "jump to view #3" without destroying the other views or their overall order of appearance? Any ideas or advice is welcome. Code can be found on the iOS developer site for PhotoScroller sample code.
Ok, I'm rambling... Thanks in advance for your help!
The way I do this is to have a variable called startingPage which gets set in the initialiser of the photo view controller. Then when the pages are being created, first set the correct offset for the scroll view.
So in the PhotoScroller case that would be in loadView. Like so:
- (void)loadView
{
// Step 1: make the outer paging scroll view
CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
pagingScrollView.pagingEnabled = YES;
pagingScrollView.backgroundColor = [UIColor blackColor];
pagingScrollView.showsVerticalScrollIndicator = NO;
pagingScrollView.showsHorizontalScrollIndicator = NO;
pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
pagingScrollView.delegate = self;
self.view = pagingScrollView;
// Set the content offset of the scrollview
CGRect bounds = pagingScrollView.bounds;
CGPoint contentOffset = CGPointMake(bounds.size.width * startingPage, 0.0f);
[pagingScrollView setContentOffset:contentOffset animated:NO];
// Step 2: prepare to tile content
recycledPages = [[NSMutableSet alloc] init];
visiblePages = [[NSMutableSet alloc] init];
[self tilePages];
}

Resources