52

を使用[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];
4

9 に答える 9

87

実際、Swiftの例のように、ほとんどの場合、はるかに簡単な方法があります。

path.append(cutout.reversing())

これが機能するのは、デフォルトのフィルルールがゼロ以外のワインディングルールであるためです。

于 2015-08-25T09:21:22.953 に答える
63

減算されたパスをストロークしたい場合は、あなた自身です。Appleは、あるパスから別のパスへの減算を返す(または単にストロークする)APIを提供していません。

(サンプル画像のように)減算されたパスを埋めたいだけの場合は、クリッピングパスを使用してそれを行うことができます。ただし、トリックを使用する必要があります。クリッピングパスにパスを追加すると、新しいクリッピングパスは、古いクリッピングパスと追加されたパスの交点になります。したがってsmallMaskPath、クリッピングパスに追加するだけでは、内部の領域のみを埋めるsmallMaskPathことになります。これは、必要な領域とは逆になります。

あなたがする必要があるのは、既存のクリッピングパスをの逆数と交差させることですsmallMaskPath。幸いなことに、偶数奇数の巻線ルールを使用すると、これを非常に簡単に行うことができます。偶奇規則については、Quartz2Dプログラミングガイドを参照してください。

基本的な考え方は、2つのサブパスを持つ複合パスを作成することです。1つと、塗りつぶしたいピクセルを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());
于 2012-01-14T05:22:50.667 に答える
34

これでうまくいくはずです。好きなようにサイズを調整してください。

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

目的の効果を得るもう1つの非常に簡単な方法は、外側の丸い長方形を描画し、色を変更して、内側の丸い長方形をその上に描画することです。

于 2012-01-14T07:48:39.867 に答える
13

@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(Double.pi / 2), endAngle: CGFloat(Double.pi + Double.pi / 2), clockwise: false)
leftSubPath.close()

let rightSubPath = UIBezierPath(arcCenter: CGPoint(x:rect.width, y:rect.height / 2), 
                               radius: subPathSideSize / 2, startAngle:  CGFloat(Double.pi / 2), endAngle: CGFloat(Double.pi + Double.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

someView.layer.mask = mask 

view
PlaygroundPage.current.liveView = view

ここに画像の説明を入力してください

ここに画像の説明を入力してください

于 2017-03-09T15:28:51.617 に答える
9

2019-簡単すぎる

これは非常に簡単なので、他の答えにも驚いています。私が理解していないいくつかの要件があるかもしれません。だが:

p = UIBezierPath(rect: .. )
let hole = UIBezierPath(ovalIn: ... )
p.append(hole.reversing())
p.usesEvenOddFillRule = false

これは、切断の有無に関係なく完全に機能します。

重複していても問題ありません。

ここに画像の説明を入力してください

ここに画像の説明を入力してください

(これは、「ノッチ」または「インデント」を作成するための優れた方法です。)

テクニックは

  1. 外側のパスはcwになり、内側のパスはccwになります
  2. カウントルール、別名非ゼロルールを使用する

(この例では、文字通りUIViewのマスクとして使用しています...)

layerToUseAsUIViewMask.path = p
layer.mask = layerToUseAsUIViewMask

ホールコードを入力する人を救うために...

let hole = UIBezierPath(ovalIn: CGRect(
    origin: CGPoint(x: 70, y: 10), .. use -10 for the second demo above.
    size: CGSize(width: 50, height: 50))
)

2つのパスがどちらの方向に実行されているかわからない場合は、もちろん、それぞれを試してください。

p.append(hole.reversing()) // that's acw currently on iOS

p.append(hole) // that's cw currently on iOS

それが機能するまで。


同様の役立つヒント:

グローボックスを作成します。

https://stackoverflow.com/a/59092828/294884

于 2019-08-15T18:16:29.750 に答える
5

複数の重複するCGPathを使用してこれを行う方法を見つけようとして、壁に頭をぶつけてきました。複数回オーバーラップする場合は、上記のソリューションで補充されます。複数の重複するパスで「減算」効果を真に達成する方法は、コンテキストのブレンドモードをクリアに設定することです。

CGContextSetBlendMode(ctx, kCGBlendModeClear);

于 2014-04-16T21:51:04.220 に答える
2

Patrickは、ゼロ以外の巻線ルールを使用して、ユーザー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()

これがその要点です。

これをストーリーボードに入れて、出力を確認して試してみることができます。

結果

于 2016-04-23T06:01:23.777 に答える
1

ビューの2つの透明な穴は次のとおりです。

ここに画像の説明を入力してください

@IBDesignable
class HoleView: UIView {
    
    @IBInspectable
    var radius1: CGFloat = 10 { didSet { updateMask() } }
    
    @IBInspectable
    var XPositionPercent1: CGFloat = 90 { didSet { updateMask() } }
    
    @IBInspectable
    var YPositionPercent1: CGFloat = 50 { didSet { updateMask() } }
    
    
    @IBInspectable
    var radius2: CGFloat = 10 { didSet { updateMask() } }
    
    @IBInspectable
    var XPositionPercent2: CGFloat = 10 { didSet { updateMask() } }
    
    @IBInspectable
    var YPositionPercent2: CGFloat = 50 { didSet { updateMask() } }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        updateMask()
    }
    
    private func updateMask() {
        
        let path = UIBezierPath(rect: bounds)
        path.usesEvenOddFillRule = false
        
        
        // MARK: Hole 1
        let XPos1 = (bounds.width * XPositionPercent1) / 100
        let YPos1 = (bounds.height * YPositionPercent1) / 100
        let center1 = CGPoint(x: XPos1, y: YPos1)
        
        let hole1 = UIBezierPath(arcCenter: center1,
                                 radius: radius1,
                                 startAngle: 0,
                                 endAngle: 2 * .pi,
                                 clockwise: true)
        path.append(hole1)
        
        // MARK: Hole 2
        let XPos2 = (bounds.width * XPositionPercent2) / 100
        let YPos2 = (bounds.height * YPositionPercent2) / 100
        let center2 = CGPoint(x: XPos2, y: YPos2)
        let hole2 = UIBezierPath(arcCenter: center2,
                                 radius: radius2,
                                 startAngle: 0,
                                 endAngle: 2 * .pi,
                                 clockwise: true)
        path.append(hole2)
        
        
        
        let mask1 = CAShapeLayer()
        mask1.fillRule = .evenOdd
        mask1.path = path.cgPath
        
        layer.mask = mask1
    }
}
于 2021-10-22T07:38:37.320 に答える
-1

減算する代わりに、CGPathusingCGPathAddLineToPointとを作成することで解決できましたCGPathAddArcToPointUIBiezerPathこれは、同様のコードを使用して実行することもできます。

于 2012-01-14T23:47:38.257 に答える