UIScrollView Adjust Bounciness - ios

Is it possible to adjust the amount of bounce in a UIScrollView?
For example, if dragging the scroll view 100pt past the end of the content moves the scroll view by 50pt, I'd like to be able to reduce the distance travelled by the same drag to 20pt.
I have experimented with a few things without success.
Adjusting the contentOffset in the scrollViewDidScroll: delegate method does not seem possible because you lose the original scrolling position when the value is set and the content offset approaches zero.
Adding a transform which translates the scroll view by some fraction of the content offset in reverse seems like a good idea. This breaks all the touch tracking and causes a mess of problems. Additionally, in many other situations, moving the scroll view would break the layout of a view.

One thing you can do is to make your own scroll view with UIView + UIPanGestureRecognizer.
(Kind of an over-kill, depending on how bad you want this custom effect.)
Here's a good explanation of Apple's rubber band algorithm.
The core of it is:
b = (1.0 – (1.0 / ((x * c / d) + 1.0))) * d
where:
x = distance from the edge
c = constant value, UIScrollView uses 0.55
d = dimension, either width or height
You can then play with the value of c and x to get the effect you desired.
Some code snippet to get you started:
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
static CGRect beginRect;
CGPoint translation = [sender translationInView:sender.view.superview];
CGPoint velocity = [sender velocityInView:sender.view.superview];
CGRect currentRect = CGRectOffset(beginRect, 0, translation.y);
if (sender.state == UIGestureRecognizerStateBegan) {
beginRect = sender.view.frame;
}
else if (sender.state == UIGestureRecognizerStateChanged) {
if (currentRect.origin.y > self.naturalHidePosition) {
CGFloat distanceFromEdge = currentRect.origin.y - self.naturalHidePosition;
CGFloat height = CGRectGetHeight(sender.view.frame);
CGFloat b = [self rubberBandDistance:distanceFromEdge dimension:height];
currentRect.origin.y = self.naturalHidePosition + b;
}
else if (currentRect.origin.y < self.naturalShowPosition) {
CGFloat distanceFromEdge = self.naturalShowPosition - currentRect.origin.y;
CGFloat height = CGRectGetHeight(sender.view.frame);
CGFloat b = [self rubberBandDistance:distanceFromEdge dimension:height];
currentRect.origin.y = self.naturalShowPosition - b;
}
sender.view.frame = currentRect;
}
else if (sender.state == UIGestureRecognizerStateEnded) {
if (velocity.y < 0) {
currentRect.origin.y = self.naturalShowPosition;
}
else if (velocity.y > 0) {
currentRect.origin.y = self.naturalHidePosition;
}
else if (currentRect.origin.y > (0.5 * (self.naturalShowPosition + self.naturalHidePosition))) {
currentRect.origin.y = self.naturalHidePosition;
}
else {
currentRect.origin.y = self.naturalShowPosition;
}
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
sender.view.frame = currentRect;
} completion:nil];
}
}
- (CGFloat)rubberBandDistance:(CGFloat)distanceFromEdge dimension:(CGFloat)d
{
CGFloat c = 0.55;
CGFloat b = (1.0 - (1.0 / ((distanceFromEdge * c / d) + 1.0))) * d;
return b;
}

Related

How to pinch google map when it set in UIScrollView in iOS Objective C

