iOS
UIBezierPath
Szukaj…
Jak zastosować promień narożnika do prostokątów narysowanych przez UIBezierPath
Promień narożnika dla wszystkich 4 krawędzi:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(x,y,width,height) cornerRadius: 11];
[UIColor.grayColor setFill];
[rectanglePath fill];
Promień narożnika lewej górnej krawędzi:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(x,y,width,height) byRoundingCorners: UIRectCornerTopLeft cornerRadii: CGSizeMake(11, 11)];
[rectanglePath closePath];
[UIColor.grayColor setFill];
[rectanglePath fill];
Promień narożnika dla prawej górnej krawędzi:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(x,y,width,height) byRoundingCorners: UIRectCornerTopRight cornerRadii: CGSizeMake(11, 11)];
[rectanglePath closePath];
[UIColor.grayColor setFill];
[rectanglePath fill];
promień narożnika dla dolnej lewej krawędzi:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(x,y,width,height) byRoundingCorners: UIRectCornerBottomLeft cornerRadii: CGSizeMake(11, 11)];
[rectanglePath closePath];
[UIColor.grayColor setFill];
[rectanglePath fill];
promień narożnika dla prawej dolnej krawędzi:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(x,y,width,height) byRoundingCorners: UIRectCornerBottomRight cornerRadii: CGSizeMake(11, 11)];
[rectanglePath closePath];
[UIColor.grayColor setFill];
[rectanglePath fill];
promień narożnika dla dolnych krawędzi:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(x,y,width,height) byRoundingCorners: UIRectCornerBottomLeft | UIRectCornerBottomRight cornerRadii: CGSizeMake(11, 11)];
[rectanglePath closePath];
[UIColor.grayColor setFill];
[rectanglePath fill];
promień narożnika dla górnych krawędzi:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(x,y,width,height) byRoundingCorners: UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii: CGSizeMake(11, 11)];
[rectanglePath closePath];
[UIColor.grayColor setFill];
[rectanglePath fill];
Jak tworzyć proste kształty za pomocą UIBezierPath
Dla prostego koła:
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(0,0,50,50)];
[UIColor.grayColor setFill];
[ovalPath fill];
Szybki:
let ovalPath = UIBezierPath(ovalInRect: CGRect(x: 0, y: 0, width: 50, height: 50))
UIColor.grayColor().setFill()
ovalPath.fill()
Dla prostego prostokąta:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(0,0,50,50)];
[UIColor.grayColor setFill];
[rectanglePath fill];
Szybki:
let rectanglePath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 50, height: 50))
UIColor.grayColor().setFill()
rectanglePath.fill()
Dla prostej linii:
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint: CGPointMake(x1,y1)];
[bezierPath addLineToPoint: CGPointMake(x2,y2)];
[UIColor.blackColor setStroke];
bezierPath.lineWidth = 1;
[bezierPath stroke];
Szybki:
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPoint(x: x1, y: y1))
bezierPath.addLineToPoint(CGPoint(x: x2, y: y2))
UIColor.blackColor().setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
Dla półkola:
CGRect ovalRect = CGRectMake(x,y,width,height);
UIBezierPath* ovalPath = [UIBezierPath bezierPath];
[ovalPath addArcWithCenter: CGPointMake(0, 0) radius: CGRectGetWidth(ovalRect) / 2 startAngle: 180 * M_PI/180 endAngle: 0 * M_PI/180 clockwise: YES];
[ovalPath addLineToPoint: CGPointMake(0, 0)];
[ovalPath closePath];
CGAffineTransform ovalTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect));
ovalTransform = CGAffineTransformScale(ovalTransform, 1, CGRectGetHeight(ovalRect) / CGRectGetWidth(ovalRect));
[ovalPath applyTransform: ovalTransform];
[UIColor.grayColor setFill];
[ovalPath fill];
Szybki:
let ovalRect = CGRect(x: 0, y: 0, width: 50, height: 50)
let ovalPath = UIBezierPath()
ovalPath.addArcWithCenter(CGPoint.zero, radius: ovalRect.width / 2, startAngle: 180 * CGFloat(M_PI)/180, endAngle: 0 * CGFloat(M_PI)/180, clockwise: true)
ovalPath.addLineToPoint(CGPoint.zero)
ovalPath.closePath()
var ovalTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))
ovalTransform = CGAffineTransformScale(ovalTransform, 1, ovalRect.height / ovalRect.width)
ovalPath.applyTransform(ovalTransform)
UIColor.grayColor().setFill()
ovalPath.fill()
Dla prostego trójkąta:
UIBezierPath* polygonPath = [UIBezierPath bezierPath];
[polygonPath moveToPoint: CGPointMake(x1, y1)];
[polygonPath addLineToPoint: CGPointMake(x2, y2)];
[polygonPath addLineToPoint: CGPointMake(x3, y2)];
[polygonPath closePath];
[UIColor.grayColor setFill];
[polygonPath fill];
Szybki:
let polygonPath = UIBezierPath()
polygonPath.moveToPoint(CGPoint(x: x1, y: y1))
polygonPath.addLineToPoint(CGPoint(x: x2, y: y2))
polygonPath.addLineToPoint(CGPoint(x: x3, y: y3))
polygonPath.closePath()
UIColor.grayColor().setFill()
polygonPath.fill()
UIBezierPath + AutoLayout
Aby zmienić ścieżkę Béziera na podstawie ramki widoku, nadpisz drawRect widoku, który rysujesz ścieżką Béziera:
- (void)drawRect:(CGRect)frame
{
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(CGRectGetMinX(frame), CGRectGetMinY(frame), CGRectGetWidth(frame), CGRectGetHeight(frame))];
[UIColor.grayColor setFill];
[rectanglePath fill];
}
Jak zastosować cienie do UIBezierPath
Rozważ prosty prostokąt rysowany ścieżką Beziera.
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(x,y,width,height)];
[UIColor.grayColor setFill];
[rectanglePath fill];
Podstawowy cień zewnętrzny:
CGContextRef context = UIGraphicsGetCurrentContext();
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: UIColor.blackColor];
[shadow setShadowOffset: CGSizeMake(7.1, 5.1)];
[shadow setShadowBlurRadius: 5];
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(x,y,width,height)];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, shadow.shadowOffset, shadow.shadowBlurRadius, [shadow.shadowColor CGColor]);
[UIColor.grayColor setFill];
[rectanglePath fill];
CGContextRestoreGState(context);
Podstawowy cień wypełnienia wewnętrznego:
CGContextRef context = UIGraphicsGetCurrentContext();
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: UIColor.blackColor];
[shadow setShadowOffset: CGSizeMake(9.1, -7.1)];
[shadow setShadowBlurRadius: 6];
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(x,y,width,height)];
[UIColor.grayColor setFill];
[rectanglePath fill];
CGContextSaveGState(context);
UIRectClip(rectanglePath.bounds);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
CGContextSetAlpha(context, CGColorGetAlpha([shadow.shadowColor CGColor]));
CGContextBeginTransparencyLayer(context, NULL);
{
UIColor* opaqueShadow = [shadow.shadowColor colorWithAlphaComponent: 1];
CGContextSetShadowWithColor(context, shadow.shadowOffset, shadow.shadowBlurRadius, [opaqueShadow CGColor]);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextBeginTransparencyLayer(context, NULL);
[opaqueShadow setFill];
[rectanglePath fill];
CGContextEndTransparencyLayer(context);
}
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
Projektowanie i rysowanie ścieżki Beziera
Ten przykład pokazuje proces projektowania kształtu, który chcesz narysować na widoku. Używany jest określony kształt, ale poznanych pojęć można zastosować do dowolnego kształtu.
Jak narysować ścieżkę Béziera w widoku niestandardowym
Oto główne kroki:
- Zaprojektuj kontur pożądanego kształtu.
- Podziel ścieżkę konturu na segmenty linii, łuków i krzywych.
- Zbuduj tę ścieżkę programowo.
- Narysuj ścieżkę w
drawRect
lub za pomocąCAShapeLayer
.
Zarys kształtu projektu
Możesz zrobić wszystko, ale jako przykład wybrałem poniższy kształt. Może to być klawisz wyskakujący na klawiaturze.
Podziel ścieżkę na segmenty
Spójrz wstecz na swój projekt kształtu i podziel go na prostsze elementy linii (dla linii prostych), łuków (dla okręgów i okrągłych narożników) i krzywych (dla czegokolwiek innego).
Oto jak wyglądałby nasz przykładowy projekt:
- Czarne to odcinki linii
- Jasnoniebieskie to segmenty łukowe
- Czerwone to krzywe
- Pomarańczowe kropki to punkty kontrolne dla krzywych
- Zielone kropki to punkty między segmentami ścieżki
- Kropkowane linie pokazują prostokąt ograniczający
- Ciemnoniebieskie liczby to segmenty w kolejności, w której zostaną dodane programowo
Zbuduj ścieżkę programowo
Dowolnie zaczniemy w lewym dolnym rogu i będziemy pracować zgodnie z ruchem wskazówek zegara. Użyję siatki na obrazie, aby uzyskać wartości xiy dla punktów. Będę tutaj kodować wszystko na sztywno, ale oczywiście nie zrobiłbyś tego w prawdziwym projekcie.
Podstawowym procesem jest:
- Utwórz nową
UIBezierPath
- Wybierz punkt początkowy na ścieżce za pomocą
moveToPoint
- Dodaj segmenty do ścieżki
- linia:
addLineToPoint
- arc:
addArcWithCenter
- krzywa:
addCurveToPoint
- Zamknij ścieżkę za pomocą
closePath
Oto kod do utworzenia ścieżki na powyższym obrazku.
func createBezierPath() -> UIBezierPath {
// create a new path
let path = UIBezierPath()
// starting point for the path (bottom left)
path.moveToPoint(CGPoint(x: 2, y: 26))
// *********************
// ***** Left side *****
// *********************
// segment 1: line
path.addLineToPoint(CGPoint(x: 2, y: 15))
// segment 2: curve
path.addCurveToPoint(CGPoint(x: 0, y: 12), // ending point
controlPoint1: CGPoint(x: 2, y: 14),
controlPoint2: CGPoint(x: 0, y: 14))
// segment 3: line
path.addLineToPoint(CGPoint(x: 0, y: 2))
// *********************
// ****** Top side *****
// *********************
// segment 4: arc
path.addArcWithCenter(CGPoint(x: 2, y: 2), // center point of circle
radius: 2, // this will make it meet our path line
startAngle: CGFloat(M_PI), // π radians = 180 degrees = straight left
endAngle: CGFloat(3*M_PI_2), // 3π/2 radians = 270 degrees = straight up
clockwise: true) // startAngle to endAngle goes in a clockwise direction
// segment 5: line
path.addLineToPoint(CGPoint(x: 8, y: 0))
// segment 6: arc
path.addArcWithCenter(CGPoint(x: 8, y: 2),
radius: 2,
startAngle: CGFloat(3*M_PI_2), // straight up
endAngle: CGFloat(0), // 0 radians = straight right
clockwise: true)
// *********************
// ***** Right side ****
// *********************
// segment 7: line
path.addLineToPoint(CGPoint(x: 10, y: 12))
// segment 8: curve
path.addCurveToPoint(CGPoint(x: 8, y: 15), // ending point
controlPoint1: CGPoint(x: 10, y: 14),
controlPoint2: CGPoint(x: 8, y: 14))
// segment 9: line
path.addLineToPoint(CGPoint(x: 8, y: 26))
// *********************
// **** Bottom side ****
// *********************
// segment 10: line
path.closePath() // draws the final line to close the path
return path
}
Uwaga: Część powyższego kodu można zmniejszyć, dodając linię i łuk w jednym poleceniu (ponieważ łuk ma domyślny punkt początkowy). Zobacz tutaj po więcej szczegółów.
Narysuj ścieżkę
Możemy narysować ścieżkę w warstwie lub w drawRect
.
Metoda 1: Narysuj ścieżkę w warstwie
Nasza klasa niestandardowa wygląda tak. Dodajemy naszą ścieżkę Beziera do nowej CAShapeLayer
po zainicjowaniu widoku.
import UIKit
class MyCustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
// Create a CAShapeLayer
let shapeLayer = CAShapeLayer()
// The Bezier path that we made needs to be converted to
// a CGPath before it can be used on a layer.
shapeLayer.path = createBezierPath().CGPath
// apply other properties related to the path
shapeLayer.strokeColor = UIColor.blueColor().CGColor
shapeLayer.fillColor = UIColor.whiteColor().CGColor
shapeLayer.lineWidth = 1.0
shapeLayer.position = CGPoint(x: 10, y: 10)
// add the new layer to our custom view
self.layer.addSublayer(shapeLayer)
}
func createBezierPath() -> UIBezierPath {
// see previous code for creating the Bezier path
}
}
I tworzenie naszego widoku w kontrolerze widoku w ten sposób
override func viewDidLoad() {
super.viewDidLoad()
// create a new UIView and add it to the view controller
let myView = MyCustomView()
myView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
myView.backgroundColor = UIColor.yellowColor()
view.addSubview(myView)
}
Dostajemy ...
Hmm, to trochę za małe, bo na stałe wpisałem wszystkie liczby. Mogę jednak zwiększyć rozmiar ścieżki w ten sposób:
let path = createBezierPath()
let scale = CGAffineTransformMakeScale(2, 2)
path.applyTransform(scale)
shapeLayer.path = path.CGPath
Metoda 2: Narysuj ścieżkę w drawRect
Korzystanie z drawRect
jest wolniejsze niż rysowanie na warstwie, więc nie jest to zalecana metoda, jeśli jej nie potrzebujesz.
Oto poprawiony kod naszego niestandardowego widoku:
import UIKit
class MyCustomView: UIView {
override func drawRect(rect: CGRect) {
// create path (see previous code)
let path = createBezierPath()
// fill
let fillColor = UIColor.whiteColor()
fillColor.setFill()
// stroke
path.lineWidth = 1.0
let strokeColor = UIColor.blueColor()
strokeColor.setStroke()
// Move the path to a new location
path.applyTransform(CGAffineTransformMakeTranslation(10, 10))
// fill and stroke the path (always do these last)
path.fill()
path.stroke()
}
func createBezierPath() -> UIBezierPath {
// see previous code for creating the Bezier path
}
}
co daje nam ten sam wynik ...
Dalsze badanie
Doskonałe artykuły do zrozumienia ścieżek Beziera.
- Myślenie jak ścieżka Béziera (wszystko, co kiedykolwiek czytałem od tego autora, jest dobre, a inspiracja do powyższego przykładu pochodzi stąd).
- Coding Math: Episode 19 - Bezier Curves (zabawne i dobre ilustracje wizualne)
- Krzywe Beziera (jak są wykorzystywane w aplikacjach graficznych)
- Krzywe Beziera (dobry opis wyprowadzania wzorów matematycznych)
Notatki
- Ten przykład pochodzi pierwotnie z tej odpowiedzi Przepełnienie stosu .
- W swoich rzeczywistych projektach prawdopodobnie nie powinieneś używać liczb zakodowanych na stałe, ale raczej uzyskać rozmiary z granic widoku.
widok kołowy i widok kolumny z UIBezierPath
- (void)drawRect:(CGRect)rect { NSArray *data = @[@30, @15, @5, @17, @3, @10, @20]; // 1. context CGContextRef cxtRef = UIGraphicsGetCurrentContext(); CGPoint center = CGPointMake(150, 150); CGFloat radius = 150; __block CGFloat startAngle = 0; [data enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // 2. create path CGFloat endAngle = obj.floatValue / 100 * M_PI * 2 + startAngle; UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [circlePath addLineToPoint:center]; // 3. add path CGContextAddPath(cxtRef, circlePath.CGPath); // set color [[UIColor colorWithRed:((float)arc4random_uniform(256) / 255.0) green:((float)arc4random_uniform(256) / 255.0) blue:((float)arc4random_uniform(256) / 255.0) alpha:1.0] setFill]; // 4. render CGContextDrawPath(cxtRef, kCGPathFill); // reset angle startAngle = endAngle; }]; }
override func draw(_ rect: CGRect) { // define data to create pie chart let data: [Int] = [30, 15, 5, 17, 3, 10, 20] // 1. find center of draw rect let center: CGPoint = CGPoint(x: rect.midX, y: rect.midY) // 2. calculate radius of pie let radius = min(rect.width, rect.height) / 2.0 var startAngle: CGFloat = 0.0 for value in data { // 3. calculate end angle for slice let endAngle = CGFloat(value) / 100.0 * CGFloat.pi * 2.0 + startAngle // 4. create UIBezierPath for slide let circlePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) // 5. add line to center to close path circlePath.addLine(to: center) // 6. set fill color for current slice UIColor(red: (CGFloat(arc4random_uniform(256)) / 255.0), green: (CGFloat(arc4random_uniform(256)) / 255.0), blue: (CGFloat(arc4random_uniform(256)) / 255.0), alpha: 1.0).setFill() // 7. fill slice path circlePath.fill() // 8. set end angle as start angle for next slice startAngle = endAngle } }
- (void)drawRect:(CGRect)rect { NSArray *data = @[@300, @150.65, @55.3, @507.7, @95.8, @700, @650.65]; // 1. CGContextRef cxtRef = UIGraphicsGetCurrentContext(); NSInteger columnCount = 7; CGFloat width = self.bounds.size.width / (columnCount + columnCount - 1); for (NSInteger i = 0; i < columnCount; i++) { // 2. CGFloat height = [data[i] floatValue] / 1000 * self.bounds.size.height; // floatValue CGFloat x = 0 + width * (2 * i); CGFloat y = self.bounds.size.height - height; UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, width, height)]; CGContextAddPath(cxtRef, rectPath.CGPath); // 3. [[UIColor colorWithRed:((float)arc4random_uniform(256) / 255.0) green:((float)arc4random_uniform(256) / 255.0) blue:((float)arc4random_uniform(256) / 255.0) alpha:1.0] setFill]; CGContextDrawPath(cxtRef, kCGPathFill); } }
override func draw(_ rect: CGRect) { // define data for chart let data: [CGFloat] = [300, 150.65, 55.3, 507.7, 95.8, 700, 650.65] // 1. calculate number of columns let columnCount = data.count // 2. calculate column width let columnWidth = rect.width / CGFloat(columnCount + columnCount - 1) for (columnIndex, value) in data.enumerated() { // 3. calculate column height let columnHeight = value / 1000.0 * rect.height // 4. calculate column origin let columnOrigin = CGPoint(x: (columnWidth * 2.0 * CGFloat(columnIndex)), y: (rect.height - columnHeight)) // 5. create path for column let columnPath = UIBezierPath(rect: CGRect(origin: columnOrigin, size: CGSize(width: columnWidth, height: columnHeight))) // 6. set fill color for current column UIColor(red: (CGFloat(arc4random_uniform(256)) / 255.0), green: (CGFloat(arc4random_uniform(256)) / 255.0), blue: (CGFloat(arc4random_uniform(256)) / 255.0), alpha: 1.0).setFill() // 7. fill column path columnPath.fill() } }