iOS - 使用触摸拖动分隔符调整多个视图的大小 [英] iOS - Resize multiple views with touch-drag separators

查看:185
本文介绍了iOS - 使用触摸拖动分隔符调整多个视图的大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何使用分隔符调整视图大小?我想做的是像



如何完成此操作?我从哪里开始?

解决方案

有很多方法可以实现这一目标,但像Avinash一样,我建议创建一个分隔符视图在各种内容 UIView 对象之间。然后你可以拖动它。但是,这里的诀窍是你可能希望分隔符视图大于窄的可见线,这样它不仅会在分隔线上捕获触摸,而且也会接近它。



与你引用的其他答案不同,现在我新推荐使用autolayout,这样你只需要更新分隔符视图的位置(例如更新顶部约束)的分隔符视图),然后所有其他视图将自动调整大小。我还建议在子视图的大小上添加一个低优先级约束,这样当你第一次设置所有内容并开始拖动分隔符之前它们就会很好地布局,但是当拖动的分隔符指示时它会优雅地失败相邻视图的大小必须改变。



最后,虽然我们历史上使用手势识别器来做这样的事情,随着iOS 9预测触摸的出现,我建议只执行 touchesBegan touchesMoved 等。使用预测触摸,你不会注意到差异模拟器或旧设备,但是当你在能够预测接触的设备上运行它时(例如iPad Pro和其他新设备等新设备),你将获得更具响应性的用户体验。



因此水平分隔符视图类可能如下所示。

  static CGFloat const kTotalHeight = 44; //分隔符的总高度(包括不可见的部分
static CGFloat const kVisibleHeight = 2; //分隔符可见部分的高度
static CGFloat const kMargin =(kTotalHeight - kVisibleHeight )/ 2.0; //分隔符不可见部分的高度(即可见部分的上方和下方)
static CGFloat const kMinHeight = 10; //分隔符上方和下方视图允许的最小高度

/ **水平分隔符视图

@note这会呈现分隔符视图,但视图大于您在设备上看到的可见分隔符
行因此,当用户在可见分隔符附近触摸时,它可以接收触摸。当
试图触摸非常窄的东西时,你总是希望允许一些余量,例如分隔线。
* /

@interface Horizo​​ntalSeparatorView:UIView

@proper ty(非原子,强)NSLayoutConstraint * topConstraint; //决定分隔符垂直位置的约束
@property(非原子,弱)UIView * firstView; //分隔符上方的视图
@property(非原子,弱)UIView * secondView; //分隔符下面的视图

//用于处理触摸的一些属性

@property(nonatomic)CGFloat oldY; //手势开始前分隔符的位置
@property(nonatomic)CGPoint firstTouch; //拖动手势开始的位置

@end

@implementation Horizo​​ntalSeparatorView

#pragma mark - 配置

/ **在视图之间添加一个分隔符

这将创建分隔符视图;将其添加到视图层次结构中;添加高度约束;
为其superview添加了前导/尾随的约束;并添加约束
与上方和下方视图的关系

@param firstView分隔符上方的UIView
@param secondView分隔符下方的UIView
@returns分隔符UIView
* /

+(instancetype)addSeparatorBetweenView:(UIView *)firstView secondView:(UIView *)secondView {
Horizo​​ntalSeparatorView * separator = [[self alloc] init ]。
[firstView.superview addSubview:separator];
separator.firstView = firstView;
separator.secondView = secondView;

[NSLayoutConstraint activateConstraints:@ [
[separator.heightAnchor constraintEqualToConstant:kTotalHeight],
[separator.superview.leadingAnchor constraintEqualToAnchor:separator.leadingAnchor],
[separator] .superview.trailingAnchor constraintEqualToAnchor:separator.trailingAnchor],
[firstView.bottomAnchor constraintEqualToAnchor:separator.topAnchor constant:kMargin],
[secondView.topAnchor constraintEqualToAnchor:separator.bottomAnchor constant:-kMargin],
]];

separator.topConstraint = [separator.topAnchor constraintEqualToAnchor:separator.superview.topAnchor constant:0]; //这个常数是什么并不重要,因为它没有被启用

返回分隔符;
}

- (instancetype)init {
self = [super init];
if(self){
self.translatesAutoresizingMaskIntoConstraints = false;
self.userInteractionEnabled = true;
self.backgroundColor = [UIColor clearColor];
}
返回自我;
}

#pragma mark - Handle Touches

