2015-01-28 2 views
4

Наш дизайнер попросил меня воссоздать это: an orange arrow-bar rotating around a circleИспользование Core Graphics от ObjectiveC, как вам нарисовать стрелку вокруг круга?

Im подклассов UIView, и я переопределить команду DrawRect так:

[super drawRect:frame]; 

CGFloat x = self.frame.origin.x; 
CGFloat y = self.frame.origin.y; 
CGFloat w = self.frame.size.width; 
CGFloat h = self.frame.size.height; 
CGFloat lineWidth = lineWidthRequested; 
CGPoint centerPoint = CGPointMake(w/2, h/2); 
CGFloat radius = radiusRequested; 
CGFloat startAngle = 3 * M_PI/2; 
CGFloat endAngle = startAngle + percentage * 2 * M_PI; 

CGMutablePathRef arc = CGPathCreateMutable(); 
CGPathAddArc(arc, NULL, 
      centerPoint.x, centerPoint.y, 
      radius, 
      startAngle, 
      endAngle, 
      NO); 
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(arc, NULL, 
           lineWidth, 
           kCGLineCapButt, 
           kCGLineJoinMiter, // the default 
           10); // 10 is default miter limit 

CGContextRef c = UIGraphicsGetCurrentContext(); 
CGContextAddPath(c, strokedArc); 
CGContextSetFillColorWithColor(c, [UIColor colorWithRed:239/255.0 green:101/255.0 blue:47/255.0 alpha:1.0].CGColor); 
CGContextDrawPath(c, kCGPathFill); 

То, что я закончил с это: an orange bar around the circle

Должен по-прежнему рисовать стрелку. Будет легко, не так ли?

После борьбы вспомнить мои триг, я нашел вращение точек вокруг центра на этой странице: Rotating a point around an origin in VB

Но когда я попытался перевод объективных C нарисовать стрелку, я получаю очень странные результаты. Вот код, далее вниз в DrawRect:

CGFloat triangle[3][2] = {{centerPoint.x + 10, h - (centerPoint.y + radius)}, 
    {centerPoint.x, h - (centerPoint.y + radius + lineWidth/2)}, 
    {centerPoint.x, h - (centerPoint.y + radius - lineWidth/2)}}; 

for (int idx=0;idx < 3; idx++) { 
    // translate to origin 
    triangle[idx][0] -= centerPoint.x; 
    triangle[idx][1] -= centerPoint.y; 
} 

CGFloat angDistance = endAngle - startAngle; 
CGFloat ct = cos(angDistance); 
CGFloat st = sin(angDistance); 

for (int idx=0;idx < 3; idx++) { 
    // rotate 
    triangle[idx][0] = ct * triangle[idx][0] - st * triangle[idx][1]; 
    triangle[idx][1] = -st * triangle[idx][0] + ct * triangle[idx][1]; 
} 

for (int idx=0;idx < 3; idx++) { 
    // translate back to position 
    triangle[idx][0] += centerPoint.x; 
    triangle[idx][1] += centerPoint.y; 
} 

NSLog(@"Rotating through %g, %06.1f,%06.1f , ct - %g, st - %g",angDistance, triangle[0][0],triangle[0][1],ct, st); 

// XXX todo draw the filled triangle at end. 
// draw a red triangle, the point of the arrow 
CGContextSetFillColorWithColor(c, [[UIColor greenColor] CGColor]); 
CGContextMoveToPoint(c, triangle[0][0], triangle[0][1]); 
CGContextAddLineToPoint(c, triangle[1][0], triangle[1][1]); 
CGContextAddLineToPoint(c, triangle[2][0], triangle[2][1]); 
CGContextFillPath(c); 

Я ожидал, что я делаю эти точки, а затем перевести их происхождения, вращать, а затем перевести их обратно, я бы смеялся.

Однако, это не то, что происходит ... при увеличении процента от 0 до 2pi стрелка направляется в неопределенно треугольный маршрут. Когда angDistance равно нулю или pi, стрелка находится в нужном месте. Когда я направляюсь в направлении pi/2 или 3pi/2, стрелка направляется к нижним углам замкнутого прямоугольника.

Должно быть, я делаю что-то вопиющее глупо, но я не могу, чтобы жизнь меня видела.

Любые идеи?

Спасибо,

Кен

+0

