Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to merge / intersect two UIViews rather than have them overlap?

Given two UIViews with borders that have UIPanGestureRecognizers attached to them:

enter image description here

If I drag the UIView on the left over the UIView on the right, this is the usual behavior:

enter image description here

Is it possible to get them to do the behavior below where it looks like they merge?enter image description here:

Looking for the simplest way possible to do this!

like image 684
Ser Pounce Avatar asked Dec 08 '22 10:12

Ser Pounce


2 Answers

One way is to use multiple sibling layers and zPosition. To achieve the effect you add two layers, one for border, one for content. And the border layer has a smaller zPosition than the content. And, of course, move the layers with the UIPanGestureRecognizer.

Showcase

MP4 version

Swift:

import UIKit

class MergingView: UIView {

    let borderLayer = CALayer()
    let backgroundLayer = CALayer()

    override func layoutSubviews() {
        super.layoutSubviews()

        addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))))

        borderLayer.borderWidth = 5
        borderLayer.frame = frame
        borderLayer.zPosition = 10
        borderLayer.borderColor = UIColor.black.cgColor
        superview?.layer.addSublayer(borderLayer)

        backgroundLayer.frame = CGRect(x: frame.origin.x + 5, y: frame.origin.y + 5, width: frame.width - 10, height: frame.height - 10)
        backgroundLayer.zPosition = 20
        backgroundLayer.backgroundColor = UIColor.white.cgColor
        superview?.layer.addSublayer(backgroundLayer);
    }

    @objc func handlePan(_ recognizer: UIPanGestureRecognizer) {
        CATransaction.begin()
        CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)

        let translation = recognizer.translation(in: self)
        frame = self.frame.offsetBy(dx: translation.x, dy: translation.y)
        recognizer.setTranslation(CGPoint.zero, in: self)

        borderLayer.frame = borderLayer.frame.offsetBy(dx: translation.x, dy: translation.y)
        backgroundLayer.frame = backgroundLayer.frame.offsetBy(dx: translation.x, dy: translation.y)

        CATransaction.commit()
    }
}

Objective-C header:

#import <UIKit/UIKit.h>

@interface MVMergingView : UIView

@end

Objective-C implementation:

#import "MVMergingView.h"

@interface MVMergingView ()

@property (strong) CALayer *borderLayer;
@property (strong) CALayer *backgroundLayer;

@end

@implementation MVMergingView

- (void)layoutSubviews {
    [super layoutSubviews];

    [self addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]];

    CALayer *borderLayer = [CALayer layer];
    borderLayer.borderWidth = 5.f;
    borderLayer.frame = self.frame;
    borderLayer.zPosition = 10;
    borderLayer.borderColor = UIColor.blackColor.CGColor;
    self.borderLayer = borderLayer;
    [self.superview.layer addSublayer:borderLayer];

    CALayer *backgroundLayer = [CALayer layer];
    backgroundLayer.frame = CGRectMake(self.frame.origin.x + 5.f, self.frame.origin.y + 5.f, self.frame.size.width - 10, self.frame.size.height - 10);
    backgroundLayer.zPosition = 20;
    backgroundLayer.backgroundColor = UIColor.whiteColor.CGColor;
    self.backgroundLayer = backgroundLayer;
    [self.superview.layer addSublayer:backgroundLayer];
}

- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

    CGPoint translation = [recognizer translationInView:self];
    self.frame = CGRectOffset(self.frame, translation.x, translation.y);
    [recognizer setTranslation:CGPointZero inView:self];

    self.borderLayer.frame = CGRectOffset(self.borderLayer.frame, translation.x, translation.y);
    self.backgroundLayer.frame = CGRectOffset(self.backgroundLayer.frame, translation.x, translation.y);

    [CATransaction commit];
}

@end

Example repo: https://github.com/dimitarnestorov/MergingView

like image 142
Dimitar Nestorov Avatar answered May 07 '23 18:05

Dimitar Nestorov


It seems like your requirement to "merge" rather than overlap exists only because of the borders. If there were no borders, then there's no need for any merging; you simply overlap them. So the question becomes how to deal with the borders.

I'm thinking perhaps you can have a special UIView subclass (MergingView?) which would act as a parent of 2 views; a border view and a content view. Now that the border and content are in separate views, "merging" becomes trivial.

When we drop A on top of B, all we have to do is give A's border view and content view to B (so they are now subviews of B), and then send all of B's border views to the back.

You can then proceed to merge more with B, the procedure is the same.

Would this suit your use case? Happy to post code if needed.

like image 41
Peter Parker Avatar answered May 07 '23 20:05

Peter Parker