//当它第一次接收到触摸时,保存(a)当前视图的位置; (b)触摸开始时

- (void)touchesBegan :( NSSet< UITouch *> *)触及withEvent:(UIEvent *)事件{
self.oldY = self。 frame.origin.y;
self.firstTouch = [[touches anyObject] locationInView:self.superview];
self.topConstraint.constant = self.oldY;
self.topConstraint.active = true;
}

//当用户拖动手指时,找出新的顶部约束应该是什么

- (void)touchesMoved:(NSSet< UITouch *> *)触及withEvent:(UIEvent *)事件{
UITouch * touch = [touches anyObject];

//对于响应更快的用户体验,使用预测的触摸,如果可能的话,

if([UIEvent instancesRespondToSelector:@selector(predictTouchesForTouch :)]){
UITouch * predictTouch = [[event predictTouchesForTouch:touch] lastObject];
if(predictTouch){
[self updateTopConstraintOnBasisOfTouch:predictTouch];
返回;
}
}

//如果找不到预测的触摸,只需使用提供的触摸

[self updateTopConstraintOnBasisOfTouch:touch];
}

//完成触摸后,根据最终触摸重置约束,
//(退出先前使用预测触摸进行的任何调整,如果有的话) 。

- (void)touchesEnded:(NSSet< UITouch *> *)触及withEvent:(UIEvent *)事件{
[self updateTopConstraintOnBasisOfTouch:[touches anyObject]];
}

/ **根据触摸更新分隔符视图的顶部约束。

这会更新水平分隔符的顶部约束(移动可见分隔符)。
请注意,这使用了touchesBegan中填充的属性,特别是`oldY`(触摸开始前
分隔符所在的位置)和`firstTouch`(这些触摸开始的地方)。

@param touch指示应移动分隔符的位置的触摸。
* /
- (void)updateTopConstraintOnBasisOfTouch:(UITouch *)touch {
//计算分隔符应该移动到

的位置CGFloat y = self.oldY + [touch locationInView:self.superview] .y - self.firstTouch.y;

//确保上方和下方的视图不会太小

y = MAX(y,self.firstView.frame.origin.y + kMinHeight - kMargin);
y = MIN(y,self.secondView.frame.origin.y + self.secondView.frame.size.height - (kMargin + kMinHeight));

//设置约束

self.topConstraint.constant = y;
}

#pragma mark - 绘图

- (void)drawRect:(CGRect)rect {
CGRect separatorRect = CGRectMake(0,kMargin, self.bounds.size.width,kVisibleHeight);
UIBezierPath * path = [UIBezierPath bezierPathWithRect:separatorRect];
[[UIColor blackColor] set];
[路径中风];
[路径填充];
}

@end

垂直分隔符可能看起来像非常相似,但我会为你留下这个练习。



无论如何,你可以像这样使用它:

  @implementation ViewController 

- (void)viewDidLoad {
[super viewDidLoad];

UIView * previousContentView = nil;

for(NSInteger i = 0; i< 4; i ++){
UIView * contentView = [self addRandomColoredView];
[self.view.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor] .active = true;
[self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor] .active = true;
if(previousContentView){
[Horizo​​ntalSeparatorView addSeparatorBetweenView:previousContentView secondView:contentView];
NSLayoutConstraint * height = [contentView.heightAnchor constraintEqualToAnchor:previousContentView.heightAnchor];
height.priority = 250;
height.active = true;
} else {
[self.view.topAnchor constraintEqualToAnchor:contentView.topAnchor] .active = true;
}
previousContentView = contentView;
}
[self.view.bottomAnchor constraintEqualToAnchor:previousContentView.bottomAnchor] .active = true;
}

- (UIView *)addRandomColoredView {
UIView * someView = [[UIView alloc] init];
someView.translatesAutoresizingMaskIntoConstraints = false;
someView.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0];
[self.view addSubview:someView];

返回someView;
}

@end

产生的结果如下: / p>



正如我所提到的,垂直分隔符看起来非常类似。如果您有包含垂直和水平分隔符的复杂视图,则可能需要使用不可见的容器视图来隔离垂直和水平视图。例如,考虑一个例子:





这可能包含两个视图,它们跨越设备的整个宽度,只有一个水平分隔符,然后顶视图本身将有两个子视图和一个垂直分隔符,底部视图将有三个子视图和两个垂直分隔符。






