Setting the initial values of a UIView animation - ios

I run this code when a swipe event is fired, it shows an arrow zooming in while fading out, as you see I set the location of the image to be the same location as where the swipe is, but it only starts from where the previous touch was(set in the completion block). But at the start of the method I set update the location so why is it animating from the location of the previous touch?
static bool isAnimating;
- (void)animateImageZoom: (UIImageView *)imageView startingAtLocation:(CGPoint)location inDirection: (FadeDirection)direction
{
if (!isAnimating) {
CGRect previousFrame = imageView.frame;
imageView.frame = CGRectMake(location.x, location.y, previousFrame.size.width, previousFrame.size.height);
imageView.hidden = NO;
[UIView animateWithDuration:0.6f
delay:0.3f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^(void) {
isAnimating = YES;
imageView.alpha = 0;
// ZOOM
if (direction == fadeLeft) {
imageView.frame = CGRectMake(location.x,
location.y, 256, 256);
} else if (direction == fadeRight) {
imageView.frame = CGRectMake(location.x,
location.y, 256, 256);
}
}
completion:^(BOOL finished) {
isAnimating = NO;
imageView.frame = CGRectMake(location.x, location.y, previousFrame.size.width, previousFrame.size.height);
imageView.hidden = YES;
imageView.alpha = 0.8f;
}];
}
}

Related

Adding NSTimer in image slide and page contol to move images automatically

