CALayer з прозорим отвором у ньому


112

У мене простий вид (ліва частина малюнка), і мені потрібно створити якусь накладку (праву частину зображення) до цього виду. Ця накладка повинна мати деяку непрозорість, тому вид під нею все ще частково видно. Найголовніше, що ця накладка повинна мати круглий отвір посередині, щоб вона не перекривала центр зору (див. Малюнок нижче).

Я легко можу створити таке коло:

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

Але я поняття не маю, як можна комбінувати ці два шари, щоб вони створили потрібний мені ефект. Хтось? Я спробував насправді все ... Дякую за допомогу!

Зображення


Чи можете ви створити один безір, який містить пряму та коло, і тоді правило намотування, яке використовується під час малювання, створить отвір (я цього не пробував).
Вени

Я не знаю, як це зробити :)
animal_chin

Створіть за допомогою прямої прямої, а moveToPointпотім додайте округлу пряму. Перевірте документи на методи, пропоновані компанією UIBezierPath.
Вени

Дивіться , якщо це подібне питання і відповідь допоможе: [прозорий отвір Cut в UIView] [1] [1]: stackoverflow.com/questions/9711248 / ...
Dichen

Ознайомтесь з моїм рішенням тут: stackoverflow.com/questions/14141081/… Сподіваємось, це комусь допомагає
Джеймс Лоренстін

Відповіді:


218

Мені вдалося вирішити це за пропозицією Джона Штайнмеца. Якщо когось хвилює, ось остаточне рішення:

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

Швидкий 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)

Swift 4.2 та 5:

let radius: CGFloat = myRect.size.width
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.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 = .evenOdd
fillLayer.fillColor = view.backgroundColor?.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)

2
Для додаткової гнучкості зробіть свій підклас подання "IBDesignable". Це дуже просто! Для початку, вставте наведений вище код у відповідь я дав на це питання: stackoverflow.com/questions/14141081 / ...
clozach

2
Як початківець розробник iOS я кілька годин намагався з'ясувати, чому цей код дає дивні результати. Нарешті я виявив, що додані підшари повинні бути видалені, якщо маска накладання буде перерахована в якийсь момент. Це можливо за допомогою властивості view.layer.sublayers. Дуже дякую за відповідь!
Сержас

Чому я отримую саме протилежне до цього. Чіткий кольоровий шар з чорною напівпрозорою формою ??
Chanchal Warde

Як я можу додати прозорий текст до кола за допомогою цього режиму? я не знаю, як
Дієго Фернандо Мурільо Валенсі,

Майже 6 років все ще допомагає, але майте на увазі, що порожнистий отвір насправді не «пробиває» шар, який утримує його. Скажіть, якщо накладіть отвір над кнопкою. Кнопка недоступна, що потрібно, якщо ви намагаєтеся зробити «керований підручник», як я. Бібліотека, надана @Nick Yap, зробить роботу за вас, де переосмисливши функцію (внутрішня точка: CGPoint, з подією: UIEvent?) -> Bool {} з UIView. Перегляньте його бібліотеку для отримання більш детальної інформації. Але те, що ви очікуєте - це лише "видимість того, що стоїть за маскою", це правдива відповідь.
infinity_coding7

32

Щоб створити цей ефект, мені було найпростіше створити цілий вигляд, накладений на екран, а потім відняти частини екрану за допомогою шарів та UIBezierPaths. Для реалізації Swift:

// 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)

Я перевірив код вище, і ось результат:

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

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

Підручник за допомогою TAOverlayView

Бібліотека називається TAOverlayView і є відкритим кодом під Apache 2.0. Сподіваюся, вам це стане в нагоді!


Також, будь ласка, не публікуйте повторюваних відповідей . Натомість розгляньте інші дії, які можуть допомогти майбутнім користувачам знайти відповідь, яка їм потрібна, як описано у пов’язаному дописі. Коли ці відповіді ледь більше, ніж посилання та рекомендація щодо використання ваших речей, вони виглядають досить спам.
Могсдад

1
@Mogsdad Я не хотів з’являтися спамом, я просто витратив багато часу на цю бібліотеку, і я вважав, що це буде корисно людям, які намагаються робити подібні речі. Але дякую за відгук, я оновлю свої відповіді, щоб використати приклади коду
Нік Яп

3
Гарне оновлення, Нік. Я у вашому куточку - я опублікував власні бібліотеки та утиліти, і я розумію, що тут можна видавати повні відповіді, коли моя документація вже охоплює це ... однак ідея полягає в тому, щоб відповіді залишалися незалежними якомога. І є люди, які не публікують нічого, крім спаму, тож я б краще не зациклювався на них. Я припускаю, що ви такої ж думки, саме тому я це вказав вам. Ура!
Могсдад

Я використовував створений вами стручок, дякую за це. Але мої погляди під накладкою перестають взаємодіяти. Що з цим не так? У мене є Scrollview із зображенням перегляду всередині нього.
Ammar Mujeeb

@AmmarMujeeb Накладення блокує взаємодію, за винятком створених вами "дірок". Мій задум із стручком - це накладки, які висвітлюють частини екрану і дозволяють взаємодіяти лише з виділеними елементами.
Нік Яп

11

Прийняте рішення сумісне з 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)

@Fattie: ваше посилання мертве
Ренді

10

Я взяв аналогічний підхід як 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

Мені подобається це рішення, тому що ви можете переміщати круглий шлях в час проектування і час роботи дуже легко.
Марк Мойкенс

це не спрацювало для мене, хоча я змінив його, щоб використовувати звичайну прямокутника замість овалу, але остаточне зображення маски просто виходить неправильним :(
GameDev

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.