透明な穴が開いた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];

しかし、私はこれらの2つのレイヤーをどのように組み合わせることができるかわからないので、必要な効果を生み出します。誰でも?私は本当にすべてを試しました...助けてくれてありがとう!

画像


四角形と円を含む1つのベジェを作成できますか?次に、描画中に使用されるワインディングルールによって穴が作成されます(私は試していません)。
2013年

私はそれを行う方法がわかりません:)
animal_chin

四角形で作成し、次にmoveToPoint、四角形の四角形を追加します。が提供するメソッドについては、ドキュメントを確認してくださいUIBezierPath
2013年

表示された場合、この類似した質問と回答ヘルプ:[UIViewの中にカット透明穴] [1] [1]:stackoverflow.com/questions/9711248/...
dichen

ここで私の解決策を確認してください:stackoverflow.com/questions/14141081/… これが誰かを助けることを願っています
James Laurenstin

回答:


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

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および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プロパティを介して可能です。回答ありがとうございます!
セルジャス2016年

なぜ私はそれと正反対のことをしているのですか?黒い半透明の形をした透明な着色層??
Chanchal Warde 2017

このモードを使用して円に透明なテキストを追加するにはどうすればよいですか?方法がわかりません
ディエゴフェルナンドムリーリョバレンシ2018年

ほぼ6年間はまだ役に立ちますが、中空の穴はそれを保持するレイヤーを実際に「パンチスルー」しないことに注意してください。ボタンの上に穴をオーバーレイする場合は、言う。ボタンにアクセスできません。私のような「ガイド付きチュートリアル」を作成しようとしている場合に必要です。@Nick Yapが提供するライブラリがあなたのために仕事をしてくれます。そこでは、funcポイント(ポイント: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に追加しました。これにより、ユーザーはオーバーレイの背後にあるビューを操作できます。これを使用して、アプリの1つにこのチュートリアルを作成しました。

TAOverlayViewを使用したチュートリアル

ライブラリはTAOverlayViewと呼ばれ、Apache 2.0でのオープンソースです。お役に立てれば幸いです。


また、重複する回答を投稿しないでください。代わりに、リンクされた投稿で説明されているように、将来のユーザーが必要な答えを見つけるのに役立つ可能性のある他のアクションを検討してください。それらの回答がリンクやあなたのコンテンツを使用するための推奨事項に過ぎない場合、それらはかなりスパムのように見えます。
Mogsdad 2016年

1
@Mogsdad私はスパム行為をしたくありませんでした。このライブラリにかなりの時間を費やしただけで、同様のことをしようとする人にとって有用だと思いました。しかし、フィードバックに感謝して、コード例を使用するように回答を更新します
Nick Yap

3
素敵なアップデート、ニック。私はあなたのコーナーにいます-私は自分で公開されたライブラリとユーティリティを持っています、そして私のドキュメントがすでにそれをカバーしているとき、ここに完全な答えを置くことは冗長に見えるかもしれません...しかし、考えは答えを自己完結型として保つことですできるだけ。また、スパム以外の投稿をしない人もいるので、ひとまとめにしたくない。私はあなたが同じ心であると思います、それが私があなたにそれを指摘した理由です。乾杯!
Mogsdad 2016年

あなたが作成したポッドを使用しました。ありがとうございます。しかし、オーバーレイの下の私のビューは相互作用を停止します。どうしたの?Scrollviewの中にimageviewが入っています。
Ammar Mujeeb 2017

@AmmarMujeebオーバーレイは、作成した「穴」を除いて相互作用をブロックします。ポッドについての私の意図は、画面の一部を強調表示するオーバーレイであり、強調表示された要素を操作することのみを許可することでした。
Nick Yap

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:リンクが切れています
Randy

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

設計時と実行時にCirclePathを非常に簡単に移動できるため、このソリューションが気に入っています。
Mark Moeykens 2017年

楕円ではなく通常の四角形を使用するように変更しましたが、これは私にとっては機能しませんでしたが、最終的なマスクイメージが間違って出力されています:(
GameDev

7

Code 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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.