iOS - 使用触摸拖动分隔符调整多个视图的大小

ims*_*ive 4 objective-c ios

如何使用分隔符调整视图大小?我想要做的就像Instagram布局应用程序.我希望能够通过拖动分隔视图的行来调整视图大小.

我已经调查了这个问题.它类似于我想要完成的并且我已经尝试了答案但是如果有超过2个视图连接到分隔符则不起作用(如果有3个或更多视图,则每次分隔符移动时仅2个视图调整大小).我试图改变代码,但我不知道该做什么或代码意味着什么.

在我的应用程序中,我将有2-6个视图.分隔符应调整其旁边的所有视图的大小.

我的观点的一些例子:

在此输入图像描述

我怎么能做到这一点?我从哪里开始?

Rob*_*Rob 7

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

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

最后,虽然我们希望在历史上使用手势识别像这种东西,有预测触摸iOS中9的到来,我建议刚实施touchesBegan,touchesMoved等使用预测触动,你不会注意到在模拟器上的差异或较旧的设备,但是当您在能够预测触摸的设备上运行此设备时(例如iPad Pro和其他新设备等新设备),您将获得响应更快的用户体验.

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

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
Run Code Online (Sandbox Code Playgroud)

垂直分隔符可能看起来非常相似,但我将为您留下该练习.

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

@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
Run Code Online (Sandbox Code Playgroud)

产生的结果如下:

在此输入图像描述

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

在此输入图像描述

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


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