スライスごとにビューを作成し、UIPinchGestureRecognizer
. 方法は次のとおりです。
まず、UIView
1 つのスライスを描画するサブクラスが必要です。pointInside:withEvent:
また、スライスの外側に着地するタッチを無視するようにオーバーライドする必要があります(タッチがビューの長方形の境界内にある場合でも)。
というクラスを作りますSliceView
。CAShapeLayer
スライス描画を行うために使用します:
@interface SliceView : UIView
@property (nonatomic) CGFloat padding;
@property (nonatomic) CGFloat startRadians;
@property (nonatomic) CGFloat endRadians;
@property (nonatomic, strong) UIColor *fillColor;
@end
@implementation SliceView
@synthesize padding = _padding;
@synthesize startRadians = _startRadians;
@synthesize endRadians = _endRadians;
@synthesize fillColor = _fillColor;
メソッドをオーバーライドしてCAShapeLayer
、プレーンの代わりに aを使用するように指示します。ビューのレイヤーを として返す便利なメソッドも追加します。CALayer
layerClass
CAShapeLayer
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (CAShapeLayer *)shapeLayer {
return (CAShapeLayer *)self.layer;
}
ビューはサイズが変更されるたびlayoutSubviews
にメッセージを受け取るため、 でスライスのパスを計算します。layoutSubviews
パイ全体をカバーするように各スライス ビューをレイアウトしますが、パイのくさびのみを描画します。各スライスのフレームは画面全体をカバーします (パイがフルスクリーンの場合)。つまり、スライス ビューは、円弧の中心が境界の中心にあることを認識しています。しかし、隣接するスライスの間にパディングを入れるために、少し三角法を使用します。
レイヤーのアンカーポイントも調整します。これは、レイヤーをスケーリングまたは回転しても移動しないレイヤー内のポイントです。中心に最も近いスライスの角にアンカー ポイントを配置します。
- (void)layoutSubviews {
CAShapeLayer *layer = self.shapeLayer;
CGRect bounds = self.bounds;
CGFloat radius = MIN(bounds.size.width, bounds.size.height) / 2 - 2 * _padding;
CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGFloat sine = sinf((_startRadians + _endRadians) * 0.5f);
CGFloat cosine = cosf((_startRadians + _endRadians) * 0.5f);
center.x += _padding * cosine;
center.y += _padding * sine;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:center];
[path addArcWithCenter:center radius:radius startAngle:_startRadians endAngle:_endRadians clockwise:YES];
[path closePath];
layer.path = path.CGPath;
// Move my anchor point to the corner of my path so scaling will leave the corner in the same place.
CGPoint cornerInSuperview = [self convertPoint:center toView:self.superview];
layer.anchorPoint = CGPointMake(center.x / bounds.size.width, center.y / bounds.size.height);
self.center = cornerInSuperview;
}
スライスに関連するビューのプロパティのいずれかが変更された場合、スライスの輪郭を描くパスを再計算する必要があります。スライスの塗りつぶしの色が変更されたら、その変更をレイヤーに渡す必要があります。したがって、プロパティ セッターをオーバーライドします。
- (void)setPadding:(CGFloat)padding {
_padding = padding;
[self setNeedsLayout];
}
- (void)setStartRadians:(CGFloat)startRadians {
_startRadians = startRadians;
[self setNeedsLayout];
}
- (void)setEndRadians:(CGFloat)endRadians {
_endRadians = endRadians;
[self setNeedsLayout];
}
- (void)setFillColor:(UIColor *)color {
_fillColor = color;
self.shapeLayer.fillColor = color.CGColor;
}
pointInside:withEvent:
最後に、タッチが実際にスライスのパス内にある場合にのみ、ヒット テストがスライス ビューにタッチを割り当てるようにオーバーライドします。すべてのスライス ビューに画面全体をカバーするフレームがあるため、これは重要です。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return CGPathContainsPoint(self.shapeLayer.path, NULL, point, NO);
}
@end
便利なSliceView
クラスができたので、それを使用して、ズーム可能なスライスで円グラフを描画できます。iPhone の画面ではスライスに 2 本の指を入れるのは難しいため、ユーザーがスライスをタップして選択し、任意の場所をピンチして選択したスライスをスケーリングできるようにします。(このインターフェースにより、シミュレーターでのテストも可能になります。)
@implementation ViewController {
__weak SliceView *_selectedSlice;
}
選択されていないスライスを赤で、選択されたスライスを青で描画します。
+ (UIColor *)unselectedSliceFillColor {
return UIColor.redColor;
}
+ (UIColor *)selectedSliceFillColor {
return UIColor.blueColor;
}
ユーザーがスライスをタップすると、前の選択と新しい選択の色を変更し、新しい選択を記録する必要があります。
- (IBAction)sliceWasTapped:(UITapGestureRecognizer *)tapper {
_selectedSlice.fillColor = self.class.unselectedSliceFillColor;
_selectedSlice = (SliceView *)tapper.view;
_selectedSlice.fillColor = self.class.selectedSliceFillColor;
}
ユーザーがピンチすると、選択されたスライスがあれば、その変形を調整します。
- (IBAction)pinched:(UIPinchGestureRecognizer *)pincher {
if (!_selectedSlice)
return;
CGFloat scale = pincher.scale;
pincher.scale = 1;
_selectedSlice.transform = CGAffineTransformScale(_selectedSlice.transform, scale, scale);
}
最後に、実際にスライス ビューとジェスチャ レコグナイザーを作成する必要があります。スライスごとに 1 つのタップ認識エンジンを作成し、バックグラウンド ビューに接続された 1 つの「グローバル」ピンチ認識エンジンを作成します。
- (void)viewDidLoad {
static int const SliceCount = 12;
CGRect bounds = self.view.bounds;
for (int i = 0; i < SliceCount; ++i) {
SliceView *slice = [[SliceView alloc] initWithFrame:bounds];
slice.startRadians = 2 * M_PI * i / SliceCount;
slice.endRadians = 2 * M_PI * (i + 1) / SliceCount;
slice.padding = 4;
slice.fillColor = self.class.unselectedSliceFillColor;
slice.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:slice];
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sliceWasTapped:)];
[slice addGestureRecognizer:tapper];
}
UIPinchGestureRecognizer *pincher = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinched:)];
[self.view addGestureRecognizer:pincher];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
@end
そして、これがどのように見えるかです:
ここからテスト プロジェクトをダウンロードできます: http://dl.dropbox.com/u/26919672/pie.zip
アップデート
スケールを制限することについて尋ねるあなたのコメントに応えて、いくつかのプロパティを に追加することをお勧めしますSliceView
。
@property (nonatomic) CGFloat minScale;
@property (nonatomic) CGFloat maxScale;
@property (nonatomic) CGFloat scale;
重要:initWithFrame:
およびで 3 つのプロパティをすべて 1 に初期化する必要がありますinitWithCoder:
。
次に、scale
セッターを実装して実際に制限を適用し、スケールを設定します。
- (void)setScale:(CGFloat)scale {
_scale = MAX(minScale, MIN(scale, maxScale));
self.transform = CGAffineTransformMakeScale(_scale, _scale);
}
では、ビューのプロパティを直接設定する代わりに、ビューのプロパティpinched:
を更新します。scale
transform
- (IBAction)pinched:(UIPinchGestureRecognizer *)pincher {
if (!_selectedSlice)
return;
CGFloat scale = pincher.scale;
pincher.scale = 1;
_selectedSlice.scale = _selectedSlice.scale * scale;
}