这里有很多,所以在你尝试推断上面的例子来处理(a)垂直分隔符之前;然后(b)视图内视图模式,确保您真正理解上述示例的工作原理。这不是一个通用的解决方案,而只是为了说明您可能采用的模式。但希望这说明了基本的想法。


How can I resize views with a separator? What I'm trying to do is something like Instagram layout app. I want to be able to resize views by dragging the line that separates the views.

I already looked into this question. It is similar to what I want to accomplish and I already tried the answers but it does not work if there are more than 2 views connected to a separator (if there are 3 or more view only 2 views resize when separator moves each time). I tried to change the code but I have no idea what to do or what the code means.

In my app I will have 2-6 views. The separator should resize all the views that is next to it.

Some examples of my views:

How can I accomplish this? Where do I start?

解决方案

There are lots of ways to accomplish this, but like Avinash, I'd suggest creating a "separator view" in between the various "content" UIView objects. Then you can drag that around. The trick here, though, is that you likely want the separator view to be bigger than just the narrow visible line, so that it will capture touches not only right on the separator line, but close to it, too.

Unlike that other answer you reference, nowadays I'd new recommend using autolayout so that all you need to do with the user gestures is update the location of the separator view (e.g. update the top constraint of the separator view), and then all of the other views will be automatically resized for you. I'd also suggest adding a low priority constraint on the size of the subviews, so that they're laid out nicely when you first set everything up and before you start dragging separators around, but that it will fail gracefully when the dragged separator dictates that the size of the neighboring views must change.

Finally, while we'd historically use gesture recognizers for stuff like this, with the advent of predicted touches in iOS 9, I'd suggest just implementing touchesBegan, touchesMoved, etc. Using predicted touches, you won't notice the difference on the simulator or older devices, but when you run this on a device capable of predicted touches (e.g. new devices like the iPad Pro and other new devices), you'll get a more responsive UX.

So a horizontal separator view class might look like the following.

static CGFloat const kTotalHeight = 44;                               // the total height of the separator (including parts that are not visible
static CGFloat const kVisibleHeight = 2;                              // the height of the visible portion of the separator
static CGFloat const kMargin = (kTotalHeight - kVisibleHeight) / 2.0; // the height of the non-visible portions of the separator (i.e. above and below the visible portion)
static CGFloat const kMinHeight = 10;                                 // the minimum height allowed for views above and below the separator

/** Horizontal separator view

 @note This renders a separator view, but the view is larger than the visible separator
 line that you see on the device so that it can receive touches when the user starts 
 touching very near the visible separator. You always want to allow some margin when
 trying to touch something very narrow, such as a separator line.
 */

@interface HorizontalSeparatorView : UIView

@property (nonatomic, strong) NSLayoutConstraint *topConstraint;      // the constraint that dictates the vertical position of the separator
@property (nonatomic, weak) UIView *firstView;                        // the view above the separator
@property (nonatomic, weak) UIView *secondView;                       // the view below the separator

// some properties used for handling the touches

@property (nonatomic) CGFloat oldY;                                   // the position of the separator before the gesture started
@property (nonatomic) CGPoint firstTouch;                             // the position where the drag gesture started

@end

@implementation HorizontalSeparatorView

#pragma mark - Configuration

/** Add a separator between views

 This creates the separator view; adds it to the view hierarchy; adds the constraint for height; 
 adds the constraints for leading/trailing with respect to its superview; and adds the constraints 
 the relation to the views above and below

 @param firstView  The UIView above the separator
 @param secondView The UIView below the separator
 @returns          The separator UIView
 */

+ (instancetype)addSeparatorBetweenView:(UIView *)firstView secondView:(UIView *)secondView {
    HorizontalSeparatorView *separator = [[self alloc] init];
    [firstView.superview addSubview:separator];
    separator.firstView = firstView;
    separator.secondView = secondView;

    [NSLayoutConstraint activateConstraints:@[
        [separator.heightAnchor constraintEqualToConstant:kTotalHeight],
        [separator.superview.leadingAnchor constraintEqualToAnchor:separator.leadingAnchor],
        [separator.superview.trailingAnchor constraintEqualToAnchor:separator.trailingAnchor],
        [firstView.bottomAnchor constraintEqualToAnchor:separator.topAnchor constant:kMargin],
        [secondView.topAnchor constraintEqualToAnchor:separator.bottomAnchor constant:-kMargin],
    ]];

    separator.topConstraint = [separator.topAnchor constraintEqualToAnchor:separator.superview.topAnchor constant:0]; // it doesn't matter what the constant is, because it hasn't been enabled

    return separator;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        self.translatesAutoresizingMaskIntoConstraints = false;
        self.userInteractionEnabled = true;
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}

