Путь вычитания UIBezierPath

Используя [UIBezierPath bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:] , я могу создать округленное представление, например:

округлый вид

Как я мог вычесть другой путь из этого (или каким-либо другим способом), чтобы создать такой путь:

вычитаемый вид

Есть ли способ сделать что-то подобное? псевдокод:

 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]; 

Если вы хотите погладить вычитаемый путь, вы сами по себе. Apple не предоставляет API, который возвращает (или просто штрихи) вычитание одного пути из другого.

Если вы просто хотите заполнить вычитаемый путь (как в вашем примере изображения), вы можете сделать это, используя обтравочный контур. Однако вы должны использовать трюк. Когда вы добавляете путь к обтравочному контуру, новый обтравочный контур является пересечением старого обтравочного контура и добавленного пути. Поэтому, если вы просто добавите smallMaskPath к обтравочному smallMaskPath , вы закончите заполнение только области внутри smallMaskPath , которая является противоположностью того, что вы хотите.

То, что вам нужно сделать, – это пересечь существующий обтравочный контур с помощью инверсии smallMaskPath . К счастью, вы можете сделать это довольно легко, используя правило четной обмотки. Вы можете прочитать о четном правиле в двумерном программировании кварца .

Основная идея заключается в том, что мы создаем сложный путь с двумя подпутами: ваш 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()); 

На самом деле в большинстве случаев гораздо проще, например в Swift:

 path.append(cutout.reversing()) 

Это работает, потому что правило заполнения по умолчанию – это правило, отличное от нуля .

Это должно сделать это, отрегулируйте размеры по своему усмотрению:

 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]; 

Еще один действительно простой способ получить эффект, который вам нужен, – просто нарисовать внешний округлый, изменить цвета и нарисовать внутреннюю над ним.

Я бил головой о стену, пытаясь понять, как это сделать с несколькими перекрывающимися CGPaths. Если вы перекрываете несколько раз, он заправляется с помощью приведенных выше решений. Оказывается, способ действительно добиться эффекта «вычитания» с несколькими перекрывающимися путями состоит в том, чтобы просто очистить режим смешивания контекста.

CGContextSetBlendMode(ctx, kCGBlendModeClear);

Используя ответ @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 shapeLayer.mask = mask view PlaygroundPage.current.liveView = view 

введите описание изображения здесь

Вместо вычитания я смог решить, создав CGPath используя CGPathAddLineToPoint и CGPathAddArcToPoint . Это также можно сделать с помощью UIBiezerPath с аналогичным кодом.

Патрик обеспечил улучшение / альтернативу ответам пользователя NSResponder, используя правило ненулевой обмотки . Вот полная реализация в 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() 

Вот суть этого.

Вы можете поместить это в Раскадровку, чтобы видеть и играть с выходом.

Результат

  • NSLocale currentLocale всегда возвращает «en_US», а не текущий язык пользователя
  • iOS 7 sizeWithAttributes: замена для sizeWithFont: constrainedToSize
  • Динамическое обновление UILabel
  • Лучший способ определить частные методы для classа в Objective-C
  • Добавление файлов в отдельные объекты в Xcode 4
  • Клиентские / серверные GKSessions
  • Должен ли я использовать свойства или прямую ссылку при доступе к переменным экземпляра внутри?
  • Увеличить значок Push Push iPhone
  • Как получить ширину NSString?
  • @class против #import
  • Как предотвратить циклическую ссылку, когда Swift-заголовок заголовка импортирует файл, который импортирует сам Hopscotch-Swift.h
  • Давайте будем гением компьютера.