I set Google Map in view in UIScrollView. everything OK but i can not pinch my map. How to fix it?
Salaam Reza jaan.
In your case you can use something like this. Cheers.
Edit: this is a better way to do it. Behbakht shid, direh inja :P
- (void) scaleSelfWith:(UIPinchGestureRecognizer *)gestureRecognizer{
if ([gestureRecognizer numberOfTouches] >1) {
//getting width and height between gestureCenter and one of my finger
float x = [gestureRecognizer locationInView:self].x - [gestureRecognizer locationOfTouch:1 inView:self].x;
if (x<0) {
x *= -1;
}
float y = [gestureRecognizer locationInView:self].y - [gestureRecognizer locationOfTouch:1 inView:self].y;
if (y<0) {
y *= -1;
}
//set Border
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
xDis = self.bounds.size.width - x*2;
yDis = self.bounds.size.height - y*2;
}
//double size cause x and y is just the way from the middle to my finger
float width = x*2+xDis;
if (width < 1) {
width = 1;
}
float height = y*2+yDis;
if (height < 1) {
height = 1;
}
self.bounds = CGRectMake(self.bounds.origin.x , self.bounds.origin.y , width, height);
[gestureRecognizer setScale:1];
[[self layer] setBorderWidth:2.f];
}
}

view slide (follow finger position vertically up/down) with uipangesturerecognizer and auto layout

