CAShapeLayerとUIBezierPathで滑らかな円を描くには?
質問
私はCAShapeLayerを使用し、その上に円形のパスを設定することによって、ストロークした円を描画しようとしています。 しかし、この方法は、borderRadiusを使用したり、CGContextRefで直接パスを描画するよりも、画面にレンダリングされたときに一貫して精度が低いです。
以下は、3つの方法すべての結果です。
3番目はレンダリングが不十分で、特に上部と下部のストロークの内側にあることに注意してください。
I
ある
を設定します。
contentsScale
プロパティに
[UIScreen mainScreen].scale
.
以下は、これら3つの円に対する私の描画コードです。CAShapeLayerをスムーズに描画させるためには何が足りないのでしょうか?
@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
編集:違いがわからない人のために、円を重ねて描き、拡大してみました。
ここでは、赤い丸に
drawRect:
で同じ円を描き、さらに
drawRect:
と書かれた同じ円を描き、その上に再び緑色で描きました。赤のにじみが少ないことに注意してください。これらの円は両方とも、quot;smooth"(そして
cornerRadius
の実装と同じです)。
この2つ目の例では、問題があることがわかります。を使って一度描画しています。
CAShapeLayer
を赤で、その上にもう一度
drawRect:
の実装を緑色で表示しています。下の赤い円からのにじみで、より多くの矛盾が見られることに注意してください。これは明らかに異なる (そして悪い) 方法で描かれています。
どのように解決するのですか?
円を描くのにこんなにたくさんの方法があるなんて。
TL;DR。 もし
CAShapeLayer
を使って、なおかつ滑らかな円を得たいのであればshouldRasterize
とrasterizationScale
を注意深く見てください。
オリジナル
以下は、あなたのオリジナルの
CAShapeLayer
との差分です。
drawRect
のバージョンです。iPad Mini with Retina Displayでスクリーンショットを作成し、Photoshopでマスキングして、200%にブローアップしています。明らかにわかるように
CAShapeLayer
バージョンでは、特に左右の端 (差分では最も暗いピクセル) に目に見える違いがあります。
画面スケールでラスタライズする
画面スケールでラスタライズしてみましょう。
2.0
であるべきです。このコードを追加します。
layer.rasterizationScale = [UIScreen mainScreen].scale;
layer.shouldRasterize = YES;
なお
rasterizationScale
はデフォルトで
1.0
であるため、デフォルトの
shouldRasterize
.
円は少し滑らかになりましたが、悪い部分 (差分の最も暗いピクセル) が上下の端に移動しています。ラスタライズを行わない場合よりも、はるかに良いとは言えません!
2 倍の画面スケールでラスタライズする
layer.rasterizationScale = 2.0 * [UIScreen mainScreen].scale;
layer.shouldRasterize = YES;
これは、パスを2倍の画面スケールでラスタライズします。
4.0
まで拡大します。
円は目に見えて滑らかになり、差分はより軽く、均等に広がるようになりました。
また、Instruments.Core Animation でこれを実行しました。Core Animation Debug Options では、大きな違いは見られませんでした。しかし、オフスクリーンのビットマップを画面にブライトするだけでなく、ダウンスケールしているため、より遅くなる可能性があります。また、一時的に
shouldRasterize = NO
を一時的に設定する必要があるかもしれません。
動作しないもの
-
設定
shouldRasterize = YES
を単体で設定します。 Retinaデバイスでは、これがぼやけて見えるのはrasterizationScale != screenScale
. -
設定
contentScale = screenScale
. 以降CAShapeLayer
には描画されません。contents
に描画しないので、ラスタライズしていてもしていなくても、描画には影響しません。
Jay Hollywood のクレジット フマーン のジェイ・ハリウッド (Jay Hollywood) が指摘してくれました。
関連
-
XCode のコンパイル例外を解決する clang: error: linker command failed with exit code 1
-
[解決済み] カスタムオブジェクトを含むNSMutableArrayをソートするにはどうすればよいですか?
-
[解決済み] UITableViewの下にある余分なセパレータをなくす
-
[解決済み] UIViewに角丸をつける
-
[解決済み] iOSで画面の幅と高さを取得する方法は?
-
[解決済み] UIDevice uniqueIdentifierは非推奨 - どうしたらいいの?
-
[解決済み] アプリのプレビュー用にiOSシミュレータのビデオをキャプチャー
-
[解決済み] Swiftのプロトコルでオプションのメソッドを定義するには?
-
[解決済み] Swiftで配列に要素を追加する
-
[解決済み] UIViewの下に影を描くには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
XCode のコンパイル例外を解決する clang: error: linker command failed with exit code 1
-
[解決済み] 制約条件の変更をアニメーションで表現するには?
-
[解決済み] 文字列の長さを取得する
-
[解決済み] 奇妙な不要なXcodeログを隠す
-
[解決済み] Xcode 12、iOS Simulator用にビルドしても、iOS用にビルドされたオブジェクトファイルでは、アーキテクチャ「arm64」用にリンクされます。
-
[解決済み] UITextViewのサイズをコンテンツに合わせるには?
-
[解決済み] UITextViewのマージン/パディングをなくす方法
-
[解決済み] UILabelで複数行のテキストを表示する
-
[解決済み] UITableView - トップにスクロールする
-
[解決済み] iPhoneでナビゲーションバーを1ページ目だけ非表示にする