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.
viewA
and viewB
into a containerView
containerView
configured for a single touch and set its delegate to your controller.gestureRecognizerShouldBegin:
from the UIGestureRecognizerDelegate
protocol and only allow it to begin if the touch in in the vicinity of the line-view.containerView
and set the line-view position and frames for viewA
and viewB
That's pretty much it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With