this is a very simple problem but i can't figure it out. first, here is how the views are added as sub view and I'm using Autolayout. the (black) root view contains blue colored and orange coloured views. the orange coloured view is the tops most which partly covers the blue view. the button in orange view has pan gesture recognizer. i got it working if gestures ends then orange view positions itself properly.( either just like in the photo covering the blue view, or orange can be slided down till only the orange is visible.) the orange view's position is only changed vertically using autolayout constant.
the issue I'm facing is that if one pans up and down UIGestureRecognizerStateChanged then it happens that the orange view slides to the opposite direction of panning. how to change the auto layout constant properly? the orange view's range of vertical movement is from starting point like in the sketch downward till that orange button remains visible.
#define MIN_SIZE 50
-(void)handlePan:(UIPanGestureRecognizer *) pan
{
CGFloat viewHeight = pan.view.superview.height; //superView is the big orange view
if (pan.state == UIGestureRecognizerStateBegan)
{
self.startLocation = [pan locationInView:self.view];
}
if (pan.state == UIGestureRecognizerStateChanged)
{
CGPoint stopLocation = [pan locationInView:self.view];
CGFloat dist = sqrt(pow((stopLocation.x - self.startLocation.x), 2) + pow((stopLocation.y - self.startLocation.y), 2));
if (velocityOfPan.y > 0)
{
if (self.verticalConstraint.constant >= viewHeight - MIN_SIZE)
{
self.verticalConstraint.constant = viewHeight - MIN_SIZE;
}
else
self.verticalConstraint.constant = dist;
}
else if(velocityOfPan.y < 0)
{
if (self.verticalConstraint.constant <= 0)
{
self.verticalConstraint.constant = 0;
}
else
{
self.verticalConstraint.constant = (viewHeight - MIN_SIZE - dist);
}
}
}
else if(pan.state == UIGestureRecognizerStateEnded)
{
if (velocityOfPan.y > 0)
{
self.verticalConstraint.constant = viewHeight - MIN_SIZE;
}
else if (velocityOfPan.y < 0)
{
self.verticalConstraint.constant = 0;
}
}
[self updateViewAnimation];
}
}
-(void) updateViewAnimation
{
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:-1 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
after some searching and reading I found out what I was doing wrong. Here is how I solved it, just in case somebody else wanna know it.
-(void)(UIPanGestureRecognizer *) pan
{
CGPoint velocityOfPan = [pan velocityInView:self.view];
CGFloat viewHeight = pan.view.superview.height;
CGPoint delta;
switch(pan.state)
{
case UIGestureRecognizerStateBegan:
self.startLocation = delta = [pan translationInView:self.view];
break;
case UIGestureRecognizerStateChanged:
{
delta = CGPointApplyAffineTransform([pan translationInView:self.view], CGAffineTransformMakeTranslation(-self.startLocation.x, -self.startLocation.y));
self.startLocation = [pan translationInView:self.view];
if(delta.y < 0 && self.verticalConstraint.constant < 0)
{
delta.y = 0;
}
else
{
delta.y = self.verticalConstraint.constant + delta.y;
}
self.verticalConstraint.constant = delta.y;
[self updateViewAnimation];
break;
}
case UIGestureRecognizerStateEnded:
{
if (velocityOfPan.y > 0)
{
delta.y = (viewHeight - MIN_SIZE);
}
else if(velocityOfPan.y < 0)
{
delta.y = 0;
}
self.verticalConstraint.constant = delta.y;
[self updateViewAnimation];
break;
}
default:
break;
}
}

how to move one direction in UIScrollView when scrolling

I'm new in objective-c. I create UIScrollView object and add in my view with this code:
height = self.view.frame.size.height;
width = self.view.frame.size.width;
scrollbar = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
scrollbar.delegate = self;
scrollbar.backgroundColor = [UIColor whiteColor];
scrollbar.maximumZoomScale = 1.0;
scrollbar.minimumZoomScale = 1.0;
scrollbar.clipsToBounds = YES;
scrollbar.showsHorizontalScrollIndicator = YES;
scrollbar.pagingEnabled = YES;
[scrollbar setContentSize:CGSizeMake(width*4, height*4)];
[self.view addSubview:scrollbar];
for (int i = 1; i <= 4; i++) {
first = [[FirstViewController alloc]initWithNibName:#"FirstViewController" bundle:nil];
first.view.frame = CGRectMake((i-1)*width, 0, width, height*4);
[scrollbar addSubview:first.view];
switch (i) {
ase 1:
first.view.backgroundColor = [UIColor blueColor];
break;
case 2:
first.view.backgroundColor = [UIColor redColor];
break;
case 3:
first.view.backgroundColor = [UIColor greenColor];
break;
case 4:
first.view.backgroundColor = [UIColor grayColor];
break;
default:
break;
}
}
in my code I add 4 view in my ScrollView with different color now I want when scrolling on my ScrollView detect dx & dy (dx: driving distance on Axis.x & dy:driving distance on Axis.y) and check these two variable and when :
Notic: I want when any one touch on ScrollView and moving touch on Axis (x or y) or touch on Both Axis (x and y) check this :
if (dx > dy) disable horizontal scroll and moving in vertical side!!!
else moving in horizontal side and disable vertical scroll!!!
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGRect visibleRect = CGRectMake(scrollView.contentOffset.x, scrollView.contentOffset.y, scrollView.contentOffset.x + scrollView.bounds.size.width, scrollView.contentOffset.y + scrollView.bounds.size.height);
NSLog(#"%f,%f",visibleRect.origin.x,visibleRect.origin.y);
/*NSLog(#"x : %f",scrollView.contentOffset.x);
NSLog(#"y : %f",scrollView.contentOffset.y);*/
if (fabsf(scrollView.contentOffset.x) > fabsf(scrollView.contentOffset.y)) {
NSLog(#"Vertical Side");
}
else {
NSLog(#"Horizontal Side");
}
}
please guide me guys. I can't disable one side and move another side!!! thanks
If i understood you right, you want to disable scrolling in the direction that has been dragged less by the user. If so, UIScrollView already offers an option to do so:
scrollView.directionalLockEnabled = YES;
When this option is set to true UIScrollView will handle the correct direction lock by itself.
For more information look at the documentation: link
You can achieve the result you want adding some code to your delegate. This is my ViewController.m file. -viewDidLoad, #import statements and the other methods are omitted.
#interface ViewController () {
CGPoint initialOffset;
NSInteger direction; // 0 undefined, 1 horizontal, 2 vertical
}
#end
#implementation ViewController
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// retrieve current offset
CGPoint currentOffset = scrollView.contentOffset;
// do we know which is the predominant direction?
if (direction == 0) {
// no.
CGFloat dx = currentOffset.x - initialOffset.x;
CGFloat dy = currentOffset.y - initialOffset.y;
// we need to decide in which direction we are moving
if (fabs(dx) >= fabs(dy)) {
direction = 1; // horizontal
} else if (fabs(dy) > fabs(dx)) {
direction = 2;
}
}
// ok now we have the direction. update the offset if necessary
if (direction == 1 && currentOffset.y != initialOffset.y) {
// remove y offset
currentOffset.y = initialOffset.y;
// update
[scrollView setContentOffset:currentOffset];
} else if (direction == 2 && currentOffset.x != initialOffset.x) {
currentOffset.x = initialOffset.x;
[scrollView setContentOffset:currentOffset];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// store the current offset
initialOffset = scrollView.contentOffset;
// reset flag
direction = 0; // AKA undefined
}
#end
When you start dragging, the delegate will reset the flag direction to "unknown" state, and store the current content offset. After every dragging move, your -scrollViewDidScroll: will be called. There, you decide which is the predominant direction (if this hasn't been done yet) and correct the current scrolling offset accordingly, by removing the x (or y) offset.
I tested this with the same settings you provided, only I used a UIImageView inside UIScrollView and I set up everything via InterfaceBuilder, but it should work fine. Theoretically, with this method you could replace directionLock, but remember that -scrollViewDidScroll is called many times during an action, and every time it rewrites the content offset (if the scrolling is happening in both directions). So if you leave directionLock enabled, you save many of the calls to setContentOffset: that the delegate performs.

Use UIPinchGestureRecognizer to scale view in direction of pinch [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I needed a Pinch Recognizer that would scale in x, or y, or both directions depending on the direction of the pinch. I looked through many of the of the other questions here and they only had parts of the answer. Here's my complete solution that uses a custom UIPinchGestureRecognizer.
I created a custom version of a UIPinchGestureRecognizer. It uses the slope of line between the two fingers to determine the direction of the scale. It does 3 types: Vertical; Horizontal; and Combined(diagonal). Please see my notes at the bottom.
-(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer
{
if ([pinchRecognizer state] == UIGestureRecognizerStateBegan || [pinchRecognizer state] == UIGestureRecognizerStateChanged) {
if ([pinchRecognizer numberOfTouches] > 1) {
UIView *theView = [pinchRecognizer view];
CGPoint locationOne = [pinchRecognizer locationOfTouch:0 inView:theView];
CGPoint locationTwo = [pinchRecognizer locationOfTouch:1 inView:theView];
NSLog(#"touch ONE = %f, %f", locationOne.x, locationOne.y);
NSLog(#"touch TWO = %f, %f", locationTwo.x, locationTwo.y);
[scalableView setBackgroundColor:[UIColor redColor]];
if (locationOne.x == locationTwo.x) {
// perfect vertical line
// not likely, but to avoid dividing by 0 in the slope equation
theSlope = 1000.0;
}else if (locationOne.y == locationTwo.y) {
// perfect horz line
// not likely, but to avoid any problems in the slope equation
theSlope = 0.0;
}else {
theSlope = (locationTwo.y - locationOne.y)/(locationTwo.x - locationOne.x);
}
double abSlope = ABS(theSlope);
if (abSlope < 0.5) {
// Horizontal pinch - scale in the X
[arrows setImage:[UIImage imageNamed:#"HorzArrows.png"]];
arrows.hidden = FALSE;
// tranform.a = X-axis
NSLog(#"transform.A = %f", scalableView.transform.a);
// tranform.d = Y-axis
NSLog(#"transform.D = %f", scalableView.transform.d);
// if hit scale limit along X-axis then stop scale and show Blocked image
if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) {
blocked.hidden = FALSE;
arrows.hidden = TRUE;
} else {
// scale along X-axis
scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, 1.0);
pinchRecognizer.scale = 1.0;
blocked.hidden = TRUE;
arrows.hidden = FALSE;
}
}else if (abSlope > 1.7) {
// Vertical pinch - scale in the Y
[arrows setImage:[UIImage imageNamed:#"VerticalArrows.png"]];
arrows.hidden = FALSE;
NSLog(#"transform.A = %f", scalableView.transform.a);
NSLog(#"transform.D = %f", scalableView.transform.d);
// if hit scale limit along Y-axis then don't scale and show Blocked image
if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1))) {
blocked.hidden = FALSE;
arrows.hidden = TRUE;
} else {
// scale along Y-axis
scalableView.transform = CGAffineTransformScale(scalableView.transform, 1.0, pinchRecognizer.scale);
pinchRecognizer.scale = 1.0;
blocked.hidden = TRUE;
arrows.hidden = FALSE;
}
} else {
// Diagonal pinch - scale in both directions
[arrows setImage:[UIImage imageNamed:#"CrossArrows.png"]];
blocked.hidden = TRUE;
arrows.hidden = FALSE;
NSLog(#"transform.A = %f", scalableView.transform.a);
NSLog(#"transform.D = %f", scalableView.transform.d);
// if we have hit any limit don't allow scaling
if ((((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) || (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1)))) {
blocked.hidden = FALSE;
arrows.hidden = TRUE;
} else {
// scale in both directions
scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, pinchRecognizer.scale);
pinchRecognizer.scale = 1.0;
blocked.hidden = TRUE;
arrows.hidden = FALSE;
}
} // else for diagonal pinch
} // if numberOfTouches
} // StateBegan if
if ([pinchRecognizer state] == UIGestureRecognizerStateEnded || [pinchRecognizer state] == UIGestureRecognizerStateCancelled) {
NSLog(#"StateEnded StateCancelled");
[scalableView setBackgroundColor:[UIColor whiteColor]];
arrows.hidden = TRUE;
blocked.hidden = TRUE;
}
}
Remember to add the protocol to the view controller header file:
#interface WhiteViewController : UIViewController <UIGestureRecognizerDelegate>
{
IBOutlet UIView *scalableView;
IBOutlet UIView *mainView;
IBOutlet UIImageView *arrows;
IBOutlet UIImageView *blocked;
}
#property (strong, nonatomic) IBOutlet UIView *scalableView;
#property (strong, nonatomic) IBOutlet UIView *mainView;
#property (strong, nonatomic)IBOutlet UIImageView *arrows;
#property (strong, nonatomic)IBOutlet UIImageView *blocked;
-(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer;
#end
And add the recognizer in the viewDidLoad:
- (void)viewDidLoad
{
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(scaleTheView:)];
[pinchGesture setDelegate:self];
[mainView addGestureRecognizer:pinchGesture];
arrows.hidden = TRUE;
blocked.hidden = TRUE;
[scalableView setBackgroundColor:[UIColor whiteColor]];
}
This is set up to use the main view to capture the pinch; and manipulate a second view. This way you can still scale it as the view gets small. You can change it to react directly to the scalable view.
LIMITS: I arbitrarily chose the starting size of my view so a scale limit of 2.0 would equal full screen. My lower scale is set at 0.1.
USER INTERACTION: I mess around with a lot of user interaction things like changing the view's background color and adding/changing arrows over the view to show direction. It's important to give them feedback during the scaling process, especially when changing directions like this codes allows.
BUG: There is a bug in Apple's UIPinchGestureRecognizer. It registers UIGestureRecognizerStateBegan with the touch of 2 fingers as you would expect. But once it is in StateBegan or StateChanged you can lift one finger and the state remains. It doesn't move to StateEnded or StateCancelled until BOTH fingers are lifted. This created a bug in my code and many headaches! The if numberOfTouches > 1 fixes it.
FUTURE: You can change the slope settings to scale in just one direction, or just 2. If you add the arrows images, you can see them change as you rotate your fingers.
Here's a solution in Swift:
extension UIPinchGestureRecognizer {
func scale(view: UIView) -> (x: CGFloat, y: CGFloat)? {
if numberOfTouches() > 1 {
let touch1 = self.locationOfTouch(0, inView: view)
let touch2 = self.locationOfTouch(1, inView: view)
let deltaX = abs(touch1.x - touch2.x)
let deltaY = abs(touch1.y - touch2.y)
let sum = deltaX + deltaY
if sum > 0 {
let scale = self.scale
return (1.0 + (scale - 1.0) * (deltaX / sum), 1.0 + (scale - 1.0) * (deltaY / sum))
}
}
return nil
}
}
This alternative solution determines the direction of scaling based on bearing angle rather than slope. I find it a bit easier to adjust the different zones using angle measurements.
#objc func viewPinched(sender: UIPinchGestureRecognizer) {
// Scale the view either vertically, horizontally, or diagonally based on the axis of the initial pinch
let locationOne = sender.location(ofTouch: 0, in: sender.view)
let locationTwo = sender.location(ofTouch: 1, in: sender.view)
let diffX = locationOne.x - locationTwo.x
let diffY = locationOne.y - locationTwo.y
// Break the plane into 3 equal segments
// Inverse tangent will return between π/2 and -π/2. Absolute value can be used to only consider 0 to π/2 - don't forget to handle divide by 0 case
// Breaking π/2 into three equal pieces, we get regions of 0 to π/6, π/6 to 2π/6, and 2π/6 to π/2 (note 2π/6 = π/3)
// Radian reminder - π/2 is 90 degreees :)
let bearingAngle = diffY == 0 ? CGFloat.pi / 2.0 : abs(atan(diffX/diffY))
if sender.state == .began {
// Determine type of pan based on bearing angle formed by the two touch points.
// Only do this when the pan begins - don't change type as the user rotates their fingers. Require a new gesture to change pan type
if bearingAngle < CGFloat.pi / 6.0 {
panType = .vertical
} else if bearingAngle < CGFloat.pi / 3.0 {
panType = .diagonal
} else if bearingAngle <= CGFloat.pi / 2.0 {
panType = .horizontal
}
}
// Scale the view based on the pan type
switch panType {
case .diagonal: transform = CGAffineTransform(scaleX: sender.scale, y: sender.scale)
case .horizontal: transform = CGAffineTransform(scaleX: sender.scale, y: 1.0)
case .vertical: transform = CGAffineTransform(scaleX: 1.0, y: sender.scale)
}
}

Drag separator to resize UIViews

What would be to the best way of implementing an interface which consists of UIViews which are separated by a line, and the line can resize the views?
In it's simplest form, it could look like this:
----------------
| |
| View A |
| |
|--------------| < line which can be moved up and down, resizing the views
| |
| View B |
| |
----------------
It could have many more views.
My first thought would be making the line a draggable UIView with something like Touches, which resized the views according to it's position, but I'm sure there must be a more elegant solution.
First, define a gesture that detects whether you started on a border, and if the gesture changes, moves said borders:
#import <UIKit/UIGestureRecognizerSubclass.h>
- (void)viewDidLoad
{
[super viewDidLoad];
// I use long press gesture recognizer so it's recognized immediately
UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
gesture.minimumPressDuration = 0.0;
gesture.allowableMovement = CGFLOAT_MAX;
gesture.delegate = self;
[self.containerView addGestureRecognizer:gesture];
}
- (void)handlePan:(UILongPressGestureRecognizer *)gesture
{
static NSArray *matches;
static CGPoint firstLocation;
if (gesture.state == UIGestureRecognizerStateBegan)
{
firstLocation = [gesture locationInView:gesture.view];
matches = [BorderBeingDragged findBordersBeingDraggedForView:gesture.view fromLocation:firstLocation];
if (!matches)
{
gesture.state = UIGestureRecognizerStateFailed;
return;
}
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint location = [gesture locationInView:gesture.view];
CGPoint translation = CGPointMake(location.x - firstLocation.x, location.y - firstLocation.y);
[BorderBeingDragged dragBorders:matches translation:translation];
}
}
// if your subviews are scrollviews, you might need to tell the gesture recognizer
// to allow simultaneous gestures
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return TRUE;
}
Second, define a BordersBeingDragged class that does the detection of borders and the changing of borders:
typedef enum NSInteger {
kBorderTypeNone = 0,
kBorderTypeLeft = 1 << 0,
kBorderTypeRight = 1 << 1,
kBorderTypeTop = 1 << 2,
kBorderTypeBottom = 1 << 3
} BorderType;
#interface BorderBeingDragged : NSObject
#property (nonatomic, weak) UIView *view;
#property (nonatomic) BorderType borderTypes;
#property (nonatomic) CGRect originalFrame;
#end
static CGFloat const kTolerance = 15.0;
#implementation BorderBeingDragged
+ (NSArray *)findBordersBeingDraggedForView:(UIView *)view fromLocation:(CGPoint)point
{
NSMutableArray *matches = nil;
for (UIView *subview in view.subviews)
{
BorderType types = kBorderTypeNone;
CGRect frame = subview.frame;
// test top and bottom borders
if (point.x >= (frame.origin.x - kTolerance) &&
point.x <= (frame.origin.x + frame.size.width + kTolerance))
{
if (point.y >= (frame.origin.y - kTolerance) && point.y <= (frame.origin.y + kTolerance))
types |= kBorderTypeTop;
else if (point.y >= (frame.origin.y + frame.size.height - kTolerance) && point.y <= (frame.origin.y + frame.size.height + kTolerance))
types |= kBorderTypeBottom;
}
// test left and right borders
if (point.y >= (frame.origin.y - kTolerance) &&
point.y <= (frame.origin.y + frame.size.height + kTolerance))
{
if (point.x >= (frame.origin.x - kTolerance) && point.x <= (frame.origin.x + kTolerance))
types |= kBorderTypeLeft;
else if (point.x >= (frame.origin.x + frame.size.width - kTolerance) && point.x <= (frame.origin.x + frame.size.width + kTolerance))
types |= kBorderTypeRight;
}
// if we found any borders, add it to our array of matches
if (types != kBorderTypeNone)
{
if (!matches)
matches = [NSMutableArray array];
BorderBeingDragged *object = [[BorderBeingDragged alloc] init];
object.borderTypes = types;
object.view = subview;
object.originalFrame = frame;
[matches addObject:object];
}
}
return matches;
}
+ (void)dragBorders:(NSArray *)matches translation:(CGPoint)translation
{
for (BorderBeingDragged *object in matches)
{
CGRect newFrame = object.originalFrame;
if (object.borderTypes & kBorderTypeLeft)
{
newFrame.origin.x += translation.x;
newFrame.size.width -= translation.x;
}
else if (object.borderTypes & kBorderTypeRight)
{
newFrame.size.width += translation.x;
}
if (object.borderTypes & kBorderTypeTop)
{
newFrame.origin.y += translation.y;
newFrame.size.height -= translation.y;
}
else if (object.borderTypes & kBorderTypeBottom)
{
newFrame.size.height += translation.y;
}
object.view.frame = newFrame;
}
}
#end
You do essentially need to make the line-view draggable, but it doesn't need to be complicated.
Put viewA and viewB into a containerView
Add a pan gesture recognizer to the containerView configured for a single touch and set its delegate to your controller.
Implement gestureRecognizerShouldBegin: from the UIGestureRecognizerDelegate protocol and only allow it to begin if the touch in in the vicinity of the line-view.
In the gesture handler get the touch position in the containerView and set the line-view position and frames for viewA and viewB
That's pretty much it.
I suggest other behavior:
1. Press and hold 2 sec on line
2. Appears some imageView which you will drag
simplest way is to add gesture recognizer to your views and resize them according to the pan.

Resources