Search code examples
ioscocoa-touchuiviewcore-graphics

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!


Solution

  • 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