Given two UIViews
with borders that have UIPanGestureRecognizers
attached to them:
If I drag the UIView
on the left over the UIView
on the right, this is the usual behavior:
Is it possible to get them to do the behavior below where it looks like they merge?:
Looking for the simplest way possible to do this!
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
.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With