15

I have a draggable view that has a mask layer on it. The mask layer has a UIBezierPath on it which makes an area on the view see-through/transparent(the effect that i want). My end goal is to change the position and size of the path(not the mask layer!) by passing a CGRect that is calculated based on the intersection of my view and another rectangle(Basically I want to hide the area that intersects).

1)How I create my mask and path(creates a see-through rectangle on my view):

self.maskLayer = [CAShapeLayer layer];
self.maskLayer.frame = self.contentView.bounds;

//default path rect
CGRect const rect = CGRectMake(CGRectGetMidX(self.contentView.bounds) ,
                                     CGRectGetMidY(self.contentView.bounds),
                                     50,50);


path = [UIBezierPath bezierPathWithRect:rect];

[path appendPath:[UIBezierPath bezierPathWithRect:self.contentView.frame]];

self.maskLayer.path = path.CGPath;

self.maskLayer.fillRule = kCAFillRuleEvenOdd;

self.contentView.layer.mask = self.maskLayer;

2) My problem is that I couldn't find a way to resize/reposition just the path. I searched online but couldn't find any solution.

This code is not working but is what I could think of:

//runs while the view is being dragged(using UIPanGestureRecognizer)
-(void)move{
 CGRect intersectedRect = CGRectIntersection(self.frame, self.productView.frame);
 path = [UIBezierPath bezierPathWithRect:intersectedRect];
[path appendPath:[UIBezierPath bezierPathWithRect:intersectedRect]];
[self.maskLayer setNeedsDisplay];
 }

How can I manipulate just the path of the masklayer in my move function? Thank you!

Soc
  • 153
  • 1
  • 1
  • 5
  • Where do you get the path variable in the second snippet? Where do you set the path to the mask layer? Why do you append the same rect to the path? – yurish Dec 05 '13 at 13:57
  • I am using the same path variable from the first snippet, a global path var. I set the path on the first snippet here(self.maskLayer.path = path.CGPath;) And i am appending self.contentView.frame to the path, sorry was a type mistake. – Soc Dec 05 '13 at 14:11
  • Yes, but I do not see where you assign the updated path in the move method. – yurish Dec 05 '13 at 14:13
  • -(void)move{ CGRect intersectedRect = CGRectIntersection(self.frame, self.productView.frame); path = [UIBezierPath bezierPathWithRect:intersectedRect]; [path appendPath:[UIBezierPath bezierPathWithRect:self.contentView.frame]]; self.maskLayer.path = path.CGPath; self.maskLayer.fillRule = kCAFillRuleEvenOdd; self.contentView.layer.mask = self.maskLayer; } – Soc Dec 05 '13 at 14:50
  • Now its updating but after like 10 seconds. Do you know why that might be? – Soc Dec 05 '13 at 14:51
  • Better update you question with the new code. There are many the delay may happen. Check first that the move method is called without delay. Do you call move on main thread? – yurish Dec 05 '13 at 14:56
  • The move method is being called correctly without delay because I have more code inside that drags the view around. I call move on the main thread, but inside i open a different thread and I calculate intersections and change the path. The path changes but after 10 + seconds – Soc Dec 05 '13 at 15:07
  • Open other question for this with updated code and details about how and what for you spin the new thread for calculation. – yurish Dec 05 '13 at 15:13
  • I found the problem, I was attempting to update the path within the thread and not on the end of the thread. Thanks for trying to help me. – Soc Dec 05 '13 at 15:16

6 Answers6

18
// example path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 100, 100) cornerRadius:4];

// scale it
CGFloat scale = 0.9;
[path applyTransform:CGAffineTransformMakeScale(scale, scale)];

// move it
CGSize translation = CGSizeMake(10, 5);
[path applyTransform:CGAffineTransformMakeTranslation(translation.width,
                                                      translation.height)];

// apply it
self.myLayer.path = path;
amergin
  • 3,106
  • 32
  • 44
hfossli
  • 22,616
  • 10
  • 116
  • 130
13

Swift version

Here is @hfossli's code for Swift:

// example path
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 100, height: 100), cornerRadius: 4)

// scale it
let scale = CGFloat(0.9)
path.applyTransform(CGAffineTransformMakeScale(scale, scale))

// move it
let translation = CGSize(width: 10, height: 5)
path.applyTransform(CGAffineTransformMakeTranslation(translation.width,
    translation.height))

// apply it
self.myLayer.path = path

Update

I've found in actual practice that I just just different initial values when creating a UIBezierPath rather than transforming it later. I guess it would depend on the purpose.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
7

Swift 3 extension (modify it with factorX, factorY if needed) :

extension UIBezierPath
{
    
    func scaleAroundCenter(factor: CGFloat)
    {
        let beforeCenter = self.bounds.getCenter()
        
        // SCALE path by factor
        let scaleTransform = CGAffineTransform(scaleX: factor, y: factor)
        self.apply(scaleTransform)
        
        let afterCenter = self.bounds.getCenter()
        let diff = CGPoint(
            x: beforeCenter.x - afterCenter.x,
            y: beforeCenter.y - afterCenter.y)
        
        let translateTransform = CGAffineTransform(translationX: diff.x, y: diff.y)
        self.apply(translateTransform)
    }

SWIFT 5 UPDATE

extension UIBezierPath {

    func scaleAroundCenter(factor: CGFloat) {
        let beforeCenter = CGPoint(x: self.bounds.midX, y: self.bounds.midY)

        // SCALE path by factor
        let scaleTransform = CGAffineTransform(scaleX: factor, y: factor)
        self.apply(scaleTransform)

        let afterCenter = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
        let diff = CGPoint( x: beforeCenter.x - afterCenter.x, y: beforeCenter.y - afterCenter.y)

        let translateTransform = CGAffineTransform(translationX: diff.x, y: diff.y)
        self.apply(translateTransform)
    }

}
ExeRhythm
  • 452
  • 5
  • 13
sabiland
  • 2,526
  • 1
  • 25
  • 24
3

You need to assign the updated path to self.maskLayer.path in your move method.

yurish
  • 1,515
  • 1
  • 15
  • 16
3

It's in swift 3 or higher

func move(path: UIBezierPath, fromPoint: CGPoint, toPoint: CGPoint) {
    let moveX = toPoint.x - fromPoint.x
    let moveY = toPoint.y - fromPoint.y
    path.apply(CGAffineTransform(translationX: moveX, y: moveY))
}

func scale(path: UIBezierPath, fromSize: CGSize, toSize: CGSize) {
    if fromSize.width == 0 || fromSize.height == 0 {
        return
    }
    let scaleWidth = toSize.width / fromSize.width
    let scaleHeight = toSize.height / fromSize.height
    path.apply(CGAffineTransform(scaleX: scaleWidth, y: scaleHeight))
}
Md. Shofiulla
  • 2,135
  • 1
  • 13
  • 19
Roman Filippov
  • 2,289
  • 2
  • 11
  • 17
0

Swift 4

func tableView(_ tableView: UITableView, dragPreviewParametersForRowAt indexPath: IndexPath) -> UIDragPreviewParameters? {
    let cell = tableView.cellForRow(at: indexPath) as! ItemTableViewCell

    let previewParameters = UIDragPreviewParameters()
    let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height), cornerRadius: 4)
    let scale = CGFloat(0.85)
    path.apply(CGAffineTransform(scaleX: scale, y: scale))
    let translation = CGSize(width: 10, height: 5)
    path.apply(CGAffineTransform(translationX: translation.width, y: translation.height))
    previewParameters.visiblePath = path
    return previewParameters
}
Reza
  • 145
  • 1
  • 2
  • 12