0

UIBezierPathを使用して作成された円グラフがあります。これらの個々のパス(パイピース)をスケーラブルにする必要があります。ピンチスケーリングを使用できるようにするにはビューが必要だと思うので、touchesMoved:が進むべき道だと思います(回避策がない限り)。

アドバイスや助けをいただければ幸いです。

更新/進行コード

MySliceClass.m

+ (UIBezierPath *)sliceRadius:(float)radius andStartingAngle:(float)startingAngle andFinishingAngle:(float)finishingAngle
{
  static UIBezierPath *path = nil;
  path = [UIBezierPath bezierPath];
  CGPoint center = {300,300};
  [path moveToPoint:center];
  [path addArcWithCenter:center radius:radius startAngle:radians(startingAngle) endAngle:radians(finishingAngle) clockwise:YES];
  [path closePath];
  path.lineWidth = 1;

  [[UIColor redColor] setFill];
  [path fill];

  return path;
}

MySliceView.m

- (void)drawRect:(CGRect)rect 
{
  NSArray *arrayOfSlices = [NSArray arrayWithObjects:
                            slice01 = [WordplaySlice sliceRadius:200 andStartingAngle:0.5 andFinishingAngle:29.5],
                            slice02 = [WordplaySlice sliceRadius:200 andStartingAngle:30.5 andFinishingAngle:59.5],
                            slice03 = [WordplaySlice sliceRadius:200 andStartingAngle:60.5 andFinishingAngle:89.5],
                            slice04 = [WordplaySlice sliceRadius:200 andStartingAngle:90.5 andFinishingAngle:119.5],
                            slice05 = [WordplaySlice sliceRadius:200 andStartingAngle:120.5 andFinishingAngle:149.5],
                            slice06 = [WordplaySlice sliceRadius:200 andStartingAngle:150.5 andFinishingAngle:179.5],
                            slice07 = [WordplaySlice sliceRadius:200 andStartingAngle:180.5 andFinishingAngle:209.5],
                            slice08 = [WordplaySlice sliceRadius:200 andStartingAngle:210.5 andFinishingAngle:239.5],
                            slice09 = [WordplaySlice sliceRadius:200 andStartingAngle:240.5 andFinishingAngle:269.5],
                            slice10 = [WordplaySlice sliceRadius:200 andStartingAngle:270.5 andFinishingAngle:299.5],
                            slice11 = [WordplaySlice sliceRadius:200 andStartingAngle:300.5 andFinishingAngle:329.5],
                            slice12 = [WordplaySlice sliceRadius:200 andStartingAngle:330.5 andFinishingAngle:359.5], nil];                             
}
4

2 に答える 2

3

スライスごとにビューを作成し、UIPinchGestureRecognizer. 方法は次のとおりです。

まず、UIView1 つのスライスを描画するサブクラスが必要です。pointInside:withEvent:また、スライスの外側に着地するタッチを無視するようにオーバーライドする必要があります(タッチがビューの長方形の境界内にある場合でも)。

というクラスを作りますSliceViewCAShapeLayerスライス描画を行うために使用します:

@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を使用するように指示します。ビューのレイヤーを として返す便利なメソッドも追加します。CALayerlayerClassCAShapeLayer

+ (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

そして、これがどのように見えるかです:

SliceView デモのスクリーンショット

ここからテスト プロジェクトをダウンロードできます: 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:を更新します。scaletransform

- (IBAction)pinched:(UIPinchGestureRecognizer *)pincher {
    if (!_selectedSlice)
        return;
    CGFloat scale = pincher.scale;
    pincher.scale = 1;
    _selectedSlice.scale = _selectedSlice.scale * scale;
}
于 2012-02-20T05:51:26.480 に答える
1

まず、スライスを配列に格納することをお勧めします。

MySliceClass第 2 に、 (UIBezierPath から継承する可能性のある)クラスを定義することもお勧めします。このクラスには、スライスを定義するプロパティがあります: startingAngleendAngle.

これで、コードが改善されただけでなく、スライスのサイズ変更がより簡単になりました。

radiusclassにパラメーターを追加する必要がありMySliceClass、スライスに触れるたびに半径を変更し[self setNeedsDisplay]、メソッドdrawRectが呼び出されるように呼び出します。

最後に、初期化をビューの初期化メソッドに移動する必要もあります。これは、ビューを描画するたびに新しいスライスを作成する方法だからです。

編集これが実装例です

@implementation Slice // Subclass of NSObject

@synthesize radius, startAngle, endAngle, center;

- (void)draw
{
    UIBezierPath *path = nil;
    path = [UIBezierPath bezierPath];
    [path moveToPoint:center];
    [path addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
    [path closePath];
    path.lineWidth = 1;

    [[UIColor redColor] setFill];
    [path fill];
}

@end

@implementation SliceView

@synthesize slices;

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        int numSlices = 12; // you can change this
        slices = [NSMutableArray array];
        for (int i = 0; i < numSlices; i++) {
            Slice *slice = [[Slice alloc] init];
            slice.center = self.center;
            slice.radius = 100;
            slice.startAngle = (2*M_PI / numSlices) * i;
            slice.endAngle = (2*M_PI / numSlices) * (i+0.9);
        }
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    [[UIColor redColor] setFill];
    for (Slice *slice in slices) {
    [slice draw];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    CGFloat d = sqrtf(powf(point.x-self.center.x, 2) + powf(point.y-self.center.y, 2));

    int index = atan2f(point.x-self.center.x, point.y-self.center.y) * self.slices.count / (2*M_PI);
    Slice *slice = [slices objectAtIndex:index];
    slice.radius = d;
}

@end

このコードはテストしていないため、エラーがある可能性があります。しかし、あなたはこれから始めることができます。

于 2012-02-20T00:17:37.047 に答える