Ios

UIBezierPath Triangle med afrundede kanter

Uibezierpath Triangle With Rounded Edges



Løsning:

Redigere

FWIW: Dette svar tjener sit uddannelsesmæssige formål ved at forklare hvadCGPathAddArcToPoint (...) gør for dig. Jeg vil meget anbefale, at du læser det igennem, da det vil hjælpe dig med at forstå og værdsætte CGPath API. Så skal du gå videre og bruge det, som det ses i an0s svar, i stedet for denne kode, når du afrunder kanter i din app. Denne kode bør kun bruges som reference, hvis du vil lege med og lære om geometriberegninger som denne.


Originalt svar

Fordi jeg synes, at spørgsmål som dette er så sjove, måtte jeg svare på det :)



Dette er et langt svar. Der er ingen kort version: D




Bemærk: For min egen enkelhed gør min løsning nogle antagelser om de punkter, der bruges til at danne trekanten, såsom:



  • Trekantens areal er stort nok til at passe til det afrundede hjørne (f.eks. Er trekanthøjden større end diameteren på cirklerne i hjørnerne. Jeg kontrollerer ikke eller forsøger at forhindre nogen form for mærkelige resultater, der kan ske Ellers.
  • Hjørnerne er angivet i rækkefølge mod uret. Du kunne få det til at fungere for enhver ordre, men det føltes som en rimelig begrænsning at tilføje for enkelhedens skyld.

Hvis du ville, kunne du bruge den samme teknik til at afrunde enhver polygon, så længe den er strengt konveks (dvs. ikke en spids stjerne). Jeg vil dog ikke forklare, hvordan man gør det, men det følger det samme princip.


Det hele starter med en trekant, som du vil afrunde hjørnerne med med en radius, r :

indtast billedbeskrivelse her



Den afrundede trekant skal være indeholdt i den spidse trekant, så det første trin er at finde de placeringer, så tæt på hjørnerne som muligt, hvor du kan passe en cirkel med radius, r.

En enkel måde at gøre dette på er at oprette 3 nye linjer parallelt med de 3 sider i trekanten og flytte hver af afstanden r indad, vinkelret på siden af ​​den originale side.

For at gøre dette beregner du hældningen/vinklen for hver linje og forskydningen til at gælde for de to nye punkter:

CGFloat vinkel = atan2f (end.y - start.y, end.x - start.x); CGVector offset = CGVectorMake (-sinf (vinkel)*radius, cosf (vinkel)*radius);

Bemærk: For klarhedens skyld bruger jeg CGVector -typen (tilgængelig i iOS 7), men du kan lige så godt bruge et punkt eller en størrelse til at arbejde med tidligere OS -versioner .

derefter tilføjer du forskydningen til både start- og slutpunkter for hver linje:

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

Når du gør det, vil du se, at de tre linjer skærer hinanden tre steder:

indtast billedbeskrivelse her

Hvert skæringspunkt er nøjagtigt afstanden r fra to af siderne (forudsat at trekanten er stor nok, som angivet ovenfor) .

Du kan beregne skæringspunktet mellem to linjer som:

// (x1⋅y2-y1⋅x2) (x3-x4)-(x1-x2) (x3⋅y4-y3⋅x4) // px = –––––––––––––––––––––––– ––––---------- ) // (x1⋅y2-y1⋅x2) (y3-y4)-(y1-y2) (x3⋅y4-y3⋅x4) // py = –––––––––––––––– ––––---------- x4) CGFloat-kryds X = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4 )-(y1-y2)*(x3-x4)); CGFloat-skæringspunkt Y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)- (y1-y2)*(x3-x4)); CGPoint -kryds = CGPointMake (intersectionX, krydsY);

hvor (x1, y1) til (x2, y2) er den første linje og (x3, y3) til (x4, y4) er den anden linje.

Hvis du derefter sætter en cirkel, med radius r , på hvert skæringspunkt kan du se, at det faktisk vil gøre det for den afrundede trekant (ignorerer de forskellige linjebredder i trekanten og cirklerne):

indtast billedbeskrivelse her

For at oprette den afrundede trekant vil du oprette en sti, der skifter fra en linje til en bue til en linje (osv.) På de punkter, hvor den originale trekant er vinkelret på skæringspunkterne. Dette er også det punkt, hvor cirklerne tangerer den originale trekant.

Ved at kende skråningerne på alle 3 sider i trekanten, hjørneradius og midten af ​​cirklerne (skæringspunkterne), er start- og stopvinklen for hvert afrundede hjørne hældningen på den side - 90 grader. For at gruppere disse ting sammen lavede jeg en struct i min kode, men du behøver ikke, hvis du ikke vil:

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

For at reducere duplikering af kode oprettede jeg en metode til mig selv, der beregner skæringspunktet og vinklerne for et punkt givet en linje fra et punkt, via et andet, til et sidste punkt (det er ikke lukket, så det er ikke en trekant):

indtast billedbeskrivelse her

Koden er som følger (det er egentlig bare den kode, jeg har vist ovenfor, samlet):

- (CornerPoint) roundedCornerWithLinesFrom: (CGPoint) fra via: (CGPoint) via til: (CGPoint) til 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 +fraOffset.dx; CGFloat y2 = via.y +fraOffset.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-kryds X = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)- (y1-y2)*(x3-x4)); CGFloat-skæringspunkt Y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)- (y1-y2)*(x3-x4)); CGPoint -kryds = CGPointMake (intersectionX, krydsY); CornerPoint hjørne; corner.centerPoint = skæringspunkt; corner.startAngle = fromAngle - M_PI_2; corner.endAngle = toAngle - M_PI_2; retur hjørne; }

