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

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

我已经看过这个问题 。 它与我想要完成的类似,我已经尝试了答案,但是如果有超过2个视图连接到分隔符(如果有3个或更多视图只有2个视图在分隔符每次移动时resize),则不起作用。 我试图改变代码,但我不知道该怎么做或代码的意思。

在我的应用程序中,我将有2-6的意见。 分隔符应调整其旁边的所有视图。

我的观点的一些例子:

在这里输入图像说明

我怎样才能做到这一点? 我从哪说起呢?

有很多方法来完成这一点,但像Avinash,我build议在各种“内容” UIView对象之间创build一个“分隔视图”。 然后你可以拖动它。 然而,这里的诀窍是,你可能希望分隔视图比可见的窄线更大,这样它不仅可以捕捉到分隔线上的接触,而且还可以接近分隔线。

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

最后,虽然我们历史上会使用手势识别器来处理这样的事情,但是随着iOS 9预测的到来,我build议只实现touchesBegantouchesMoved等。使用预测的触摸,您将不会注意到模拟器或较旧的设备,但是当您在具有预测触摸function的设备(例如iPad Pro和其他新设备等新设备)上运行此function时,您将获得更具响应性的用户体验。

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

 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 (ie 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 

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

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

 @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 

这产生了类似于:

在这里输入图像说明

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

在这里输入图像说明

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


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

基于Rob的解决scheme我创build了水平和垂直分隔视图的Swift类: https ://gist.github.com/JULI-ya/1a7c293b022207bb427caa3bbb9d3ed8

有代码只有两个内部视图与分隔符,因为我的想法是把每个人创build此自定义布局。 它看起来像一个二叉树结构的视图。

我已经更新@ JULIIncognito的Swift类Swift 4,增加了一个拖动指标,并修复了一些错别字。

SeparatorView

只需导入到您的项目,并像这样使用它:

 SeparatorView.addSeparatorBetweenViews(separatorType: .horizontal, primaryView: view1, secondaryView: view2, parentView: self.view) 

这是它的样子(MapView顶部,底部的TableView): SeparatorView

使用UIPanGestureRecognizers。 为每个视图添加一个识别器。 在gestureRecognizerShouldBegin:方法中,如果手势的位置非常接近边缘,则返回YES(使用手势的locationInView:view方法)。 然后在手势的动作方法(在手势的initWithTarget: action:指定),你可以像这样进行移动:

 -(void)viewPan:(UIPanGestureRecognizer *)sender switch (sender.state) { case UIGestureRecognizerStateBegan: { //determine the second view based on gesture's locationInView: //for instance if close to bottom, the second view is the one under the current. } case UIGestureRecognizerStateChanged: { //change the frames of the current and the second view based on sender's translationInView: } ... } 

据我所知,我们可以使用UIGestureRecognizer和自动布局来做到这一点。

1.使用UIView作为行分隔符。
2.将Pan gestureRecognizer添加到分隔线视图。
3.使用UIView.animatewithDuration()处理委托协议方法中的视图移动
PanGestureRecognizer

最重要的是,不要忘记在Attribute Inspector中为所有行分隔符视图设置/勾选UserInteration Enabled。