#pragma mark - Handle Touches

// When it first receives touches, save (a) where the view currently is; and (b) where the touch started

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.oldY = self.frame.origin.y;
    self.firstTouch = [[touches anyObject] locationInView:self.superview];
    self.topConstraint.constant = self.oldY;
    self.topConstraint.active = true;
}

// When user drags finger, figure out what the new top constraint should be

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];

    // for more responsive UX, use predicted touches, if possible

    if ([UIEvent instancesRespondToSelector:@selector(predictedTouchesForTouch:)]) {
        UITouch *predictedTouch = [[event predictedTouchesForTouch:touch] lastObject];
        if (predictedTouch) {
            [self updateTopConstraintOnBasisOfTouch:predictedTouch];
            return;
        }
    }

    // if no predicted touch found, just use the touch provided

    [self updateTopConstraintOnBasisOfTouch:touch];
}

// When touches are done, reset constraint on the basis of the final touch,
// (backing out any adjustment previously done with predicted touches, if any).

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self updateTopConstraintOnBasisOfTouch:[touches anyObject]];
}

/** Update top constraint of the separator view on the basis of a touch.

 This updates the top constraint of the horizontal separator (which moves the visible separator).
 Please note that this uses properties populated in touchesBegan, notably the `oldY` (where the
 separator was before the touches began) and `firstTouch` (where these touches began).

 @param touch    The touch that dictates to where the separator should be moved.
 */
- (void)updateTopConstraintOnBasisOfTouch:(UITouch *)touch {
    // calculate where separator should be moved to

    CGFloat y = self.oldY + [touch locationInView:self.superview].y - self.firstTouch.y;

    // make sure the views above and below are not too small

    y = MAX(y, self.firstView.frame.origin.y + kMinHeight - kMargin);
    y = MIN(y, self.secondView.frame.origin.y + self.secondView.frame.size.height - (kMargin + kMinHeight));

    // set constraint

    self.topConstraint.constant = y;
}

#pragma mark - Drawing

- (void)drawRect:(CGRect)rect {
    CGRect separatorRect = CGRectMake(0, kMargin, self.bounds.size.width, kVisibleHeight);
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:separatorRect];
    [[UIColor blackColor] set];
    [path stroke];
    [path fill];
}

@end

A vertical separator would probably look very similar, but I'll leave that exercise for you.

Anyway, you could use it like so:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIView *previousContentView = nil;

    for (NSInteger i = 0; i < 4; i++) {
        UIView *contentView = [self addRandomColoredView];
        [self.view.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor].active = true;
        [self.view.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor].active = true;
        if (previousContentView) {
            [HorizontalSeparatorView addSeparatorBetweenView:previousContentView secondView:contentView];
            NSLayoutConstraint *height = [contentView.heightAnchor constraintEqualToAnchor:previousContentView.heightAnchor];
            height.priority = 250;
            height.active = true;
        } else {
            [self.view.topAnchor constraintEqualToAnchor:contentView.topAnchor].active = true;
        }
        previousContentView = contentView;
    }
    [self.view.bottomAnchor constraintEqualToAnchor:previousContentView.bottomAnchor].active = true;
}

- (UIView *)addRandomColoredView {
    UIView *someView = [[UIView alloc] init];
    someView.translatesAutoresizingMaskIntoConstraints = false;
    someView.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0];
    [self.view addSubview:someView];

    return someView;
}

@end

That yields something like:

As I mentioned, a vertical separator would look very similar. If you have complicated views with both vertical and horizontal separators, you'd probably want to have invisible container views to isolate the vertical and horizontal views. For example, consider one of your examples:

That would probably consist of two views that span the entire width of the device with a single horizontal separator, and then the top view would, itself, have two subviews with one vertical separator and the bottom view would have three subviews with two vertical separators.


There's a lot here, so before you try extrapolating the above example to handle (a) vertical separators; and then (b) the views-within-views pattern, make sure you really understand how the above example works. This isn't intended as a generalized solution, but rather just to illustrate a pattern you might adopt. But hopefully this illustrates the basic idea.

这篇关于iOS - 使用触摸拖动分隔符调整多个视图的大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