Круговой индикатор прогресса iPhone. CGContextRef. Ничья по требованию

Я хочу нарисовать изображение, которое фактически будет круговым индикатором прогресса на кнопке UIB. Поскольку изображение должно отражать ход выполнения задачи, я не думаю, что мне следует обрабатывать код рисования в методе drawrect вида.

У меня есть поток, который выполняет некоторые задачи. После каждой задачи он вызывает метод в главном потоке. Вызываемый метод должен обновить изображение на кнопке.

В методе обновления кнопки я создаю CGContextRef с помощью CGBitmapContextCreate. Затем я использую рамку кнопки, чтобы создать CGRect. Затем я пытаюсь использовать созданный мной контекст. Наконец я установил NeedsDisplay и убрал.

Но ничего из этого не находится внутри метода drawrect.

Я хотел бы знать, использовал ли кто-нибудь CGContext для рисования в / в представлении по требованию в представлении во время отображения представления.

Я хотел бы получить некоторые идеи относительно подхода к этому.

Вот инкапсулированная версия того, что я делаю сейчас: CGContextRef xContext = nil; CGColorSpaceRef xColorSpace; CGRect xRect; void* xBitmapData; int iBMPByteCount; int iBMPBytesPerRow; float fBMPWidth = 20.0f; float fBMPHeight = 20.0f; ФПИ плавания = 3,14159; float fRadius = 25,0f;

