1

This question is similar to the question I asked here. Rob answered me and his code worked perfectly. I am trying to do the same thing but make the views vertical.

Here is what I have now.

I my 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) {
            VerticalSeparatorView *view = [[VerticalSeparatorView alloc] init];
            [view addSeparatorBetweenView:previousContentView secondView:contentView];
            NSLayoutConstraint *width = [contentView.widthAnchor constraintEqualToAnchor:previousContentView.widthAnchor];
            width.priority = 250;
            width.active = true;
        } else {
            [self.view.leftAnchor constraintEqualToAnchor:contentView.leftAnchor].active = true;
        }
        previousContentView = contentView;
    }
    [self.view.rightAnchor constraintEqualToAnchor:previousContentView.rightAnchor].active = true;

In my VerticalSeparatorView:

@implementation VerticalSeparatorView

#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 {
    VerticalSeparatorView *separator = [[VerticalSeparatorView alloc] init];
    [firstView.superview addSubview:separator];
    separator.firstView = firstView;
    separator.secondView = secondView;

    [NSLayoutConstraint activateConstraints:@[
                                              [separator.widthAnchor constraintEqualToConstant:kTotalWidth],
                                              [separator.superview.leadingAnchor constraintEqualToAnchor:separator.leadingAnchor],
                                              [separator.superview.trailingAnchor constraintEqualToAnchor:separator.trailingAnchor],
                                              [firstView.rightAnchor constraintEqualToAnchor:separator.leftAnchor constant:kMargin],
                                              [secondView.leftAnchor constraintEqualToAnchor:separator.rightAnchor constant:-kMargin],
                                              ]];

    separator.leftConstraint = [separator.leftAnchor constraintEqualToAnchor:separator.superview.leftAnchor 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 redColor];
    }
    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.oldX = self.frame.origin.x;
    self.firstTouch = [[touches anyObject] locationInView:self.superview];
    self.leftConstraint.constant = self.oldX;
    self.leftConstraint.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 x = self.oldX + [touch locationInView:self.superview].y - self.firstTouch.y;

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

    x = MAX(x, self.firstView.frame.origin.x + kMinWidth - kMargin);
    x = MIN(x, self.secondView.frame.origin.x + self.secondView.frame.size.width - (kMargin + kMinWidth));

    // set constraint

    self.leftConstraint.constant = x;
}

#pragma mark - Drawing

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

@end

I am connecting the contentView right anchor to the separator left anchor and contentView left anchor to the separator right anchor but nothing is displayed after I run this code. What am I doing wrong?

Community
  • 1
  • 1
imstillalive
  • 369
  • 4
  • 14

1 Answers1

1

A couple of observations:

  1. The constraints in the for loop, which was written for a horizontal separator, are all backwards when dealing with vertical separator. You have to replace all occurrences of top/bottom anchors with leading/trailing, and vice versa. You've done this (largely) within the VerticalSeparatorClass, not not where you are creating your content views.

    UIView *previousContentView = nil;
    
    for (NSInteger i = 0; i < 4; i++) {
        UIView *contentView = [self addRandomColoredView];
        [self.view.topAnchor constraintEqualToAnchor:contentView.topAnchor].active = true;
        [self.view.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor].active = true;
        if (previousContentView) {
            [VerticalSeparatorView addSeparatorBetweenView:previousContentView secondView:contentView];
            NSLayoutConstraint *width = [contentView.widthAnchor constraintEqualToAnchor:previousContentView.widthAnchor];
            width.priority = 250;
            width.active = true;
        } else {
            [self.view.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor].active = true;
        }
        previousContentView = contentView;
    }
    [self.view.trailingAnchor constraintEqualToAnchor:previousContentView.trailingAnchor].active = true;
    
  2. Also, I'm unclear why you made addSeparatorBetweenView an instance method rather than a class method like it was before, because now you're dealing with two separate instances. It's not a problem, but it's inefficient.

    Also, I think a few constraints that should have been flipped between top/bottom and leading/trailing slipped through. You want:

    + (instancetype)addSeparatorBetweenView:(UIView *)firstView secondView:(UIView *)secondView {
        VerticalSeparatorView *separator = [[VerticalSeparatorView alloc] init];
        [firstView.superview addSubview:separator];
        separator.firstView = firstView;
        separator.secondView = secondView;
    
        [NSLayoutConstraint activateConstraints:@[
            [separator.widthAnchor constraintEqualToConstant:kTotalWidth],
            [separator.superview.topAnchor constraintEqualToAnchor:separator.topAnchor],
            [separator.superview.bottomAnchor constraintEqualToAnchor:separator.bottomAnchor],
            [firstView.rightAnchor constraintEqualToAnchor:separator.leftAnchor constant:kMargin],
            [secondView.leftAnchor constraintEqualToAnchor:separator.rightAnchor constant:-kMargin],
        ]];
    
        separator.leftConstraint = [separator.leftAnchor constraintEqualToAnchor:separator.superview.leftAnchor constant:0]; // it doesn't matter what the constant is, because it hasn't been enabled
    
        return separator;
    }
    
  3. In the touch handling code, you were still referencing y, whereas you now want to refer to x:

    - (void)updateTopConstraintOnBasisOfTouch:(UITouch *)touch {
        // calculate where separator should be moved to
    
        CGFloat x = self.oldX + [touch locationInView:self.superview].x - self.firstTouch.x;
    
        // make sure the views above and below are not too small
    
        x = MAX(x, self.firstView.frame.origin.x + kMinWidth - kMargin);
        x = MIN(x, self.secondView.frame.origin.x + self.secondView.frame.size.width - (kMargin + kMinWidth));
    
        // set constraint
    
        self.leftConstraint.constant = x;
    }
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hi Rob. Instead of contentView being UIView I replaced it with UIScrollView because I want to zoom in/out photos inside. I have a UIImageView inside the scrollView with auto layout and it looks great. But somehow I can scroll and not pinch zoom in/out the image in scrollview. Is it because the separator touches that is confusing the app from the scrollview touches? – imstillalive Mar 06 '16 at 17:00
  • If nothing else you should probably reduce `kTotalHeight` and `kTotalWidth` a bit to avoid accidentally grabbing the separator when you really meant to pinch-zoom. You could probably also have separator look at the number of touches and only have it act upon touches if there is a single touch. But before you kill yourself fixing this, make sure that the separator's touch handling is really the root of the problem and that there's not something else wrong with the imageview zooming. – Rob Mar 06 '16 at 17:52
  • Hi Rob. I think the problem is not coming from the separator touches but instead the scrollview or the scrollview constraints. I googled and found [here](http://stackoverflow.com/a/15350252/4345655) that if I'm using scrollviews with auto layout I need to connect the image view constraints with the scrollview superview constraints. Unfortunately when I did the scrollview did not scroll at all and the imageView gets resized when the separator moves. – imstillalive Mar 07 '16 at 17:42
  • I have tried searching for answers but no luck. I know I have asked you to answer 2 of my questions already but I just don't know who to ask. If you have time can you please check out my question [here](http://stackoverflow.com/q/35828296/4345655) – imstillalive Mar 07 '16 at 17:42