Here is the image slider works fine when dragging manually the image changes with page control indicator change but I want to add the timer to the following code and move the images and indicator automatically. Please help me to apply NS timer in this code and also want to move from the last image to 1st image and so on.
#implementation DashboardViewController {
NSArray * animationArray;
}
#synthesize scroller = scroller;
#synthesize pageControl = pageControl;
-(void) viewDidLoad {
[super viewDidLoad];
scroller.pagingEnabled = YES;
scroller.showsHorizontalScrollIndicator = NO;
scroller.delegate = self;
animationArray = [NSArray arrayWithObjects: [UIImage imageNamed: # "image1.jpg"],
[UIImage imageNamed: # "image2.jpg"],
[UIImage imageNamed: # "image3.jpg"],
[UIImage imageNamed: # "image4.png"],
[UIImage imageNamed: # "image5.jpg"],
nil
];
CGRect scrollFrame = CGRectMake(0, 0, self.view.frame.size.width, self.scroller.frame.size.height);
scroller.frame = scrollFrame;
self.scroller.contentSize = CGSizeMake(self.scroller.frame.size.width * animationArray.count, self.scroller.frame.size.height);
for (int i = 0; i < animationArray.count; i++) {
CGRect frame;
frame.origin.x = self.scroller.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scroller.frame.size;
UIImageView * imgView = [
[UIImageView alloc] initWithFrame: CGRectMake(self.scroller.frame.size.width * i, 0, self.scroller.frame.size.width, self.scroller.frame.size.height)
];
imgView.image = [animationArray objectAtIndex: i];
imgView.frame = frame;
[self.scroller addSubview: imgView];
}
self.pageControl.currentPage = 0;
}
-(void) scrollViewDidScroll: (UIScrollView * ) sender {
if (!pageControlBeingUsed) {
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = self.scroller.frame.size.width;
int page = floor((self.scroller.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
}
}
-(void) scrollViewWillBeginDragging: (UIScrollView * ) scrollView {
pageControlBeingUsed = NO;
}
-(void) scrollViewDidEndDecelerating: (UIScrollView * ) scrollView {
[self setIndiactorForCurrentPage];
}
-(void) setIndiactorForCurrentPage {
uint page = scroller.contentOffset.x / scroller.frame.size.width;
[pageControl setCurrentPage: page];
}
- (IBAction) changePage {
// Update the scroll view to the appropriate page
CGRect frame;
pageControl.currentPage = animationArray.count;
frame.origin.x = self.scroller.frame.size.width * self.pageControl.currentPage;
frame.origin.y = 0;
frame.size = self.scroller.frame.size;
[self.scroller scrollRectToVisible: frame animated: YES];
pageControlBeingUsed = YES;
}
- (void) didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
self.scroller = nil;
self.pageControl = nil;
}
Please try this code for NSTimer (i use collectionView for easy use)
var timer : Timer?
var currentFeaturedCouponIndex = 0
//MARK: - NSTimer -
func setupTimer(){
timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(self.scrollFeatured), userInfo: nil, repeats: true);
}
func scrollFeatured(){
if self.arrFeaturedCoupon.count > 0 {
//Becauese we scroll coupon if total is > 1
if self.arrFeaturedCoupon.count > 1 {
if self.currentFeaturedCouponIndex < self.arrFeaturedCoupon.count - 1 {
self.currentFeaturedCouponIndex = self.currentFeaturedCouponIndex + 1
}
else {
self.currentFeaturedCouponIndex = 0
}
self.pageControl.currentPage = currentFeaturedCouponIndex
//When currentFeaturedCouponIndex == 0 we make animated: false
if self.currentFeaturedCouponIndex == 0 {
self.featuredCouponColView.scrollToItem(at: self.featuredCouponCurrentIndexPath, at: UICollectionViewScrollPosition.left, animated: false)
}
else {
self.featuredCouponColView.scrollToItem(at: self.featuredCouponCurrentIndexPath, at: UICollectionViewScrollPosition.left, animated: true)
}
}
}
}
To invalidate timer when and where you want
func invalidateTimer(){
if timer != nil {
self.timer!.invalidate()
self.timer = nil
}
}

How do I create a bouncing popover animation like this?

I want to create an animated popover bubble like the one below. With the same expanding/contracting bounce animation. Does anyone know how to go about this? Is is just a UIView with animation? What animation effect is this? Any pointers on how to go about this would be really appreciated. Thanks!
You can use the
Obj-C:
animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:animations completion: method like this:
UIView *animationView = [[UIView alloc] initWithFrame:CGRectMake(50, 90, 100, 50)];
animationView.backgroundColor = [UIColor redColor];
animationView.transform = CGAffineTransformMakeScale(0, 0);
[self.view addSubview:animationView];
[UIView animateWithDuration:0.3
delay:0
usingSpringWithDamping:0.5
initialSpringVelocity:5
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
animationView.transform = CGAffineTransformIdentity;
}
completion:nil];
Swift:
var animationView: UIView = UIView(frame: CGRectMake(50, 90, 100, 50))
animationView.backgroundColor = UIColor.redColor()
animationView.transform = CGAffineTransformMakeScale(0, 0)
self.view!.addSubview(animationView)
UIView.animateWithDuration(0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 5, options: .CurveEaseInOut, animations: {() -> Void in
animationView.transform = CGAffineTransformIdentity
}, completion: { _ in })
i think it is UIPopoverPresentationController.
sample:
-(void)showPopover:(UIBarButtonItem*)sender{
if(!_btnController){
_btnController = [[ButtonsViewController alloc] init];
_btnController.delegate = self;
}
if (_popover == nil) {
_btnController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *pc = [ _btnController popoverPresentationController];
pc.barButtonItem = sender;
pc.permittedArrowDirections = UIPopoverArrowDirectionAny;
pc.delegate = self;
[self presentViewController: _btnController animated:YES completion:nil];
} else {
//The color picker popover is showing. Hide it.
[_popover dismissPopoverAnimated:YES];
_popover = nil;
}}

How to animate a floating UIView in ObjectiveC

I am trying to have a UIView animate as a floating object, or as a Balloon :D
The UIView is in the middle of the screen, and I want it to keep floating randomly around its first initiated spot. Not across the screen or anything like that, just floating in the area of 5 pixels around it.
Any suggestions? :D
I tried this:
[UIView animateWithDuration:0.1
delay:0.0
options: UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction
animations:^{
myCircleUIView.transform = CGAffineTransformMakeTranslation([self randomFloatingGenerator], [self randomFloatingGenerator]);
}
completion:NULL];
randomFloatingGenerator generates a number between -5 and 5. Problem is, it only executes once, and keeps repeating with the same random values.
EDIT1:
Now I have tried This
-(void)animationLoop{
[UIView animateWithDuration:1.0
animations: ^{ myCircleUIView.transform = CGAffineTransformMakeTranslation([self randomFloatingGenerator], [self randomFloatingGenerator]); }
completion:
^(BOOL finished) {
[UIView animateWithDuration:1.0
animations:^{ myCircleUIView.transform = CGAffineTransformMakeTranslation(0,0);
}
completion:
^(BOOL finished) {[self animationLoop];}];
}];
But it is still not working, the animation is.... Cracky... I think I am doing some stupid mistake that I need a second set of eyes to help me with.
EDIT2:
Fixed it.
-(void)animationLoop{
CGPoint oldPoint = CGPointMake(myCircleUIView.frame.origin.x, myCircleUIView.frame.origin.y);
[UIView animateWithDuration:1.0
animations: ^{ myCircleUIView.frame = CGRectMake(myCircleUIView.frame.origin.x + [self randomFloatingGenerator], myCircleUIView.frame.origin.y + [self randomFloatingGenerator], myCircleUIView.frame.size.width, myCircleUIView.frame.size.height); }
completion:
^(BOOL finished) {
[UIView animateWithDuration:1.0
animations:^{ myCircleUIView.frame = CGRectMake(oldPoint.x, oldPoint.y, myCircleUIView.frame.size.width, myCircleUIView.frame.size.height);}
completion:
^(BOOL finished) {[self animationLoop];}];
}];
}
Thanks for the help anyone.
EDIT 3:
Someone posted an enhanced code.
#Shamy's solution, improved, fixed and CPU safe.
-(void)animateFloatView:(UIView*)view{
// Abort the recursive loop
if (!view)
return;
CGPoint oldPoint = CGPointMake(view.frame.origin.x, view.frame.origin.y);
[UIView animateWithDuration:0.6
animations: ^{ view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y - 15, view.frame.size.width, view.frame.size.height); }
completion:
^(BOOL finished) {
if (finished) {
[UIView animateWithDuration:0.6
animations:^{ view.frame = CGRectMake(oldPoint.x, oldPoint.y, view.frame.size.width, view.frame.size.height);}
completion:
^(BOOL finished2) {
if(finished2)
[self animateFloatView:view];
}];
}
}];
}
Whenever you want to stop the animation (necessary when leaving the View Controller), call the function using nil, as this:
[self recursiveFloatingAnimation:nil];
Not stoping the animation will cause the recursive loop to run infinitely and overwhelm the CPU stack.
Swift 4
UIView.animate(withDuration: 0.6, delay: 0.1, options: [.autoreverse, .repeat], animations: {
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y - 15, width: self.frame.size.width, height: self.frame.size.height)
},completion: nil )
It will behave in that way. As you are repeating the animation not the function. So it will repeat same animation every time you set at first call. Do it like:
#interface AAViewController ()
#property (nonatomic, strong) UIView * floatingView;
#end
#implementation AAViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_floatingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
[_floatingView setBackgroundColor:[UIColor redColor]];
[self.view addSubview:_floatingView];
[self circleUIViewFloating];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Circle UIView floating
- (void) circleUIViewFloating {
[UIView animateWithDuration:2.0
animations:^{
_floatingView.transform = CGAffineTransformMakeTranslation([self randomFloatingGenerator : 270], [self randomFloatingGenerator:480]);
} completion:^(BOOL finished) {
[self circleUIViewFloating];
}];
}
- (int) randomFloatingGenerator : (int) max{
return arc4random() % max;
}
Here is complete running project for this.
You can achieve the hovering or floating effect using CABasicAnimation.
You will have to include the QuartzCore framework to use this.
Swift 4.2:
import QuartzCore
let hover = CABasicAnimation(keyPath: "position")
hover.isAdditive = true
hover.fromValue = NSValue(cgPoint: CGPoint.zero)
hover.toValue = NSValue(cgPoint: CGPoint(x: 0.0, y: -5.0))
hover.autoreverses = true
hover.duration = 0.8
hover.repeatCount = Float.infinity
myCustomView.layer.add(hover, forKey: "hoverAnimation")
Make sure to remove the animation when the view is no longer being shown.
override func viewWillDisappear(_ animated: Bool) {
// Stop animating when view is going to disappear
myCustomView.layer.removeAllAnimations()
}
-(void)animationLoop{
CGPoint oldPoint = CGPointMake(myCircleUIView.frame.origin.x, myCircleUIView.frame.origin.y);
[UIView animateWithDuration:1.0
animations: ^{ myCircleUIView.frame = CGRectMake(myCircleUIView.frame.origin.x + [self randomFloatingGenerator], myCircleUIView.frame.origin.y + [self randomFloatingGenerator], myCircleUIView.frame.size.width, myCircleUIView.frame.size.height); }
completion:
^(BOOL finished) {
[UIView animateWithDuration:1.0
animations:^{ myCircleUIView.frame = CGRectMake(oldPoint.x, oldPoint.y, myCircleUIView.frame.size.width, myCircleUIView.frame.size.height);}
completion:
^(BOOL finished) {[self animationLoop];}];
}];
}
Swift 2.1 Version Here:
func animateFloatView(view: UIView?) {
// Abort the recursive loop
guard let view = view else { return }
let oldPoint: CGPoint = CGPointMake(view.frame.origin.x, view.frame.origin.y)
UIView.animateWithDuration(0.6, animations: {() -> Void in
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y - 15, view.frame.size.width, view.frame.size.height)
}, completion: {(finished: Bool) -> Void in
if finished {
UIView.animateWithDuration(0.6, animations: {() -> Void in
view.frame = CGRectMake(oldPoint.x, oldPoint.y, view.frame.size.width, view.frame.size.height)
}, completion: {(finished2: Bool) -> Void in
if finished2 {
self.animateFloatView(view)
}
})
}
})
}
Swift 3.1:
func animateFloatView(_ view: UIView?) {
guard let view = view else { return }
let oldYCoordinate = view.center.y
UIView.animate(withDuration: 0.6, animations: {
view.center.y -= 15
}, completion: { _ in
UIView.animate(withDuration: 0.6, animations: {
view.center.y = oldYCoordinate
}, completion: { _ in
self.animateFloatView(view)
})
})
}

iOS: swipe gesture to load different data within same controller

In my application I'm trying to implement a functionality (similar to calendar where month changes within same view) something like if current view shows the news of current day (today) then if user swipe right, it should show the next day news and on swipe left it should show previous day news.
I have so far
a single NewsViewController which shows the today news in table view
On other topics many suggest to use libraries like on this question Tom suggests Handling a view controller that slides in with either way swipe
I am new to IOS development and have no idea how to do it, any help will be greatly appreciated.
So you can try pulling data in any of the gesture methods using this cod here https://stackoverflow.com/a/20596933/1307844
Your answer is totally how you implement your gesture methods.
I found the solution and here is my code snippets...
ViewDidLoad:
UISwipeGestureRecognizer *oneFingerSwipeLeft = [[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector(oneFingerSwipeLeft:)];
[oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[viewContent addGestureRecognizer:oneFingerSwipeLeft];
UISwipeGestureRecognizer *oneFingerSwipeRight = [[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector(oneFingerSwipeRight:)];
[oneFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
[viewContent addGestureRecognizer:oneFingerSwipeRight];
oneFingerSwipeLeft:
- (void)oneFingerSwipeLeft:(UITapGestureRecognizer *)recognizer {
newsList = [[NSMutableArray alloc] init];
//to animate the view as new view is loaded
[UIView animateWithDuration:0.1 animations:^{
viewContent.frame = CGRectMake( -viewContent.frame.size.width, viewContent.frame.origin.y , viewContent.frame.size.width, viewContent.frame.size.height);
[self loadData];
} completion:^(BOOL finished) {
viewContent.frame = CGRectMake( viewContent.frame.size.width,viewContent.frame.origin.y, viewContent.frame.size.width, viewContent.frame.size.height);
[UIView animateWithDuration:0.3 animations:^{
viewContent.frame = CGRectMake(0.0, viewContent.frame.origin.y, viewContent.frame.size.width, viewContent.frame.size.height);
}];
}];
selectedDay++;
[self fetchDataFromWeb];
}
oneFingerSwipeRight:
- (void)oneFingerSwipeRight:(UITapGestureRecognizer *)recognizer {
newsList = [[NSMutableArray alloc] init];
//to animate the view as new view is loaded
[UIView animateWithDuration:0.1 animations:^{
viewContent.frame = CGRectMake( viewContent.frame.size.width, viewContent.frame.origin.y , viewContent.frame.size.width, viewContent.frame.size.height);
[self loadData];
} completion:^(BOOL finished) {
viewContent.frame = CGRectMake( -viewContent.frame.size.width,viewContent.frame.origin.y, viewContent.frame.size.width, viewContent.frame.size.height);
[UIView animateWithDuration:0.3 animations:^{
viewContent.frame = CGRectMake(0.0, viewContent.frame.origin.y, viewContent.frame.size.width, viewContent.frame.size.height);
}];
}];
selectedDay--;
[self fetchDataFromWeb];
}
Thanks to #BalramTiwari for valuable suggestion.
Here is IceFish's answer in swift:
override func viewDidLoad() {
super.viewDidLoad()
let swipeRight = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeRight.direction = UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(swipeRight)
let swipeLeft = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(swipeLeft)
and the responder:
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.Left:
UIView.animateWithDuration(0.1, animations: {
let myFrame = self.view.frame
self.view.frame = CGRectMake(-myFrame.size.width, myFrame.origin.y , myFrame.size.width, myFrame.size.height)
}, completion: {_ in
let myFrame = self.view.frame
self.view.frame = CGRectMake(myFrame.size.width, myFrame.origin.y , myFrame.size.width, myFrame.size.height)
UIView.animateWithDuration(0.3, animations: {
let myFrame = self.view.frame
self.view.frame = CGRectMake(0.0, myFrame.origin.y , myFrame.size.width, myFrame.size.height)
}, completion: nil)
})
// do left action here
case UISwipeGestureRecognizerDirection.Right:
UIView.animateWithDuration(0.1, animations: {
let myFrame = self.view.frame
self.view.frame = CGRectMake(myFrame.size.width, myFrame.origin.y , myFrame.size.width, myFrame.size.height)
}, completion: {_ in
let myFrame = self.view.frame
self.view.frame = CGRectMake(-myFrame.size.width, myFrame.origin.y , myFrame.size.width, myFrame.size.height)
UIView.animateWithDuration(0.3, animations: {
let myFrame = self.view.frame
self.view.frame = CGRectMake(0.0, myFrame.origin.y , myFrame.size.width, myFrame.size.height)
}, completion: nil)
})
// do right action here
default:
break
}
}
}

Pan, zoom, rotate UIView around anchorPoint using UIGestureRecognizer (iOS)

I'm trying to rotate, pan and zoom an UIView using UIGestureRecognizers. The recognisers are added to the superview, while the rotation,zoom etc is applied to a subview (_baseImage) this is to enable me to overlay other things on top of the subview in future while still receiving the gesture events.
The idea is that the UIView should scale/rotate around an "anchor" point on the subview underneath the two touch points as this seems the most natural. What I'm having problems with is the position of the subview after setting the anchorPoint, and also that the scale and rotation doesn't seem to use the set anchorPoint. My lack of understanding of overlapping co-ordinate systems/CGAffine transforms may be getting me into trouble. Code is pulled from various examples.
Here's my code as it stands at the moment:
-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view
{
CGPoint oldOrigin = view.frame.origin;
view.layer.anchorPoint = anchorPoint;
CGPoint newOrigin = view.frame.origin;
CGPoint transition;
transition.x = newOrigin.x - oldOrigin.x;
transition.y = newOrigin.y - oldOrigin.y;
view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}
- (void) updateTransformWithOffset: (CGPoint) translation
{
// Create a blended transform representing translation,
// rotation, and scaling
_baseImage.transform = CGAffineTransformMakeTranslation(translation.x + tx, translation.y + ty);
_baseImage.transform = CGAffineTransformRotate(_baseImage.transform, theta);
_baseImage.transform = CGAffineTransformScale(_baseImage.transform, scale, scale);
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)uigr {
if (uigr.state == UIGestureRecognizerStateBegan) {
UIView *piece = self.view;
CGPoint locationInView = [uigr locationInView:_baseImage];
myFrame = _baseImage.frame;
CGPoint newAnchor = CGPointMake( (locationInView.x / piece.bounds.size.width), (locationInView.y / piece.bounds.size.height ));
[self setAnchorPoint:newAnchor forView:_baseImage];
}
}
- (void) handlePinch: (UIPinchGestureRecognizer *) uigr
{
[self adjustAnchorPointForGestureRecognizer:uigr];
if (uigr.state == UIGestureRecognizerStateBegan) {
initScale = scale;
}
scale = initScale*uigr.scale;
[self updateTransformWithOffset:CGPointZero];
}
I've found a solution to my specific problem with the anchorPoint. I was trying to apply transforms to a UIView added in Interface Builder which caused problems with the anchor point and setting new centre point, removing then re-adding the subview seemed to fix it. If anyone has similar problems here is the final code I used on my view controller, it does scale,rotate and pan on a UIView using the touch position as centre for rotate and scale:
#import "ViewController.h"
#interface ViewController (){
CGFloat tx; // x translation
CGFloat ty; // y translation
CGFloat scale; // zoom scale
CGFloat theta; // rotation angle
CGFloat initScale ;
CGFloat initTheta ;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(handleRotation:)];
[rotationGesture setDelegate:self];
[self.view addGestureRecognizer:rotationGesture];
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinch:)];
[pinchGesture setDelegate:self];
[self.view addGestureRecognizer:pinchGesture];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[panGesture setDelegate:self];
[panGesture setMinimumNumberOfTouches:1];
[panGesture setMaximumNumberOfTouches:1];
[self.view addGestureRecognizer:panGesture];
_baseImage.transform = CGAffineTransformIdentity;
tx = 0.0f; ty = 0.0f; scale = 1.0f; theta = 0.0f;
scale = 1.0;
//removing and adding back to the view seems to fix problems with anchor point I was having, I suspect because of IB layout/scaling and constraints etc
UIView *mySuperView =_baseImage.superview;
[_baseImage removeFromSuperview];
[mySuperView addSubview:_baseImage];
}
-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)myview
{
CGPoint oldOrigin = myview.frame.origin;
myview.layer.anchorPoint = anchorPoint;
CGPoint newOrigin = myview.frame.origin;
CGPoint transition;
transition.x = (newOrigin.x - oldOrigin.x);
transition.y = (newOrigin.y - oldOrigin.y);
CGPoint myNewCenter = CGPointMake (myview.center.x - transition.x, myview.center.y - transition.y);
myview.center = myNewCenter;
}
- (void) updateTransformWithOffset: (CGPoint) translation
{
// Create a blended transform representing translation,
// rotation, and scaling
_baseImage.transform = CGAffineTransformMakeTranslation(translation.x + tx, translation.y + ty);
_baseImage.transform = CGAffineTransformRotate(_baseImage.transform, theta);
_baseImage.transform = CGAffineTransformScale(_baseImage.transform, scale, scale);
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)uigr {
if (uigr.state == UIGestureRecognizerStateBegan) {
tx =_baseImage.transform.tx;
ty =_baseImage.transform.ty;
CGPoint locationInView = [uigr locationInView:_baseImage];
CGPoint newAnchor = CGPointMake( (locationInView.x / _baseImage.bounds.size.width), (locationInView.y / _baseImage.bounds.size.height ));
[self setAnchorPoint:newAnchor forView:_baseImage];
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// if the gesture recognizers are on different views, don't allow simultaneous recognition
if (gestureRecognizer.view != otherGestureRecognizer.view)
return NO;
if (![gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && ![otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return YES;
}
return NO;
}
- (void) handleRotation: (UIRotationGestureRecognizer *) uigr
{
if (uigr.state == UIGestureRecognizerStateBegan) {
initTheta = theta;
}
theta = initTheta+uigr.rotation;
[self adjustAnchorPointForGestureRecognizer:uigr];
[self updateTransformWithOffset:CGPointZero];
}
- (void) handlePinch: (UIPinchGestureRecognizer *) uigr
{
if (uigr.state == UIGestureRecognizerStateBegan) {
initScale = scale;
}
scale = initScale*uigr.scale;
[self adjustAnchorPointForGestureRecognizer:uigr];
[self updateTransformWithOffset:CGPointZero];
}
- (void) handlePan: (UIPanGestureRecognizer *) uigr
{
CGPoint translation = [uigr translationInView:_baseImage.superview];
[self adjustAnchorPointForGestureRecognizer:uigr];
[self updateTransformWithOffset:translation];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
The OP's original solution translated to Swift 2.0. I left out the workaround since it doesn't seem to be an issue anymore.
class ViewController: UIViewController {
var tx:CGFloat = 0.0 // x translation
var ty:CGFloat = 0.0 // y translation
var scale:CGFloat = 1.0 // zoom scale
var theta:CGFloat = 0.0 // rotation angle
var initScale:CGFloat = 1.0
var initTheta:CGFloat = 0.0
var transformedView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
transformedView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
transformedView.backgroundColor = UIColor.blueColor()
view.addSubview(transformedView)
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(OHPlastyViewController.handleRotation(_:)))
rotationGesture.delegate = self
view.addGestureRecognizer(rotationGesture)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(OHPlastyViewController.handlePinch(_:)))
pinchGesture.delegate = self
view.addGestureRecognizer(pinchGesture)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(OHPlastyViewController.handlePan(_:)))
panGesture.delegate = self
panGesture.minimumNumberOfTouches = 1
panGesture.maximumNumberOfTouches = 1
view.addGestureRecognizer(panGesture)
}
func setAnchorPoint(anchorPoint: CGPoint, forView myView: UIView) {
let oldOrigin: CGPoint = myView.frame.origin
myView.layer.anchorPoint = anchorPoint
let newOrigin = myView.frame.origin
let transition = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)
let myNewCenter = CGPoint(x: myView.center.x - transition.x, y: myView.center.y - transition.y)
myView.center = myNewCenter
}
func updateTransformWithOffset(translation: CGPoint) {
transformedView.transform = CGAffineTransformMakeTranslation(translation.x + tx, translation.y + ty)
transformedView.transform = CGAffineTransformRotate(transformedView.transform, theta)
transformedView.transform = CGAffineTransformScale(transformedView.transform, scale, scale)
}
func adjustAnchorPointForGestureRecognizer(recognizer: UIGestureRecognizer) {
if (recognizer.state == .Began) {
tx = transformedView.transform.tx
ty = transformedView.transform.ty
let locationInView = recognizer.locationInView(transformedView)
let newAnchor = CGPoint(x: (locationInView.x / transformedView.bounds.size.width), y: (locationInView.y / transformedView.bounds.size.height))
setAnchorPoint(newAnchor, forView: transformedView)
}
}
func handleRotation(recognizer: UIRotationGestureRecognizer) {
if recognizer.state == .began {
initTheta = theta
}
theta = initTheta + recognizer.rotation
adjustAnchorPointForGestureRecognizer(recognizer)
updateTransformWithOffset(CGPointZero)
}
func handlePinch(recognizer: UIPinchGestureRecognizer) {
if recognizer.state == .began {
initScale = scale
}
scale = initScale * recognizer.scale
adjustAnchorPointForGestureRecognizer(recognizer)
updateTransformWithOffset(CGPointZero)
}
func handlePan(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(transformedView.superview)
adjustAnchorPointForGestureRecognizer(recognizer)
updateTransformWithOffset(translation)
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.view != otherGestureRecognizer.view {
return false
}
if !gestureRecognizer.isKindOfClass(UITapGestureRecognizer.self) && !otherGestureRecognizer.isKindOfClass(UITapGestureRecognizer.self) {
return true
}
return false
}
}

Resources