2013-05-12 2 views
86

У меня есть простой вид (левая часть рисунка), и мне нужно создать какой-то наклад (справа от рисунка) к этому виду. Этот оверлей должен иметь некоторую непрозрачность, так что вид ниже он все еще частично виден. Самое главное, что наложение должно иметь круглое отверстие посередине, чтобы он не перекрывал центр обзора (см. Рисунок ниже).CALayer с прозрачным отверстием в нем

Я могу легко создать круг, как это:

int radius = 20; //whatever 
CAShapeLayer *circle = [CAShapeLayer layer]; 

circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0,radius,radius) cornerRadius:radius].CGPath; 
circle.position = CGPointMake(CGRectGetMidX(view.frame)-radius, 
           CGRectGetMidY(view.frame)-radius); 
circle.fillColor = [UIColor clearColor].CGColor; 

и «полный» прямоугольную накладку так:

CAShapeLayer *shadow = [CAShapeLayer layer]; 
shadow.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height) cornerRadius:0].CGPath; 
shadow.position = CGPointMake(0, 0); 
shadow.fillColor = [UIColor grayColor].CGColor; 
shadow.lineWidth = 0; 
shadow.opacity = 0.5; 
[view.layer addSublayer:shadow]; 

Но я понятия не имею, как я могу объединить эти два слоя так они создают эффект, который я хочу. Кто угодно? Я пробовал действительно все ... Большое спасибо за помощь!

Image

+0

Вы можете создать один Безье, который содержит прямоугольник и круг, а затем обмотку правило используется во время рисования создает отверстие (я не пробовал). – Wain

+0

Я не знаю, как это сделать :) –

+0

Создайте с помощью rect, затем 'moveToPoint', затем добавьте закругленный прямоугольник. Проверьте документы для методов, предложенных 'UIBezierPath'. – Wain

ответ

158

Я был в состоянии решить эту проблему с Jon Steinmetz предложением. Если какой-либо один ухаживает, вот окончательное решение:

int radius = myRect.size.width; 
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.mapView.bounds.size.width, self.mapView.bounds.size.height) cornerRadius:0]; 
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius]; 
[path appendPath:circlePath]; 
[path setUsesEvenOddFillRule:YES]; 

CAShapeLayer *fillLayer = [CAShapeLayer layer]; 
fillLayer.path = path.CGPath; 
fillLayer.fillRule = kCAFillRuleEvenOdd; 
fillLayer.fillColor = [UIColor grayColor].CGColor; 
fillLayer.opacity = 0.5; 
[view.layer addSublayer:fillLayer]; 

для Swift 3.x:

let radius = myRect.size.width 
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.mapView.bounds.size.width, height: self.mapView.bounds.size.height), cornerRadius: 0) 
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2 * radius, height: 2 * radius), cornerRadius: radius) 
path.append(circlePath) 
path.usesEvenOddFillRule = true 

let fillLayer = CAShapeLayer() 
fillLayer.path = path.cgPath 
fillLayer.fillRule = kCAFillRuleEvenOdd 
fillLayer.fillColor = Color.background.cgColor 
fillLayer.opacity = 0.5 
view.layer.addSublayer(fillLayer) 
+0

Отличное резюме, спасибо! – JimmyB

+1

рад, что это помогло, даже после года и половины :) –

+6

Это помогает даже через два года! Спасибо :) –

9

Я принял подобный подход как animal_chin, но я более наглядными, так что я установите большую часть его в Interface Builder с помощью выходов и автоматической компоновки.

Вот мое решение в Swift

//shadowView is a UIView of what I want to be "solid" 
    var outerPath = UIBezierPath(rect: shadowView.frame) 

    //croppingView is a subview of shadowView that is laid out in interface builder using auto layout 
    //croppingView is hidden. 
    var circlePath = UIBezierPath(ovalInRect: croppingView.frame) 
    outerPath.usesEvenOddFillRule = true 
    outerPath.appendPath(circlePath) 

    var maskLayer = CAShapeLayer() 
    maskLayer.path = outerPath.CGPath 
    maskLayer.fillRule = kCAFillRuleEvenOdd 
    maskLayer.fillColor = UIColor.whiteColor().CGColor 

    shadowView.layer.mask = maskLayer 
+0

Мне нравится это решение, потому что вы можете легко перемещать circlePath во время разработки и запускать время очень легко. –

7

Код Swift 2.0 совместимый

Начиная с @animal_inch ответа, я код немного утилита класса, надеюсь, что он оценит:

import Foundation 
import UIKit 
import CoreGraphics 

/// Apply a circle mask on a target view. You can customize radius, color and opacity of the mask. 
class CircleMaskView { 

    private var fillLayer = CAShapeLayer() 
    var target: UIView? 

    var fillColor: UIColor = UIColor.grayColor() { 
     didSet { 
      self.fillLayer.fillColor = self.fillColor.CGColor 
     } 
    } 

    var radius: CGFloat? { 
     didSet { 
      self.draw() 
     } 
    } 

