Круговой индикатор прогресса 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: метод на каждом шаге процесса. Это не требует рисования в существующем виде, и этот подход хорошо сработал для меня, чтобы показать простую "анимацию" изображений (кнопок или других).
Будет ли этот подход работать для вас? Если нет, то почему нет?
Барт