I understand the various ways of detecting drag gestures just fine (currently I'm using a UIPanGestureRecognizer), but my understanding of transformations is limited, so I'm not sure if/how this is possible. Essentially, what I want to have is a scaling transformation applied to a UIView at the same pace (for lack of a better word) as the user performs a drag gesture elsewhere on the screen. In other words, as the user drags up, I want the size of my transforming UIView to increase proportionally to the position of that gesture, and if the user then starts dragging down, it should start to shrink proportionally.
Hopefully that makes sense. As a dummy example, just imagine a slider that you can adjust to change the size of a UIView in real time. Is there a good way to make those sorts of incremental and constant size updates with CGAffineTransform?
In your pan gesture handler, you simply grab translationInView or locationInView, calculate a scale from that, and then update the transform accordingly. For example:
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
static CGAffineTransform originalTransform;
if (gesture.state == UIGestureRecognizerStateBegan)
{
originalTransform = self.viewToScale.transform;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [gesture translationInView:gesture.view];
CGFloat scale = 1.0 - translation.y / 160.0;
self.viewToScale.transform = CGAffineTransformScale(originalTransform, scale, scale);
}
}
You can play around with the scale calculation depending upon precisely what you want to do, but hopefully you get the idea.
Personally, I'd rather use the pinch gesture recognizer for resizing (it's a UI that users have been trained on, it gives you the scale factor right out of the box, etc.), but whatever works for you. If you did a pinch gesture recognizer, it might look like:
- (void)handlePinch:(UIPinchGestureRecognizer *)gesture
{
static CGAffineTransform originalTransform;
if (gesture.state == UIGestureRecognizerStateBegan)
{
originalTransform = self.viewToScale.transform;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
self.viewToScale.transform = CGAffineTransformScale(originalTransform, gesture.scale, gesture.scale);
}
}
I found the best approach is to use locationInView so that you can equate a location offset in pixels with a scale. For example, if the circle is placed in the centre of the view:
func dragDot(recognizer: UIPanGestureRecognizer) {
let locationX = recognizer.location(in: self.view).x
// Expand and contract the circle
let locationXOffset = locationX - self.view.center.x
// We need scale to be 1 when locationXOffset = circle radius
let scale: CGFloat = locationXOffset / (self.widthOfCircle / 2)
self.ring.transform = CGAffineTransform(scaleX: scale, y: scale)
}
If the circle isn't in the centre of the view then replace self.view.center.x with the initial location of the circle.
This method will work across all devices and screen resolutions and will avoid the requirement to calibrate a constant
Related
I'd like to let the user control the brightness in an iOS app using a gesture recogniser to swipe up and down. Can gestures even control brightness? I've done it before using a slider and the brightness property of UIScreen...
[UIScreen mainScreen].brightness = 0.5;
I want to be able to control the brightness as you would with a slider (like the iBooks app) but instead of a slider use a gesture recogniser. The idea is that the gesture continuously changes the brightness value as long as a touch is being recognised and the finger moves either up or down. I wasn't sure if I should use a pan gesture and limit the direction somehow to up or down, or use a swipe gesture (but obviously that detects one swipe and doesn't continuously update a value).
Perhaps moving a finger up a specified amount on the screen increments the 0.0 - 1.0 range of the brightness value. I'm not sure how to approach it so i'm just thinking out loud.
Any ideas? Thanks a lot.
EDIT: Ok so I found this code on another similar question
- (void)pan:(UIPanGestureRecognizer *)recognizer
{
if ((recognizer.state == UIGestureRecognizerStateChanged) ||
(recognizer.state == UIGestureRecognizerStateEnded))
{
CGPoint velocity = [recognizer velocityInView:self.view];
if (velocity.y >0) // panning down
{
self.brightness = self.brightness -.02;
NSLog (#"Decreasing brigntness in pan");
}
else // panning up
{
self.brightness = self.brightness +.02;
NSLog (#"Increasing brigntness in pan");
}
}
}
It seems to at least print an NSLog every time you gesture up or down, I guess I now just want to know if there's a way to use NSLog to find out the current brightness value rather than it just telling you if it detected a gesture (unless i'm being stupid and the NSLog i'm using already tells me that?). Thanks, this would be really useful to know because I can't run on the device. :-D
To Increase and decrease the brightness of the Screen
For Swift
1)How to Add PanGesture in View
let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(pan(recognizer:)))
panGesture.delegate = self
panGesture.minimumNumberOfTouches = 1
mp.view.addGestureRecognizer(panGesture)
2)Code to increase and Decrease the Brightness of Screen using PanGesture
func pan(recognizer:UIPanGestureRecognizer){
if recognizer.state == UIGestureRecognizerState.changed || recognizer.state == UIGestureRecognizerState.ended {
let velocity:CGPoint = recognizer.velocity(in: self.view)
if velocity.y > 0{
var brightness: Float = Float(UIScreen.main.brightness)
brightness = brightness - 0.03
UIScreen.main.brightness = CGFloat(brightness)
print("Decrease brigntness in pan")
}
else {
var brightness: Float = Float(UIScreen.main.brightness)
brightness = brightness + 0.03
UIScreen.main.brightness = CGFloat(brightness)
print("Increase brigntness in pan")
}
}
}
Yes! Just use a UIPanGestureRecognizer and adjust the brightness value accordingly as the pan gesture is called. Check the Apple docs.
I have an image view that I want to pinch to rescale without keeping the aspect ratio. In order to do this, I thought it might be feasible to either:
Use two pinch gesture recognisers, one that stretches horizontally, one that does so vertically.
Use one pinch recogniser but apply the two transforms one after the other.
Here's my pinch handling function:
- (void) pinch:(UIPinchGestureRecognizer *)recognizer {
static CGRect initialBounds;
if (recognizer.state == UIGestureRecognizerStateBegan)
{
initialBounds = imageView.bounds;
}
CGFloat factor = [(UIPinchGestureRecognizer *)recognizer scale];
//scale horizontally
CGAffineTransform zt = CGAffineTransformScale(CGAffineTransformIdentity,
factor-(1.0-factor), 1.0);
imageView.bounds = CGRectApplyAffineTransform(initialBounds, zt);
//now scale vertically
zt = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, factor);
imageView.bounds = CGRectApplyAffineTransform(initialBounds, zt);
return;
}
For some reason, the transform is only being done vertically (last one). I tried changing the first parameter of the second CGRectApplyAffineTransform to imageView.bounds, but it still didn't work.
Can anyone please tell me where I am going wrong?
Also, when using two pinch gesture recognisers, the same thing happens - only one of them actually gets recognised.
Thanks!
Your second one is starting with a CGAffineTransformIdentity. Instead, pass in the zt.
I'm using a pinch gesture to let users increase/decrease the size of an image. I temporarily manipulate that CGAffineTransform to let the user play with the scale, then just undo it and set the frame size so that image scales with quality (CGAffineTransformScale does nothing for image quality and will only stretch out the image).
-(void)handlePinch:(UIPinchGestureRecognizer *)recognizer{
if(recognizer.state == UIGestureRecognizerStateBegan){
self.alpha = 0.7;
startingTransform = self.transform;
}
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
recognizer.scale = 1;
if (recognizer.state == UIGestureRecognizerStateEnded){
self.alpha = 1.0;
CGRect endFrame = self.frame;
self.transform = startingTransform;
self.frame = endFrame;
//self.transform = CGAffineTransformRotate(self.transform, acos(startingTransform.c)); //reapply rotation?
}
}
The issue: The problem with my code is that it does not account for rotation. Some of my images start out rotated (with CGAffineTransformRotate being previously applied to them) and what happens when the pinch gesture finishes is that the imageview warps out into really thin rectangles, devoid of any rotation or scale they were before.
I thought I could carry over the original rotation and apply it after the frame was set, but I don't think that works. Any help is appreciated. Thanks
Note that the UIView documentation says that as soon as a UIViews transform is not its identity transform, the frame property becomes undefined and should not be used, so do not rely on CGRect endFrame = ... to work.
Otherwise, if you want the quality to change, just set the transform to identity, scale the frame and then reapply the transform to keep the rotation. That should work fine.
add imageview as subview of scrollview, and then specify the delegate of scrolview which asks for viewtobezoomed as the imageview. it will easi.y solve it.
http://developer.apple.com/library/ios/documentation/uikit/reference/uiscrollviewdelegate_protocol/Reference/UIScrollViewDelegate.html#//apple_ref/occ/intfm/UIScrollViewDelegate/viewForZoomingInScrollView:
I'm trying to create a knob-like behavior in one of my views with a UIRotationGestureRecognizer. This works, and positions the view as expected. However, every single time a gesture is performed, the rotation of the recognizer resets, so the knob starts at 0 every time.
How can I remember the last rotation of the UIRotationGestureRecognizer to let the user adjust the knob UIView without resetting it every single time?
I'm trying to make the recognizer start calculating rotation changes from the view's last known rotation:
knob starts at 0, recognizer is at 0
recognizer is rotated to 45 degrees
recognizer stops rotating
the knob is left at 45 degrees //this is already happening with the provided code snippet
next touch:
//this is what's is happening
recognizer starts at 0, rotates the knob back to 0
//I want it to be:
recognizer starts at 45, rotates the knob as in the example above.
- (IBAction)rotateView:(id)sender {
if([sender isKindOfClass:[UIRotationGestureRecognizer class]])
{
UIRotationGestureRecognizer* recognizer = sender;
CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]);
rotatingView.transform = transform;
}
}
You should be able to get the current rotation of the rotatingView from it's transform property. Store this value into a savedRotation variable when the gesture begins. Make sure to assign a delegate to handle the gestureRecognizerShouldBegin callback.
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)recognizer
{
savedRotation = atan2(rotatingView.transform.b, rotatingView.transform.a);
return YES;
}
- (void)rotateView:(UIRotationGestureRecognizer*)recognizer
{
rotatingView.transform = CGAffineTransformMakeRotation(recognizer.rotation+savedRotation);
}
Transform the transform:
rotatingView.transform = CGAffineTransformRotate(rotatingView.transform, [recognizer rotation]);
You have to make sure that you reset the rotation after the transform. Otherwise they will stack on top of each other and you get the "interesting" behavior.
rotatingView.transform = CGAffineTransformScale(rotatingView.transform, recognizer.scale, recognizer.scale);
[recognizer setRotation:0]; // this line
You should also do this for any translation or scaling transformations that you may do when handling gestures. The methods there are:
[recognizer setScale:1];
[recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
I have a UIView that I rotate with this code:
helpView.transform = CGAffineTransformMakeRotation(degreesToRadians( rotationAngle ));
Where degreeToRadians just is a macro to convert from degrees to radians.
This works fine as long as the view is visible, eg alpha = 1. When I hide it (alpha = 0, which I animate) it does not rotate any more. I guess this is a smart way for the devices to "save" on drawing time, but is there any way I can force it to be drawn even when alpha is 0? Otherwise I will have to rotate it before I show it again.
Any good ideas?
Thanks
Edit: This is the code I use to show/hide the view.
-(void)showHelp
{
bool helpAlpha = !helpView.alpha;
CGFloat newScale;
if (helpView.alpha) {
newScale = kHelpSmall;
helpView.transform = CGAffineTransformMakeScale(kHelpBig, kHelpBig);
} else {
newScale = kHelpBig;
helpView.transform = CGAffineTransformMakeScale(kHelpSmall, kHelpSmall);
}
[UIView animateWithDuration:(kAnimationTimeShort / 2) animations:^(void) {
[helpView setAlpha:helpAlpha];
helpView.transform = CGAffineTransformMakeScale(newScale, newScale);
}];
}
As you see I also scale it for a nicer effect. Works perfect when visible, does not rotate when alpha = 0. Rotation is done in another method, where I would prefer to keep it as I also rotate some other views there.
You are resetting the transform every time you use CGAffineTransformMake*. If you do this, you will get either a rotated transform or a scaled one. I am assuming the scaled one is after the rotated one and hence you aren't able to see the view rotated. If you need both the effects to remain, you will have to use CGAffineTransformRotate. So a scale and rotate will be
helpView.transform = CGAffineTransformMakeScale(kHelpSmall, kHelpSmall);
helpView.transform = CGAffineTransformRotate(helpView.transform, degreesToRadians(rotationAngle));
The order might vary.