Jeg brugte derefter den kode 3 gange til at beregne de 3 hjørner:

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

Nu, med alle de nødvendige data, starter den del, hvor vi opretter den faktiske sti. Jeg vil stole på, at CGPathAddArc vil tilføje en lige linje fra det aktuelle punkt til startpunktet for ikke at skulle tegne disse linjer selv (dette er dokumenteret adfærd).

Det eneste punkt jeg manuelt skal beregne er startpunktet for stien. Jeg vælger starten på nederste højre hjørne (ingen specifik grund). Derfra tilføjer du bare en bue med midten i skæringspunkterne fra start- og slutvinklen:

CGMutablePathRef roundedTrianglePath = CGPathCreateMutable (); // manuelt beregnet startpunkt CGPathMoveToPoint (roundedTrianglePath, NULL, leftCorner.centerPoint.x + radius*cosf (leftCorner.startAngle), leftCorner.centerPoint.y + radius*sinf (leftCorner.startAngle)); // tilføj 3 buer i de 3 hjørner 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); // luk stien CGPathCloseSubpath (roundedTrianglePath);

Ser sådan ud:

indtast billedbeskrivelse her

Det endelige resultat uden alle understøttelseslinjer ser sådan ud:

indtast billedbeskrivelse her


@Davids geometri er cool og lærerig. Men du behøver virkelig ikke at gå igennem hele geometrien på denne måde. Jeg vil tilbyde en meget enklere kode:

CGFloat radius = 20; CGMutablePathRef sti = CGPathCreateMutable (); CGPathMoveToPoint (sti, NULL, (center.x + bottomLeft.x) / 2, (center.y + bottomLeft.y) / 2); CGPathAddArcToPoint (sti, NULL, bottomLeft.x, bottomLeft.y, bottomRight.x, bottomRight.y, radius); CGPathAddArcToPoint (sti, NULL, bottomRight.x, bottomRight.y, center.x, center.y, radius); CGPathAddArcToPoint (sti, NULL, center.x, center.y, bottomLeft.x, bottomLeft.y, radius); CGPathCloseSubpath (sti); UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath: sti]; CGPathRelease (sti);

bezierPath er, hvad du har brug for. Det centrale punkt er detCGPathAddArcToPoint gør geometrien for dig.


Afrundet trekant i Swift 4

En legeplads baseret på @an0s svar ovenfor.

afrundet trekant hurtigt

import UIKit import PlaygroundSupport import CoreGraphics var view = UIView (ramme: CGRect (x: 0, y: 0, bredde: 300, højde: 300)) view.backgroundColor = .black PlaygroundPage.current.liveView = vis lad trekant = CAShapeLayer ( ) triangle.fillColor = UIColor.systemGreen.cgColor triangle.path = createRoundedTriangle (bredde: 220, højde: 200, radius: 15) triangel.position = CGPoint (x: 140, y: 130) view.layer.addSublayer (trekant) func createRoundedTriangle (bredde: CGFloat, højde: CGFloat, radius: CGFloat) -> CGPath {// Tegn trekantsstien med dens oprindelse i midten. lad punkt1 = CGPoint (x: -bredde / 2, y: højde / 2) lad punkt2 = CGPoint (x: 0, y: -højde / 2) lad punkt3 = CGPoint (x: bredde / 2, y: højde / 2 ) lad sti = CGMutablePath () sti.move (til: CGPoint (x: 0, y: højde / 2)) path.addArc (tangent1End: point1, tangent2End: point2, radius: radius) path.addArc (tangent1End: point2, tangent2End: point3, radius: radius) path.addArc (tangent1End: point3, tangent2End: point1, radius: radius) path.closeSubpath () retursti}