Şeffaf delikli CALayer


112

Basit bir görünüme sahibim (resmin sol tarafı) ve bu görünüme bir çeşit kaplama (resmin sağ tarafı) oluşturmam gerekiyor. Bu bindirmenin bir miktar opaklığı olması gerekir, bu nedenle alttaki görünüm hala kısmen görülebilir. En önemlisi, bu bindirmenin ortasında dairesel bir delik olmalıdır, böylece görünümün merkezini kaplamaz (aşağıdaki resme bakın).

Kolayca böyle bir çevre oluşturabilirim:

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;

Ve bunun gibi "tam" bir dikdörtgen kaplama:

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

Ama bu iki katmanı nasıl birleştirebileceğimi bilmiyorum ki istediğim efekti yaratsınlar. Kimse? Gerçekten her şeyi denedim ... Yardım için çok teşekkürler!

görüntü


Rekt ve daireyi içeren bir bezier oluşturabilir misiniz ve sonra çizim sırasında kullanılan sarma kuralı bir delik oluşturacaktır (denemedim).
Wain

bunu nasıl yapacağımı bilmiyorum :)
animal_chin

Rekt ile oluşturun moveToPoint, ardından yuvarlatılmış rektifini ekleyin. Tarafından sunulan yöntemler için belgeleri kontrol edin UIBezierPath.
Wain

Bu benzer soru ve cevabın yardım edip etmediğine bakın: [
UIView'de

Yanıtlar:


218

Bunu Jon Steinmetz'in önerisiyle çözebildim. Herhangi biri umursuyorsa, işte nihai çözüm:

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)

Swift 4.2 ve 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
Daha fazla esneklik için, görünüm alt sınıfınızı "IBDesignable" yapın. Gerçekten çok kolay! Başlamak için, yukarıdaki kodu bu soruya
verdiğim yanıta ekleyin

2
Acemi bir iOS geliştiricisi olarak, bu kodun neden garip sonuçlar verdiğini anlamaya çalışmak için birkaç saat harcadım. Sonunda, bindirme maskesi bir noktada yeniden hesaplanırsa, eklenen alt katmanların kaldırılması gerektiğini anladım. Bu, view.layer.sublayers özelliği ile mümkündür. Cevabınız için çok teşekkür ederim!
Serzhas

Neden tam tersini alıyorum. Siyah yarı şeffaf şekilli açık renkli katman?
Chanchal Warde

Bu modu kullanarak bir daireye nasıl şeffaf bir metin ekleyebilirim? nasıl olduğunu bulamıyorum
Diego Fernando Murillo Valenci

Neredeyse 6 yıl, yine de yardımcı oluyor, ancak boş deliğin onu tutan tabakayı gerçekten 'delmediğini' unutmayın. Deliği bir düğmenin üzerine bindirdiğinizi söyleyin. Düğmeye erişilemez, bu da benim gibi 'rehberli bir eğitim' yapmaya çalışıyorsanız gereklidir. @Nick Yap tarafından sağlanan kitaplık, işi sizin için yapacak, burada UIView işlev noktasını (iç nokta: CGPoint, olay: UIEvent?) -> Bool {} ile geçersiz kılarak. Daha fazla ayrıntı için kütüphanesine bakın. Ancak, beklediğiniz şey sadece 'maskenin ardında olanın görünürlüğüdür', bu geçerli bir cevap.
infinity_coding7

32

Bu efekti yaratmak için, ekranı kaplayan bütün bir görünümü oluşturmanın ve ardından katmanları ve UIBezierPaths'i kullanarak ekranın bölümlerini çıkarmayı en kolay buldum. Swift uygulaması için:

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

Yukarıdaki kodu test ettim ve işte sonuç:

görüntü açıklamasını buraya girin

CocoaPods'a, yukarıdaki kodun çoğunu soyutlayan ve dikdörtgen / dairesel delikli kaplamalar oluşturmanıza olanak tanıyan ve kullanıcının kaplamanın arkasındaki görünümlerle etkileşime girmesine olanak tanıyan bir kitaplık ekledim. Uygulamalarımızdan biri için bu öğreticiyi oluşturmak için kullandım:

TAOverlayView kullanarak eğitim

Kitaplık TAOverlayView olarak adlandırılır ve Apache 2.0 altında açık kaynaklıdır. Umarım yararlı bulursunuz!


Ayrıca, lütfen yinelenen yanıtlar göndermeyin . Bunun yerine, bağlantılı gönderide açıklandığı gibi, gelecekteki kullanıcıların ihtiyaç duydukları cevabı bulmalarına yardımcı olabilecek diğer işlemleri düşünün. Bu yanıtlar, içeriklerinizi kullanmanız için bir bağlantı ve öneriden biraz daha fazlası olduğunda, oldukça spam gibi görünürler.
Mogsdad

1
@Mogsdad Spam gibi görünmek istemedim, sadece bu kitaplıkta epey zaman geçirdim ve benzer şeyler yapmaya çalışan insanlar için yararlı olacağını düşündüm. Ancak geri bildirim için teşekkürler, kod örneklerini kullanmak için yanıtlarımı güncelleyeceğim
Nick Yap

3
Güzel güncelleme, Nick. Ben senin köşesindeyim - kendim de yayınlanmış kütüphaneler ve yardımcı programlar var ve belgelerim zaten kapsadığında buraya tam cevaplar koymanın gereksiz görünebileceğini anlıyorum ... ancak fikir, cevapları bağımsız olarak tutmaktır olabildiğince. Ve orada vardır bu yüzden oldukça onlarla aynı kefeye edersin başka bir şey Spam gönderme insanlar. Aynı fikirde olduğunuzu varsayıyorum, bu yüzden size işaret ettim. Şerefe!
Mogsdad

Yarattığın bölmeyi kullandım, bunun için teşekkürler. Ancak yer paylaşımının altındaki görüşlerim etkileşim kurmayı bırakıyor. Bunun nesi var? İçinde görüntü olan bir Scrollview var.
Ammar Mujeeb

@AmmarMujeeb Kaplama, oluşturduğunuz "delikler" dışında etkileşimi engeller. Bölmeyle ilgili niyetim, ekranın bazı kısımlarını vurgulayan ve yalnızca vurgulanan öğelerle etkileşime girmenize izin veren bindirmelerdi.
Nick Yap

11

Kabul edilen çözüm Swift 3.0 uyumlu

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: Bağlantınız öldü
Randy

10

Animal_chin ile benzer bir yaklaşım benimsedim, ancak daha görselim, bu yüzden çoğunu çıkışları ve otomatik düzeni kullanarak Interface Builder'da kurdum.

İşte Swift'deki çözümüm

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

Bu çözümü seviyorum çünkü circlePath'i tasarım zamanında hareket ettirebilir ve zamanı çok kolay çalıştırabilirsiniz.
Mark Moeykens

bu benim için işe yaramadı, ancak oval yerine normal bir
dikdörtgen

7

Code Swift 2.0 uyumlu

@Animal_inch cevabından başlayarak, küçük bir fayda sınıfı kodluyorum, umarım beğenir:

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()
    }

}

Ardından, kodunuzun neresinde olursa olsun:

let circle = CircleMaskView(drawIn: <target_view>)
circle.opacity = 0.7
circle.draw()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.