    var opacity: Float = 0.5 { 
     didSet { 
      self.fillLayer.opacity = self.opacity 
     } 
    } 

    /** 
    Constructor 

    - parameter drawIn: target view 

    - returns: object instance 
    */ 
    init(drawIn: UIView) { 
     self.target = drawIn 
    } 

    /** 
    Draw a circle mask on target view 
    */ 
    func draw() { 
     guard (let target = target) else { 
      print("target is nil") 
      return 
     } 

     var rad: CGFloat = 0 
     let size = target.frame.size 
     if let r = self.radius { 
      rad = r 
     } else { 
      rad = min(size.height, size.width) 
     } 

     let path = UIBezierPath(roundedRect: CGRectMake(0, 0, size.width, size.height), cornerRadius: 0.0) 
     let circlePath = UIBezierPath(roundedRect: CGRectMake(size.width/2.0 - rad/2.0, 0, rad, rad), cornerRadius: rad) 
     path.appendPath(circlePath) 
     path.usesEvenOddFillRule = true 

     fillLayer.path = path.CGPath 
     fillLayer.fillRule = kCAFillRuleEvenOdd 
     fillLayer.fillColor = self.fillColor.CGColor 
     fillLayer.opacity = self.opacity 
     self.target.layer.addSublayer(fillLayer) 
    } 

    /** 
    Remove circle mask 
    */ 


    func remove() { 
     self.fillLayer.removeFromSuperlayer() 
    } 

} 

Затем, где бы вы ни находились:

let circle = CircleMaskView(drawIn: <target_view>) 
circle.opacity = 0.7 
circle.draw() 
19

Чтобы создать этот эффект, мне было проще всего создать весь вид, накладывающийся на экран, а затем вычитая части экрана с помощью слоев и UIBezierPaths. Для быстрой реализации:

// Create a view filling the screen. 
let overlay = UIView(frame: CGRectMake(0, 0, 
    UIScreen.mainScreen().bounds.width, 
    UIScreen.mainScreen().bounds.height)) 

// Set a semi-transparent, black background. 
overlay.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.85) 

// Create the initial layer from the view bounds. 
let maskLayer = CAShapeLayer() 
maskLayer.frame = overlay.bounds 
maskLayer.fillColor = UIColor.blackColor().CGColor 

// Create the frame for the circle. 
let radius: CGFloat = 50.0 
let rect = CGRectMake(
     CGRectGetMidX(overlay.frame) - radius, 
     CGRectGetMidY(overlay.frame) - radius, 
     2 * radius, 
     2 * radius) 

// Create the path. 
let path = UIBezierPath(rect: overlay.bounds) 
maskLayer.fillRule = kCAFillRuleEvenOdd 

// Append the circle to the path so that it is subtracted. 
path.appendPath(UIBezierPath(ovalInRect: rect)) 
maskLayer.path = path.CGPath 

// Set the mask of the view. 
overlay.layer.mask = maskLayer 

// Add the view so it is visible. 
self.view.addSubview(overlay) 

Я тестировал код выше, и вот результат:

enter image description here

Я добавил библиотеку CocoaPods, что абстрагирует много кода выше и позволяет вы можете легко создавать наложения с прямоугольными/круглыми отверстиями, позволяя пользователю взаимодействовать с видами за оверлей. Я использовал его, чтобы создать этот учебник для одного из наших приложений:

Tutorial using the TAOverlayView

Библиотека называется TAOverlayView, и является открытым исходным кодом под Apache 2.0. Надеюсь, вы сочтете это полезным!

+1

Пример кода мог бы сделать это хорошим ответом. – Mogsdad

+0

Кроме того, не отправляйте [дублированные ответы] (// meta.stackexchange.com/a/211726/206345). Вместо этого рассмотрите другие действия, которые помогут будущим пользователям найти ответ, который им нужен, как описано в связанной записи. Когда эти ответы едва ли больше, чем ссылка и рекомендация использовать ваши вещи, они выглядят довольно спам. – Mogsdad

+1

@Mogsdad Я не хотел появляться спам, я просто потратил много времени на эту библиотеку, и я решил, что это будет полезно людям, которые пытаются делать подобные вещи. Но спасибо за отзывы, я буду обновлять свои ответы, чтобы использовать примеры кода –

8

Принято решение Swift 3,0 совместимый

let radius = myRect.size.width 
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: self.mapView.bounds.size.width, height: self.mapView.bounds.size.height), cornerRadius: 0) 
let circlePath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 2.0*radius, height: 2.0*radius), cornerRadius: radius) 
path.append(circlePath) 
path.usesEvenOddFillRule = true 

let fillLayer = CAShapeLayer() 
fillLayer.path = path.cgPath 
fillLayer.fillRule = kCAFillRuleEvenOdd 
fillLayer.fillColor = UIColor.gray.cgColor 
fillLayer.opacity = 0.5 
view.layer.addSublayer(fillLayer) 
+0

@Fattie: ваша ссылка мертва – Randy