Ios

丸みを帯びたエッジを持つUIBezierPath三角形



Uibezierpath Triangle With Rounded Edges



解決:

編集

FWIW: この答えは、何を説明することによってその教育目的に役立ちますCGPathAddArcToPoint(...)があなたに代わって行います。 CGPath APIを理解し、理解するのに役立つので、これを一読することを強くお勧めします。次に、アプリのエッジを丸めるときに、このコードの代わりに、an0の回答に示されているように、先に進んでそれを使用する必要があります。このコードは、このようなジオメトリ計算を試して学びたい場合にのみ参照として使用する必要があります。


元の答え

このような質問はとても楽しいと思うので、私はそれに答えなければなりませんでした:)



これは長い答えです。短いバージョンはありません:D


注:私自身の単純さのために、私の解決策は、三角形を形成するために使用されている点について、次のようないくつかの仮定を行うことです。



  • 三角形の面積は、丸みを帯びた角に合うのに十分な大きさです(たとえば、三角形の高さが角の円の直径よりも大きいです。発生する可能性のある奇妙な結果をチェックしたり、防止したりしていません。それ以外は。
  • コーナーは反時計回りの順序でリストされています。どのような順序でも機能させることができますが、簡単にするために追加するのに十分な制約のように感じました。

必要に応じて、同じ手法を使用して、厳密に凸状である(つまり、先のとがった星ではない)限り、任意のポリゴンを丸めることができます。方法については説明しませんが、同じ原則に従います。


それはすべて三角形から始まります。それはあなたがいくつかの半径で角を丸めたいということです、 NS

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



丸みを帯びた三角形は先のとがった三角形に含まれている必要があるため、最初のステップは、半径rの円を合わせることができる、できるだけ角に近い場所を見つけることです。

これを行う簡単な方法は、三角形の3つの辺に平行な3つの新しい線を作成し、それぞれの距離をシフトすることです。 NS 内側、元の側面の側面に直交します。

これを行うには、各線の傾き/角度と、2つの新しいポイントに適用するオフセットを計算します。

CGFloat角度= atan2f(end.y-start.y、end.x-start.x); CGVectorオフセット= CGVectorMake(-sinf(angle)* radius、cosf(angle)* radius);

注:わかりやすくするために、CGVectorタイプ(iOS 7で使用可能)を使用していますが、ポイントまたはサイズを使用して、以前のOSバージョンで作業することもできます。

次に、各線の始点と終点の両方にオフセットを追加します。

CGPoint offsetStart = CGPointMake(start.x + offset.dx、start.y + offset.dy); CGPoint offsetEnd = CGPointMake(end.x + offset.dx、end.y + offset.dy);

これを行うと、3つの線が3つの場所で互いに交差していることがわかります。

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

各交点は正確に距離です NS 両側から (上記のように、三角形が十分に大きいと仮定します)

2本の線の交点は次のように計算できます。

//(x1⋅y2-y1⋅x2)(x3-x4)-(x1-x2)(x3⋅y4-y3⋅x4)// px = ––––––––––––––– –––––––––––––––––––––––––––– //(x1-x2)(y3-y4)-(y1-y2)(x3-x4 )//(x1⋅y2-y1⋅x2)(y3-y4)-(y1-y2)(x3⋅y4-y3⋅x4)// py = –––––––––––––– ––––––––––––––––––––––––––––– //(x1-x2)(y3-y4)-(y1-y2)(x3- x4)CGFloat交差点X =((x1 * y2-y1 * x2)*(x3-x4)-(x1-x2)*(x3 * y4-y3 * x4))/((x1-x2)*(y3-y4 )-(y1-y2)*(x3-x4)); CGFloat交差点Y =((x1 * y2-y1 * x2)*(y3-y4)-(y1-y2)*(x3 * y4-y3 * x4))/((x1-x2)*(y3-y4)- (y1-y2)*(x3-x4)); CGPoint交差= CGPointMake(intersectionX、intersectionY);

ここで、(x1、y1)から(x2、y2)は最初の行で、(x3、y3)から(x4、y4)は2番目の行です。

次に、半径のある円を配置すると NS 、各交点で、実際に丸みを帯びた三角形(三角形と円の異なる線幅を無視)に対してそれが行われることがわかります。

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

次に、丸みを帯びた三角形を作成するために、元の三角形が交点に直交する点で、線から円弧、線(など)に変化するパスを作成します。これは、円が元の三角形に接する点でもあります。

三角形の3つの辺すべての傾き、角の半径、円の中心(交点)がわかっているので、丸みを帯びた各角の開始角度と停止角度は、その辺の傾き(90度)です。これらをグループ化するために、コードで構造体を作成しましたが、必要がない場合は構造体を作成する必要はありません。

typedef struct {CGPoint centerPoint; CGFloat startAngle; CGFloat endAngle; } CornerPoint;

コードの重複を減らすために、あるポイントから別のポイントを経由して最終ポイントまでの線を指定して、あるポイントの交点と角度を計算するメソッドを自分で作成しました(閉じていないため、三角形ではありません)。

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

コードは次のとおりです(実際には、上に示したコードをまとめたものです)。

-(CornerPoint)roundedCornerWithLinesFrom:(CGPoint)from via:(CGPoint)via to:(CGPoint)to withRadius:(CGFloat)radius {CGFloat fromAngle = atan2f(via.y --from.y、via.x --from.x) ; CGFloat toAngle = atan2f(to.y-via.y、to.x-via.x); CGVector fromOffset = CGVectorMake(-sinf(fromAngle)* radius、cosf(fromAngle)* radius); CGVector toOffset = CGVectorMake(-sinf(toAngle)* radius、cosf(toAngle)* radius); CGFloat x1 = from.x + fromOffset.dx; CGFloat y1 = from.y + fromOffset.dy; CGFloat x2 = via.x + fromOffset.dx; CGFloat y2 = via.y + fromOffset.dy; CGFloat x3 = via.x + toOffset.dx; CGFloat y3 = via.y + toOffset.dy; CGFloat x4 = to.x + toOffset.dx; CGFloat y4 = to.y + toOffset.dy; CGFloat交差点X =((x1 * y2-y1 * x2)*(x3-x4)-(x1-x2)*(x3 * y4-y3 * x4))/((x1-x2)*(y3-y4)- (y1-y2)*(x3-x4)); CGFloat交差点Y =((x1 * y2-y1 * x2)*(y3-y4)-(y1-y2)*(x3 * y4-y3 * x4))/((x1-x2)*(y3-y4)- (y1-y2)*(x3-x4)); CGPoint交差= CGPointMake(intersectionX、intersectionY); CornerPointコーナー; corner.centerPoint =交差点; corner.startAngle = fromAngle-M_PI_2; corner.endAngle = toAngle-M_PI_2;リターンコーナー; }

次に、そのコードを3回使用して、3つのコーナーを計算しました。

CornerPoint leftCorner = [self roundedCornerWithLinesFrom:right via:left to:top withRadius:radius]; CornerPoint topCorner = [self roundedCornerWithLinesFrom:left via:top to:right withRadius:radius]; CornerPoint rightCorner = [self roundedCornerWithLinesFrom:top via:right to:left withRadius:radius];

これで、必要なデータがすべて揃ったので、実際のパスを作成する部分を開始します。 CGPathAddArcが現在のポイントから開始ポイントまで直線を追加するという事実に依存して、それらの線を自分で描画する必要がないようにします(これは文書化された動作です)。

手動で計算する必要がある唯一のポイントは、パスの開始ポイントです。右下隅の始点を選択します(特別な理由はありません)。そこから、開始角度と終了角度からの交点を中心とする円弧を追加するだけです。

CGMutablePathRef roundedTrianglePath = CGPathCreateMutable(); //手動で計算された開始点CGPathMoveToPoint(roundedTrianglePath、NULL、leftCorner.centerPoint.x + radius * cosf(leftCorner.startAngle)、leftCorner.centerPoint.y + radius * sinf(leftCorner.startAngle)); // 3つのコーナーに3つの円弧を追加しますCGPathAddArc(roundedTrianglePath、NULL、leftCorner.centerPoint.x、leftCorner.centerPoint.y、radius、leftCorner.startAngle、leftCorner.endAngle、NO); CGPathAddArc(roundedTrianglePath、NULL、topCorner.centerPoint.x、topCorner.centerPoint.y、radius、topCorner.startAngle、topCorner.endAngle、NO); CGPathAddArc(roundedTrianglePath、NULL、rightCorner.centerPoint.x、rightCorner.centerPoint.y、radius、rightCorner.startAngle、rightCorner.endAngle、NO); //パスを閉じますCGPathCloseSubpath(roundedTrianglePath);

このように見えます:

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

すべてのサポートラインがない場合の最終結果は、次のようになります。

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


@Davidのジオメトリはクールで教育的です。しかし、実際には、この方法でジオメトリ全体を調べる必要はありません。もっと簡単なコードを提供します:

CGFloat半径= 20; CGMutablePathRefパス= CGPathCreateMutable(); CGPathMoveToPoint(path、NULL、(center.x + bottomLeft.x)/ 2、(center.y + bottomLeft.y)/ 2); CGPathAddArcToPoint(path、NULL、bottomLeft.x、bottomLeft.y、bottomRight.x、bottomRight.y、radius); CGPathAddArcToPoint(path、NULL、bottomRight.x、bottomRight.y、center.x、center.y、radius); CGPathAddArcToPoint(path、NULL、center.x、center.y、bottomLeft.x、bottomLeft.y、radius); CGPathCloseSubpath(path); UIBezierPath * bezierPath = [UIBezierPath bezierPathWithCGPath:path]; CGPathRelease(path);

bezierPathはあなたが必要とするものです。重要な点はCGPathAddArcToPointがジオメトリを作成します。


Swift4の丸みを帯びた三角形

上記の@ an0の回答に基づく遊び場。

迅速な丸みを帯びた三角形

import UIKit import PlaygroundSupport import CoreGraphics var view = UIView(frame:CGRect(x:0、y:0、width:300、height:300))view.backgroundColor = .black PlaygroundPage.current.liveView = view let triangle = CAShapeLayer( )triangle.fillColor = UIColor.systemGreen.cgColor triangle.path = createRoundedTriangle(width:220、height:200、radius:15)triangle.position = CGPoint(x:140、y:130)view.layer.addSublayer(triangle) func createRoundedTriangle(width:CGFloat、height:CGFloat、radius:CGFloat)-> CGPath {//原点を中心に三角形のパスを描画します。 let point1 = CGPoint(x:-width / 2、y:height / 2)let point2 = CGPoint(x:0、y:-height / 2)let point3 = CGPoint(x:width / 2、y:height / 2 )let path = CGMutablePath()path.move(to:CGPoint(x:0、y:height / 2))path.addArc(tangent1End:point1、tangent2End:point2、radius:radius)path.addArc(tangent1End:point2、タンジェント2エンド:ポイント3、半径:半径)path.addArc(タンジェント1エンド:ポイント3、タンジェント2エンド:ポイント1、半径:半径)path.closeSubpath()リターンパス}