Путь вычитания UIBezierPath
Используя [UIBezierPath bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:]
, я могу создать округленное представление, например:
![rounded view]()
Как я мог вычесть другой путь из этого (или каким-либо другим способом), чтобы создать такой путь:
![subtracted view]()
Есть ли способ сделать что-то подобное?
Псевдокод:
UIBezierPath *bigMaskPath = [UIBezierPath bezierPathWithRoundedRect:bigView.bounds
byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight)
cornerRadii:CGSizeMake(18, 18)];
UIBezierPath *smallMaskPath = [UIBezierPath bezierPathWithRoundedRect:smalLView.bounds
byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight)
cornerRadii:CGSizeMake(18, 18)];
UIBezierPath *finalPath = [UIBezierPath pathBySubtractingPath:smallMaskPath fromPath:bigMaskPath];
Ответы
Ответ 1
Если вы хотите погладить вычитаемый путь, вы сами по себе. Apple не предоставляет API, который возвращает (или просто штрихи) вычитание одного пути из другого.
Если вы просто хотите заполнить вычитаемый путь (как в вашем примере изображения), вы можете сделать это, используя обтравочный контур. Однако вы должны использовать трюк. Когда вы добавляете путь к обтравочному контуру, новый обтравочный контур является пересечением старого обтравочного контура и добавленного пути. Поэтому, если вы просто добавите smallMaskPath
к обтравочному контуру, вы получите заполнение только области внутри smallMaskPath
, которая противоположна тому, что вы хотите.
Что вам нужно сделать, это пересечь существующий обтравочный путь с инверсией smallMaskPath
. К счастью, вы можете сделать это довольно легко, используя правило четной обмотки. Вы можете прочитать о четном правиле в Кварцевом 2D-руководстве по программированию.
Основная идея состоит в том, что мы создаем составной путь с двумя подпутами: ваш smallMaskPath
и огромный прямоугольник, который полностью охватывает ваш smallMaskPath
и каждый другой пиксель, который вы, возможно, захотите заполнить. Из-за четного правила каждый пиксель внутри smallMaskPath
будет рассматриваться как вне составного пути, и каждый пиксель вне smallMaskPath
будет рассматриваться как внутри составного пути.
Итак, создайте этот составной путь. Мы начнем с огромного прямоугольника. И нет прямоугольника, более огромного, чем бесконечный прямоугольник:
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
Теперь мы перейдем к сложному пути, добавив к нему smallMaskPath
:
[clipPath appendPath:smallMaskPath];
Затем мы устанавливаем путь для использования четно-нечетного правила:
clipPath.usesEvenOddFillRule = YES;
Прежде чем кликать по этому пути, мы должны сохранить состояние графики, чтобы мы могли отменить изменение обтравочного контура, когда мы закончим:
CGContextSaveGState(UIGraphicsGetCurrentContext()); {
Теперь мы можем изменить обтравочный путь:
[clipPath addClip];
и мы можем заполнить bigMaskPath
:
[[UIColor orangeColor] setFill];
[bigMaskPath fill];
Наконец, мы восстанавливаем состояние графики, отменяя изменение на обтравочный путь:
} CGContextRestoreGState(UIGraphicsGetCurrentContext());
Здесь код все вместе, если вы хотите скопировать/вставить его:
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
[clipPath appendPath:smallMaskPath];
clipPath.usesEvenOddFillRule = YES;
CGContextSaveGState(UIGraphicsGetCurrentContext()); {
[clipPath addClip];
[[UIColor orangeColor] setFill];
[bigMaskPath fill];
} CGContextRestoreGState(UIGraphicsGetCurrentContext());
Ответ 2
На самом деле для большинства случаев гораздо проще, например в Swift:
path.append(cutout.reversing())
Это работает, потому что правило заполнения по умолчанию - это правило ненулевой обмотки.
Ответ 3
Это должно сделать это, отрегулируйте размеры по своему усмотрению:
CGRect outerRect = {0, 0, 200, 200};
CGRect innerRect = CGRectInset(outerRect, 30, 30);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:outerRect cornerRadius:10];
[path appendPath:[UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:5]];
path.usesEvenOddFillRule = YES;
[[UIColor orangeColor] set];
[path fill];
Еще один очень простой способ получить эффект, который вам нужен, - просто нарисовать внешний округлый, изменить цвета и нарисовать внутреннюю над ним.
Ответ 4
Используя ответ @Patrick Pijnappel, вы можете подготовить тестовую площадку для быстрого тестирования
import UIKit
import PlaygroundSupport
let view = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 647))
view.backgroundColor = UIColor.green
let someView = UIView(frame: CGRect(x:50, y: 50, width:250, height:250))
someView.backgroundColor = UIColor.red
view.addSubview(someView)
let shapeLayer = CAShapeLayer()
shapeLayer.frame = someView.bounds
shapeLayer.path = UIBezierPath(roundedRect: someView.bounds,
byRoundingCorners: [UIRectCorner.bottomLeft,UIRectCorner.bottomRight] ,
cornerRadii: CGSize(width: 5.0, height: 5.0)).cgPath
someView.layer.mask = shapeLayer
someView.layer.masksToBounds = true
let rect = CGRect(x:0, y:0, width:200, height:100)
let cornerRadius:CGFloat = 5
let subPathSideSize:CGFloat = 25
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
let leftSubPath = UIBezierPath(arcCenter: CGPoint(x:0, y:rect.height / 2),
radius: subPathSideSize / 2, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI + M_PI_2), clockwise: false)
leftSubPath.close()
let rightSubPath = UIBezierPath(arcCenter: CGPoint(x:rect.width, y:rect.height / 2),
radius: subPathSideSize / 2, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI + M_PI_2), clockwise: true)
rightSubPath.close()
path.append(leftSubPath)
path.append(rightSubPath.reversing())
path.append(path)
let mask = CAShapeLayer()
mask.frame = shapeLayer.bounds
mask.path = path.cgPath
someView.layer.mask = mask
view
PlaygroundPage.current.liveView = view
![enter image description here]()
![enter image description here]()
Ответ 5
Я бил головой о стену, пытаясь понять, как это сделать с несколькими перекрывающимися CGPaths. Если вы перекрываете несколько раз, он заправляется с помощью приведенных выше решений. Оказывается, способ действительно добиться эффекта "вычитания" с несколькими перекрывающимися путями заключается в том, чтобы просто очистить режим контекстного смешивания.
CGContextSetBlendMode(ctx, kCGBlendModeClear);
Ответ 6
Патрик предоставил альтернативу ответам пользователя NSRsponder, используя правило ненулевой обмотки. Вот полная реализация в Swift для тех, кто ищет расширенный ответ.
UIGraphicsBeginImageContextWithOptions(CGSize(width: 200, height: 200), false, 0.0)
let context = UIGraphicsGetCurrentContext()
let rectPath = UIBezierPath(roundedRect: CGRectMake(0, 0, 200, 200), cornerRadius: 10)
var cutoutPath = UIBezierPath(roundedRect: CGRectMake(30, 30, 140, 140), cornerRadius: 10)
rectPath.appendPath(cutoutPath.bezierPathByReversingPath())
UIColor.orangeColor().set()
outerForegroundPath.fill()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Вот суть этого.
Вы можете поместить это в Раскадровку, чтобы видеть и играть с выходом.
![Результат]()
Ответ 7
2019 - слишком просто
Я удивлен другими ответами, потому что это чрезвычайно легко сделать. Могут ли быть некоторые требования, которые я не понимаю. Но:
p = UIBezierPath(rect: .. )
let hole = UIBezierPath(ovalIn: ... )
p.append(hole.reversing())
p.usesEvenOddFillRule = false
Это отлично работает, независимо от того, есть ли у вас резка.
![enter image description here]()
и
![enter image description here]()
(Это отличный способ сделать надрез или углубление.)
Техника
- Внешний путь идет непрерывно, внутренний путь идет непрерывно
- Использовать правило подсчета, также называемое ненулевым правилом
(В примере я буквально использую его в качестве маски на UIView...)
layerToUseAsUIViewMask.path = p
layer.mask = layerToUseAsUIViewMask
Чтобы спасти любого, набрав код дыры...
let hole = UIBezierPath(ovalIn: CGRect(
origin: CGPoint(x: 70, y: 10), .. use -10 for the second demo above.
size: CGSize(width: 50, height: 50))
)
Примечание. Если вы не уверены, как работают эти два пути, просто попробуйте каждый из.
p.append(hole.reversing()) // that acw currently on iOS
и
p.append(hole) // that cw currently on iOS
пока это не работает.
Ответ 8
Вместо вычитания я смог решить, создав CGPath
с помощью CGPathAddLineToPoint
и CGPathAddArcToPoint
. Это также можно сделать с помощью UIBiezerPath
с похожим кодом.