[Это Q & A] (http://stackoverflow.com/q/13562137/77567) может представлять интерес для Вас. –

ответ

4

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

Таким образом, этот путь может состоять из дуги снаружи стрелки, двух линий для головы стрелки, дуги назад для внутренней части стрелки, а затем закрыть путь. Это может выглядеть так:

- (void)drawRect:(CGRect)rect { 
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    CGContextSetFillColorWithColor(context, self.arrowColor.CGColor); 

    CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0); 

    CGContextMoveToPoint(context, center.x + cosf(self.startAngle) * (self.radius + self.lineWidth/2.0), 
            center.y + sinf(self.startAngle) * (self.radius + self.lineWidth/2.0)); 

    CGContextAddArc(context, center.x, center.y, self.radius + self.lineWidth/2.0, self.startAngle, self.endAngle, !self.clockwise); 

    CGFloat theta = asinf(self.lineWidth/self.radius/2.0) * (self.clockwise ? 1.0 : -1.0); 
    CGFloat pointDistance = self.radius/cosf(theta); 

    CGContextAddLineToPoint(context, center.x + cosf(self.endAngle + theta) * pointDistance, 
            center.y + sinf(self.endAngle + theta) * pointDistance); 

    CGContextAddLineToPoint(context, center.x + cosf(self.endAngle) * (self.radius - self.lineWidth/2.0), 
            center.y + sinf(self.endAngle) * (self.radius - self.lineWidth/2.0)); 

    CGContextAddArc(context, center.x, center.y, self.radius - self.lineWidth/2.0, self.endAngle, self.startAngle, self.clockwise); 

    CGContextClosePath(context); 

    CGContextDrawPath(context, kCGPathFill); 
} 

Единственный трюк здесь - подходящий момент для конца стрелки. Я улучшил выбор, чтобы лучше регулировать толстые стрелки, но вы должны быть свободны использовать все, что, по вашему мнению, лучше всего подходит для вашего приложения.

Таким образом, следующий код:

self.arrowView.radius = 100; 
self.arrowView.arrowColor = [UIColor blueColor]; 
self.arrowView.lineWidth = 40; 
self.arrowView.startAngle = -M_PI_2; 
self.arrowView.endAngle = M_PI; 
self.arrowView.clockwise = TRUE; 

даст следующее (который я анимировать с CADisplayLink):

arrow

При этом используется начальный угол, равный нулю, как смысл положение «3 часа», но вы можете, очевидно, настроить это, как вы считаете нужным. Но, надеюсь, это иллюстрирует один подход к проблеме.

Кстати, хотя я ответил на вопрос о том, как это сделать с CoreGraphics, я бы не стал предлагать это делать. Например, в https://github.com/robertmryan/CircularArrowDemo я не реализую drawRect, но вместо этого обновляю CAShapeLayer. Делая это, я не только избегаю drawRect неэффективности, но теоретически можно также изменить способ использования этого CAShapeLayer (например, используйте его как mask для некоторого UIView, показывая еще одну интересную градацию цвета (или другое изображение) за ней).

+0

Вау, спасибо! Это абсолютно идеально. Если вы когда-нибудь попадете в регби, Англия, я буду рад купить вам пиво! –

+0

Свободное пиво всегда приятно. Возможно, SO должен добавить функцию отправки бесплатных пивных денег людям с правильным ответом. –

+0

Я использовал ваше решение для рисования круговой стрелки другого типа. Просто сделал несколько настроек для углов и радиуса при использовании CGContextAddLineToPoint. –

0

Вот еще одно решение (не очень масштабируемое). Это решение предполагает, что это похоже на логотип, где угол/процент кругового рисунка не изменится.

- (void)drawRect:(CGRect)rect { 
    UIBezierPath *circleOutline = [UIBezierPath bezierPath]; 

    [self.circleColor setStroke]; 
    [circleOutline setLineWidth:self.bounds.size.width*0.15]; 
    [circleOutline addArcWithCenter:CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2) radius:self.bounds.size.width/2-circleOutline.lineWidth/2 startAngle:3*M_PI/2 endAngle:3*M_PI/4 clockwise:YES]; 
    [circleOutline stroke]; 

    [self addArrowView:circleOutline]; 
} 

- (void)addArrowView:(UIBezierPath *)path { 
    for (int x = 0; x < self.bounds.size.width/2; x++) { 
     for (int y = self.bounds.size.height/2; y < self.bounds.size.height; y++) { 
      if ([path containsPoint:CGPointMake(x, y)]) { 

       // Pythagorean Theorem - We want the diagonal length of the square to be lineWidth, so we need to calculate what size 
       // to make each side of the square to make the diagonal equal to lineWidth 
       double sideLength = sqrt((path.lineWidth*path.lineWidth)/2); 

       UIView *arrowView = [[UIView alloc] initWithFrame:CGRectMake(x-sideLength/2, y-sideLength/2, sideLength, sideLength)]; 
       arrowView.backgroundColor = self.circleColor; 
       [self addSubview:arrowView]; 

       return; 
      } 
     } 
    } 
} 

уступит:

enter image description here