Ich versuche, mit einem CAShapeLayer einen gestrichenen Kreis zu zeichnen und eine Kreisbahn darauf zu setzen. Diese Methode ist jedoch beim Rendern auf dem Bildschirm durchweg weniger genau als die Verwendung von borderRadius oder das direkte Zeichnen des Pfads in einem CGContextRef.
Hier sind die Ergebnisse aller drei Methoden:
Beachten Sie, dass der dritte Teil schlecht gerendert ist, insbesondere innerhalb des Strichs oben und unten.
Ich habe die contentsScale
Eigenschaft auf gesetzt [UIScreen mainScreen].scale
.
Hier ist mein Zeichnungscode für diese drei Kreise. Was fehlt, damit der CAShapeLayer reibungslos zeichnet?
@interface BCViewController ()
@end
@interface BCDrawingView : UIView
@end
@implementation BCDrawingView
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
self.backgroundColor = nil;
self.opaque = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[[UIColor whiteColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), NULL);
[[UIColor redColor] setStroke];
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 1);
[[UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, 4, 4)] stroke];
}
@end
@interface BCShapeView : UIView
@end
@implementation BCShapeView
+ (Class)layerClass
{
return [CAShapeLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
self.backgroundColor = nil;
CAShapeLayer *layer = (id)self.layer;
layer.lineWidth = 1;
layer.fillColor = NULL;
layer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, 4, 4)].CGPath;
layer.strokeColor = [UIColor redColor].CGColor;
layer.contentsScale = [UIScreen mainScreen].scale;
layer.shouldRasterize = NO;
}
return self;
}
@end
@implementation BCViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *borderView = [[UIView alloc] initWithFrame:CGRectMake(24, 104, 36, 36)];
borderView.layer.borderColor = [UIColor redColor].CGColor;
borderView.layer.borderWidth = 1;
borderView.layer.cornerRadius = 18;
[self.view addSubview:borderView];
BCDrawingView *drawingView = [[BCDrawingView alloc] initWithFrame:CGRectMake(20, 40, 44, 44)];
[self.view addSubview:drawingView];
BCShapeView *shapeView = [[BCShapeView alloc] initWithFrame:CGRectMake(20, 160, 44, 44)];
[self.view addSubview:shapeView];
UILabel *borderLabel = [UILabel new];
borderLabel.text = @"CALayer borderRadius";
[borderLabel sizeToFit];
borderLabel.center = CGPointMake(borderView.center.x + 26 + borderLabel.bounds.size.width/2.0, borderView.center.y);
[self.view addSubview:borderLabel];
UILabel *drawingLabel = [UILabel new];
drawingLabel.text = @"drawRect: UIBezierPath";
[drawingLabel sizeToFit];
drawingLabel.center = CGPointMake(drawingView.center.x + 26 + drawingLabel.bounds.size.width/2.0, drawingView.center.y);
[self.view addSubview:drawingLabel];
UILabel *shapeLabel = [UILabel new];
shapeLabel.text = @"CAShapeLayer UIBezierPath";
[shapeLabel sizeToFit];
shapeLabel.center = CGPointMake(shapeView.center.x + 26 + shapeLabel.bounds.size.width/2.0, shapeView.center.y);
[self.view addSubview:shapeLabel];
}
@end
BEARBEITEN: Für diejenigen, die den Unterschied nicht sehen können, habe ich Kreise übereinander gezeichnet und vergrößert:
Hier habe ich einen roten Kreis mit drawRect:
gezeichnet und dann einen identischen Kreis mit drawRect:
wieder grün darüber gezeichnet . Beachten Sie das begrenzte Ausbluten von Rot. Beide Kreise sind "glatt" (und identisch mit der cornerRadius
Implementierung):
In diesem zweiten Beispiel sehen Sie das Problem. Ich habe einmal mit einem CAShapeLayer
in rot gezeichnet und wieder oben mit einer drawRect:
Implementierung des gleichen Pfades, aber in grün. Beachten Sie, dass Sie viel mehr Inkonsistenzen mit mehr Blutungen aus dem roten Kreis darunter sehen können. Es wird eindeutig anders (und schlimmer) gezeichnet.
quelle
[UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, 4, 4)]
Dieself.bound
Haltepunkte (keine Pixel), sodass Sie technisch gesehen eine Kurve ohne Netzhautgröße erstellen. Wenn Sie diese Größe mit dem[UIScreen mainScreen].scale
Wert multiplizieren, ist Ihr Oval auf Retina-Bildschirmen perfekt glatt.Antworten:
Wer hätte gedacht, dass es so viele Möglichkeiten gibt, einen Kreis zu zeichnen?
Original
Hier ist dein Original
CAShapeLayer
und ein Unterschied zurdrawRect
Version. Ich habe mit Retina Display einen Screenshot von meinem iPad Mini gemacht, ihn dann in Photoshop massiert und zu 200% gesprengt. Wie Sie deutlich sehen können,CAShapeLayer
weist die Version sichtbare Unterschiede auf, insbesondere am linken und rechten Rand (dunkelste Pixel im Diff).Im Bildschirmmaßstab rastern
Lassen Sie uns im Bildschirmmaßstab rastern, der sich
2.0
auf Retina-Geräten befinden sollte. Fügen Sie diesen Code hinzu:layer.rasterizationScale = [UIScreen mainScreen].scale; layer.shouldRasterize = YES;
Beachten Sie, dass
rasterizationScale
standardmäßig1.0
auch auf Retina - Geräte, die für die Verschwommenheit von StandardkontenshouldRasterize
.Der Kreis ist jetzt etwas glatter, aber die schlechten Stellen (dunkelste Pixel im Diff) haben sich an den oberen und unteren Rand verschoben. Nicht nennenswert besser als kein Rastering!
Bei 2x Bildschirmskala rastern
layer.rasterizationScale = 2.0 * [UIScreen mainScreen].scale; layer.shouldRasterize = YES;
Dadurch wird der Pfad im 2-fachen Bildschirmmaßstab oder bis zu
4.0
Retina-Geräten gerastert .Der Kreis ist jetzt sichtbar glatter, die Unterschiede sind viel heller und gleichmäßig verteilt.
Ich habe dies auch in Instruments: Core Animation ausgeführt und keine wesentlichen Unterschiede bei den Debug-Optionen für Core Animation festgestellt. Es kann jedoch langsamer sein, da es nicht nur eine Offscreen-Bitmap auf den Bildschirm verkleinert. Möglicherweise müssen Sie
shouldRasterize = NO
während der Animation auch vorübergehend Einstellungen vornehmen.Was funktioniert nicht?
Stellen Sie
shouldRasterize = YES
sich. Auf Netzhautgeräten sieht dies da unscharf ausrasterizationScale != screenScale
.Stellen Sie ein
contentScale = screenScale
. DaCAShapeLayer
nicht berücksichtigt wirdcontents
, ob es sich um ein Rastering handelt oder nicht, wirkt sich dies nicht auf die Wiedergabe aus.Dank an Jay Hollywood von Humaan , einen scharfen Grafikdesigner, der mich zuerst darauf hingewiesen hat.
quelle
CAShapeLayer
es für schnelles Zeichnen und Animieren optimiert ist. Es hat jedoch noch so andere Vorteile, dass ich es lieber benutzedrawRect
: Auflösungsunabhängigkeit (warten Sie nur, bis Apple ein 4x Retina-Display produziert), weniger Speicher, ohne Verzerrung transformierbar, flexible Rasterung usw.CAShapeLayer
.CAShapeLayer
Wiedergabe. Sie können dies manchmal in Nicht-Retina-Anzeigen sehen: Die Wiedergabe sieht aus wie ein Polygon. Die Lösung wäre dann, die Abflachung selbst vorzunehmen. Siehe z. B. ps.missouri.edu/ps2/support/tutorialfolder/flatness/index.htmlAh, ich bin vor einiger Zeit auf dasselbe Problem gestoßen (es war immer noch iOS 5, dann iirc), und ich habe den folgenden Kommentar in den Code geschrieben:
/* ShapeLayer ---------- Fixed equivalent of CAShapeLayer. CAShapeLayer is meant for animatable bezierpath and also doesn't cache properly for retina display. ShapeLayer converts its path into a pixelimage, honoring any displayscaling required for retina. */
Ein gefüllter Kreis unter einer Kreisform würde seine Füllfarbe ausbluten lassen. Abhängig von den Farben wäre dies sehr auffällig. Und während der Benutzerinteraktion würde die Form noch schlechter gerendert, was mich zu dem Schluss führen würde, dass die Formschicht unabhängig vom Ebenen-Skalierungsfaktor immer mit einem Skalierungsfaktor von 1,0 gerendert wird, da sie für Animationszwecke gedacht ist.
Das heißt, Sie verwenden einen CAShapeLayer nur, wenn Sie bestimmte animierbare Änderungen an der Form des Bezierpfads benötigen, nicht an anderen Eigenschaften, die über die üblichen Ebeneneigenschaften animierbar sind.
Ich habe mich schließlich entschlossen, einen einfachen ShapeLayer zu schreiben, der sein eigenes Ergebnis zwischenspeichert, aber Sie könnten versuchen, den displayLayer: oder den drawLayer: inContext: zu implementieren.
Etwas wie:
- (void)displayLayer:(CALayer *)layer { UIImage *image = nil; CGContextRef context = UIImageContextBegin(layer.bounds.size, NO, 0.0); if (context != nil) { [layer renderInContext:context]; image = UIImageContextEnd(); } layer.contents = image; }
Ich habe das nicht versucht, wäre aber interessant, das Ergebnis zu erfahren ...
quelle
Ich weiß, dass dies eine ältere Frage ist, aber für diejenigen, die versuchen, mit der drawRect-Methode zu arbeiten und immer noch Probleme haben, war eine kleine Änderung, die mir immens geholfen hat, die richtige Methode zum Abrufen des UIGraphicsContext zu verwenden. Standard verwenden:
let newSize = CGSize(width: 50, height: 50) UIGraphicsBeginImageContext(newSize) let context = UIGraphicsGetCurrentContext()
würde zu verschwommenen Kreisen führen, egal welchem Vorschlag ich aus den anderen Antworten folgte. Für mich war es schließlich klar, dass die Standardmethode zum Abrufen eines ImageContext die Skalierung auf Nicht-Retina setzt. Um einen ImageContext für eine Retina-Anzeige zu erhalten, müssen Sie folgende Methode verwenden:
let newSize = CGSize(width: 50, height: 50) UIGraphicsBeginImageContextWithOptions(newSize, false, 0) let context = UIGraphicsGetCurrentContext()
Von dort aus funktionierten die normalen Zeichenmethoden einwandfrei. Wenn Sie die letzte Option auf
0
einstellen, wird das System angewiesen, den Skalierungsfaktor des Hauptbildschirms des Geräts zu verwenden. Mit der mittleren Optionfalse
können Sie dem Grafikkontext mitteilen, ob Sie ein undurchsichtiges Bild zeichnen (true
dh das Bild ist undurchsichtig) oder einen Alphakanal für Transparentfolien benötigen. Hier sind die entsprechenden Apple-Dokumente für mehr Kontext: https://developer.apple.com/reference/uikit/1623912-uigraphicsbeginimagecontextwitho?language=objcquelle
Ich denke, es
CAShapeLayer
wird durch eine performantere Art des Renderns seiner Formen unterstützt und es werden einige Verknüpfungen verwendet. Auf jedenCAShapeLayer
Fall kann der Hauptfaden etwas langsam sein. Sofern Sie nicht zwischen verschiedenen Pfaden animieren müssen, würde ich vorschlagen, asynchron zuUIImage
einem Hintergrundthread zu rendern .quelle
Verwenden Sie diese Methode, um UIBezierPath zu zeichnen
/*********draw circle where double tapped******/ - (UIBezierPath *)makeCircleAtLocation:(CGPoint)location radius:(CGFloat)radius { self.circleCenter = location; self.circleRadius = radius; UIBezierPath *path = [UIBezierPath bezierPath]; [path addArcWithCenter:self.circleCenter radius:self.circleRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; return path; }
Und so zeichnen
CAShapeLayer *shapeLayer = [CAShapeLayer layer]; shapeLayer.path = [[self makeCircleAtLocation:location radius:50.0] CGPath]; shapeLayer.strokeColor = [[UIColor whiteColor] CGColor]; shapeLayer.fillColor = nil; shapeLayer.lineWidth = 3.0; // Add CAShapeLayer to our view [gesture.view.layer addSublayer:shapeLayer];
quelle