iBMPBytesPerRow = (fBMPWidth * 4);
iBMPByteCount = (iBMPBytesPerRow * fBMPHeight);
xColorSpace = CGColorSpaceCreateDeviceRGB();
xBitmapData = malloc(iBMPByteCount);
xContext = CGBitmapContextCreate(xBitmapData, fBMPWidth, fBMPHeight, 8, iBMPBytesPerRow, xColorSpace, kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(xColorSpace);
UIGraphicsPushContext(xContext);
xRect = CGRectMake(30.0f, 400.0f, 50.0f, 50.0f);

float fWidth = xRect.size.width;
float fHeight = xRect.size.height;

CGContextClearRect(xContext, xRect);
CGContextSetRGBStrokeColor(xContext, 0.5f, 0.6f, 0.7f, 1.0f);
CGContextSetLineWidth(xContext, 1.0f);

float fArcBegin = 45.0f * fPI / 180.0f;
float fArcEnd = 90.0f * fPI / 180.0f;

CGContextSetFillColor(xContext, CGColorGetComponents( [[UIColor greenColor] CGColor]));
CGContextMoveToPoint(xContext, fWidth, fHeight);
CGContextAddArc(xContext, fWidth, fHeight, fRadius, fArcBegin, fArcEnd, 0);

CGContextClosePath(xContext); 
CGContextFillPath(xContext); 

UIGraphicsPopContext;
CGContextRelease(xContext);

[self.view setNeedsDisplay];

// [self.view setNeedsDisplayInRect: xRect];

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

2 ответа

Решение

Это действительно беспокоило меня, поэтому после того, как я столкнулся с серией глупых, но необходимых вопросов, касающихся проекта, для которого я хочу эту функциональность, я поиграл с этим. Конечный результат заключается в том, что теперь я могу произвольно нарисовать дугу, представляющую ход выполнения определенной фоновой задачи для кнопки.

Цель состояла в том, чтобы нарисовать что-то вроде маленького индикатора в правом нижнем углу окон XCode во время очистки или компиляции проекта.

Я создал функцию, которая будет рисовать и заполнять дугу и возвращать ее как UIImage.

Рабочий поток вызывает метод (PerformSelectorOnMainThread) с текущими значениями и идентификатором кнопки. В вызываемом методе я вызываю функцию изображения дуги с заполненным процентом и тому подобное.

пример вызова:

oImg = [self ArcImageCreate:100.0f fWidth:100.0f 
    fPercentFilled: 0.45f fAngleStart: 0.0f xFillColor:[UIColor blueColor]];

Затем установите фоновое изображение кнопки:

[oBtn setBackgroundImage: oImg forState: UIControlStateNormal];

Вот функция:
Он не закончен, но работает достаточно хорошо, чтобы проиллюстрировать, как я это делаю.



/**
ArcImageCreate
@ingroup    UngroupedFunctions
@brief  Create a filled or unfilled solid arc and return it as a UIImage.

        Allows for dynamic / arbitrary update of an object that allows a UIImage to be drawn on it. \
    This can be used for some sort of pie chart or progress indicator by Image Flipping.

@param  fHeight     The height of the created UIImage.
@param  fWidth      The width of the created UIImage.
@param  fPercentFilled  A percentage of the circle to be filled by the arc. 0.0 to 1.0.
@param  AngleStart  The angle where the arc should start. 0 to 360. Clock Reference.
@param  xFillColor  The color of the filled area.

@return Pointer to a UIImage.

@todo [] Validate object creation at each step.

@todo [] Convert PercentFilled (0.0 to 1.0) to appropriate radian(?) (-3.15 to +3.15)

@todo [] Background Image Support. Allow for the arc to be drawn on top of an image \
    and the whole thing returned.

@todo [] Background Image Reduction. Background images will have to be resized to fit the specfied size. \
    Do not want to return a 65KB object because the background is 60K or whatever.

@todo [] UIColor RGBA Components. Determine a decent method of extracting RGVA values \
    from a UIColor*. Check out arstechnica.com/apple/guides/2009/02/iphone-development-accessing-uicolor-components.ars \
    for an idea.

*/
- (UIImage*) ArcImageCreate: (float)fHeight fWidth:(float)fWidth fPercentFilled:(float)fPercentFilled fAngleStart:(float)fAngleStart xFillColor:(UIColor*)xFillColor
{
    UIImage* fnRez = nil;
    float fArcBegin = 0.0f;
    float fArcEnd = 0.0f;
    float fArcPercent = 0.0f;
    UIColor* xArcColor = nil;
    float fArcImageWidth = 0.0f;
    float fArcImageHeight = 0.0f;
    CGRect xArcImageRect;

    CGContextRef xContext = nil;
    CGColorSpaceRef xColorSpace;
    void* xBitmapData;
    int iBMPByteCount;
    int iBMPBytesPerRow;
    float fPI = 3.14159;
    float fRadius = 25.0f;

// @todo Force default of 100x100 px if out of bounds. \
//  Check max image dimensions for iPhone. \
//  If negative, flip values *if* values are 'reasonable'. \
//  Determine minimum useable pixel dimensions. 10x10 px is too small. Or is it?
    fArcImageWidth = fHeight;
    fArcImageHeight = fWidth;

    // Get the passed target percentage and clip it between 0.0 and 1.0
    fArcPercent = (fPercentFilled  1.0f) ? 1.0f : fPercentFilled;
    fArcPercent = (fArcPercent > 1.0f) ? 1.0f : fArcPercent;

    // Get the passed start angle and clip it between 0.0 to 360.0
    fArcBegin = (fAngleStart  359.0f) ? 0.0f : fAngleStart;
    fArcBegin = (fArcBegin > 359.0f) ? 0.0f : fArcBegin;

    fArcBegin = (fArcBegin * fPI) / 180.0f;
    fArcEnd = ((360.0f * fArcPercent) * fPI) / 180.0f;

    // 
    if (xFillColor == nil) {
        // random color
    } else {
        xArcColor = xFillColor;
    }

    // Calculate memory required for image.
    iBMPBytesPerRow = (fArcImageWidth * 4);
    iBMPByteCount = (iBMPBytesPerRow * fArcImageHeight);
    xBitmapData = malloc(iBMPByteCount);

    // Create a color space. Behavior changes at OSXv10.4. Do not rely on it for consistency across devices.
    xColorSpace = CGColorSpaceCreateDeviceRGB();

    // Set the system to draw. Behavior changes at OSXv10.3.
    //  Both of these work. Not sure which is better.
//  xContext = CGBitmapContextCreate(xBitmapData, fArcImageWidth, fArcImageHeight, 8, iBMPBytesPerRow, xColorSpace, kCGImageAlphaPremultipliedFirst);
    xContext = CGBitmapContextCreate(NULL, fArcImageWidth, fArcImageHeight, 8, iBMPBytesPerRow, xColorSpace, kCGImageAlphaPremultipliedFirst);

    // Let the system know the colorspace reference is no longer required.
    CGColorSpaceRelease(xColorSpace);

    // Set the created context as the current context.
//  UIGraphicsPushContext(xContext);

    // Define the image's box.
    xArcImageRect = CGRectMake(0.0f, 0.0f, fArcImageWidth, fArcImageHeight);

    // Clear the image's box.
//  CGContextClearRect(xContext, xRect);

    // Draw the ArcImage's background image.
//  CGContextDrawImage(xContext, xArcImageRect, [oBackgroundImage CGImage]);

    // Set Us Up The Transparent Drawing Area.
    CGContextBeginTransparencyLayer(xContext, nil);

    // Set the fill and stroke colors
// @todo [] Determine why SetFilColor does not. Use alternative method.
//  CGContextSetFillColor(xContext, CGColorGetComponents([xArcColor CGColor]));
//  CGContextSetFillColorWithColor(xContext, CGColorGetComponents([xArcColor CGColor]));
// Test Colors
    CGContextSetRGBFillColor(xContext, 0.3f, 0.4f, 0.5f, 1.0f);
    CGContextSetRGBStrokeColor(xContext, 0.5f, 0.6f, 0.7f, 1.0f);
    CGContextSetLineWidth(xContext, 1.0f);

// Something like this to reverse drawing?
//  CGContextTranslateCTM(xContext, TranslateXValue, TranslateYValue);
//  CGContextScaleCTM(xContext, -1.0f, 1.0f); or CGContextScaleCTM(xContext, 1.0f, -1.0f);

    // Test Vals
//  fArcBegin = 45.0f * fPI / 180.0f;   // 0.785397
//  fArcEnd = 90.0f * fPI / 180.0f; // 1.570795

    // Move to the start point and draw the arc.
    CGContextMoveToPoint(xContext, fArcImageWidth/2.0f, fArcImageHeight/2.0f);
    CGContextAddArc(xContext, fArcImageWidth/2.0f, fArcImageHeight/2.0f, fRadius, fArcBegin, fArcEnd, 0);


    // Ask the OS to close the arc (current point to starting point). 
    CGContextClosePath(xContext);

    // Fill 'er up. Implicit path closure.
    CGContextFillPath(xContext);
//  CGContextEOFillPath(context);

    // Close Transparency drawing area.
    CGContextEndTransparencyLayer(xContext);

    // Create an ImageReference and create a UIImage from it.
    CGImageRef xCGImageTemp = CGBitmapContextCreateImage(xContext);
    CGContextRelease(xContext);
    fnRez = [UIImage imageWithCGImage: xCGImageTemp];
    CGImageRelease(xCGImageTemp);

//  UIGraphicsPopContext;

    return fnRez;
}


Альтернативный подход:

Вы можете создать серию изображений, представляющих обновления прогресса, а затем заменить свойство currentImage UIButton на setImage:forState: метод на каждом шаге процесса. Это не требует рисования в существующем виде, и этот подход хорошо сработал для меня, чтобы показать простую "анимацию" изображений (кнопок или других).

Будет ли этот подход работать для вас? Если нет, то почему нет?

Барт

Другие вопросы по тегам