Масштабирование с помощью штрихов - PullRequest
0 голосов
/ 20 февраля 2012

У меня есть круговая диаграмма , которая сделана с использованием UIBezierPath.Теперь мне нужно, чтобы эти отдельные пути (кусочки пирога) были масштабируемыми.Я считаю, что вам нужно представление, чтобы можно было использовать масштабирование по пинчу, поэтому я думаю, что touchSMoved: это путь (если нет обходного пути).

Любой совет или помощь приветствуются!

Обновлен / Код прогресса

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];                             
}

Ответы [ 2 ]

3 голосов
/ 20 февраля 2012

Я думаю, вам будет проще, если вы создадите представление для каждого среза и используете UIPinchGestureRecognizer. Вот как.

Во-первых, нам нужен подкласс UIView, который рисует один срез. Также следует переопределить 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 вместо простого 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, поэтому мы позволим пользователю коснуться среза, чтобы выбрать его, и ущипнуть в любом месте для масштабирования выбранного среза. (Этот интерфейс также делает его тестируемым в симуляторе.)

@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);
}

Наконец, нам нужно создать виды слайсов и распознаватели жестов. Мы создаем один распознаватель касаний для каждого среза и один «глобальный» распознаватель пинчей, прикрепленный к фоновому виду.

- (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 demo screen shot

Вы можете скачать мой тестовый проект здесь: http://dl.dropbox.com/u/26919672/pie.zip

UPDATE

В ответ на ваш комментарий с вопросом об ограничении масштаба я бы предложил добавить еще несколько свойств к SliceView:

@property (nonatomic) CGFloat minScale;
@property (nonatomic) CGFloat maxScale;
@property (nonatomic) CGFloat scale;

Важно: Вам нужно будет инициализировать все три свойства: 1 в initWithFrame: и 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;
}
1 голос
/ 20 февраля 2012

Во-первых, вероятно, лучше хранить фрагменты в массиве.

Во-вторых, также лучше определить класс MySliceClass (который может наследоваться от UIBezierPath). Этот класс имеет свойства, которые определяют срез: startingAngle, endAngle.

Теперь не только вы улучшили свой код, но и стало легче изменять размер фрагментов.

Вам необходимо добавить параметр radius в класс 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

Я не тестировал этот код, поэтому могут быть некоторые ошибки. Но вы можете начать с этого, я надеюсь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...