UIView drawRect是生涩的

时间:2011-10-07 18:00:58

标签: ios ipad drawrect

UIView drawRect是不稳定的,即不能在屏幕上平滑滚动。我尝试过以200毫秒到500毫秒的不同间隔执行'setNeedsDisplay',似乎没什么 改善外观。这就是当剧情在屏幕上移动时他们停下来开始。 有什么方法可以改善这一点,使显示更加流畅吗?

/*==============================================================================*/
/* 'drawRect' -  override function.                                         */
/*                                                                              */
/* This function is responsible for plotting cardiac waveforms (ECG) and also   */
/* erases any prior ECG plot values that may be currently visible on the        */
/* screen.                                      */
/*                                          */
/* It does this indirectly via 'EcgThread' or 'ECGErase' which send the     */
/* 'setNeedsDisplay' message which triggers the 'drawRect' callback function.   */
/* Once an ECG is started 'EcgThread' controls the frequency of 'drawRect'      */
/* being by simple timer. (See 'EcgThread' for current timer value.)     */     
/*                                          */
/*==============================================================================*/
/* Here's how the entire ECG process works:                     */
/*                                          */
/* 1) User starts ECG which causes a 'send_ecg' command to be sent to       */
/*    'CommThread'.                                 */
/*                                          */
/* 2) 'CommThread' the sends the 'send_ecg' command to the Pacemaker and then   */
/*     sends itself the 'read_ecg' command.*/                       
/*                                          */
/* 3) The Pacemaker then starts acquiring 64 cardiac A/D samples (10-bits) per  */ 
/*    second and sends them back to 'CommThread'.                   */
/*                                          */
/* 4) As samples are received by 'CommThread' (via 'read_ecg') on a streaming   */
/*    basis they are stored in 'ECGY' array (vertical plots) in reverse order   */  
/*    i.e., from top to bottom (currently room for 128 samples).            */
/*                                          */
/* 5) 'EcgThread' runs continuously on a timer basis sending 'setNeedsDisplay'  */
/*    message which causes 'iOS' to perform callbacks to 'drawRect' who is      */
/*    responsible for drawing the cardiac (plots) waveforms from left to right  */
/*    across (horizontally) the screen.                     */
/*                                          */
/* 6) 'drawRect' processes 'ECGY' bottom to top (opposite of 'CommThread') and  */
/*    each draw loop (plotting new values) takes about 13-millseconds. This is  */
/*    necessary because not doing so results in drawing upside down plots.      */
/*                                          */
/* 7) User stops ECG and 'TimerProc' sends a series of 'send_ecgstop' commands  */
/*    to 'CommThread' who in turn sends the 'send_ecgstop' commands to the PM.  */
/*    The reason why we send multiple 'send_ecgstop' is due to the streaming    */
/*    nature of the sending ECG samples and getting the PM to 'listen' to us.   */
/*    Usually stopping will go smoothly. Occasionally it may be necessary to    */
/*    move the Wand away, wait a few seconds and place Wand back over patient's */
/*    chest (causing an interrupt) before normal operation returns.         */
/*                                          */
/*==============================================================================*/
- (void) drawRect : (CGRect) rect                                               // Callback routine
{
int i, ii, x, xx, y;                                                            // Local array indices

fEcgDraw = YES;                                                                 // Show 'drawRect' is running
[super drawRect : rect];                                                        // Call standard handler
CGContextRef context = UIGraphicsGetCurrentContext ();                          // Get graphics context
CGContextSetAllowsAntialiasing (context, NO);                                   // Turn off anti-alliaing
CGContextSetLineWidth (context, 1);                                             // Set width of 'pen'
/*==============================================================================*/
/* 'HH' is used as a height bias in order to position the waveform plots in     */
/* middle of the view (screen).                                                 */  
/*==============================================================================*/
HH = 424;                                                                       // Force height bias for now
if (fEcgErase == YES)                                                           // Show we erase the view?
{
    CGContextSetStrokeColorWithColor (context,                                  // Set color of 'pen'
                                      [UIColor blackColor].CGColor);            // Black (to erase)
/*==============================================================================*/
/* Erase the last screen.                                                       */
/*==============================================================================*/
    for (i = 0, x = 0; i < 127; i++)                                            // Iterate for all array elements
    {      
        CGContextMoveToPoint (context,                                          // Update current position to specified point
                              ECGX[x],                                          // Starting X-coordinate
                              (HH - ECGS[x]));                                  // Starting Y-coordinate (with height bias)
        CGContextAddLineToPoint (context, ECGX[(x + 1)],                        // Draw line from current position
                                 (HH - ECGS[((x + 1) % 127)]));                 // Ending Y-coordinate (with height bias)
        x++;                                                                // Step to next array element
    }    // end - for (i = 0; i < 127; i++)
    CGContextClosePath  (context);                                             // Close current path
    CGContextStrokePath (context);                                            // Stroke current path (paint the path)
    fEcgErase = NO;                                                            // Reset erase flag
}    // end - if (fEcgErase == YES)
else if (fECGLOOP)                                                            // Did request come from 'EcgThread'?
/*==============================================================================*/
/* Draw ECG cardiac waveforms on view.                                          */
/*==============================================================================*/
{
    xx = 1;                                                                    // Counts markers
    x  = 0;                                                                    // Reset horizontal axis
    y  = YY;                                                                // Use saved startimg ECGY[] index
    ii = 0;                                                                    // Initialize marker count
    #define GRIDSIZE 12                                                        // Grid width in pixels
    int width  = rect.size.width;                                            // Get the view width
    int height = rect.size.height;                                            // Get the view height
/*==============================================================================*/
/* First draw a grid pattern to draw ECG waveforms into.                        */
/*==============================================================================*/
    CGContextSetStrokeColorWithColor (context,                                 // Set color of 'pen'
                                      [UIColor lightGrayColor].CGColor);    // Use 'light gray' for grid pattern
    for (i = 0; i <= width; i = i+GRIDSIZE)                                 // First the vertical lines
    {  
        CGContextMoveToPoint (context, i, 0);                                // Update current position to specified point
        CGContextAddLineToPoint (context, i, height);                         // Draw line from current position
    }     // end - for (i = 0; i <= width; i = i+GRIDSIZE)
    for (i = 0 ; i <= height; i = i+GRIDSIZE)                                 // Then the horizontal lines
    {
        CGContextMoveToPoint (context, 0, i);                                // Update current position to specified point
        CGContextAddLineToPoint (context, width, i);                        // Draw line from current position
    }    // end - for (i = 0 ; i <= height; i = i+GRIDSIZE)
    CGContextClosePath  (context);                                             // Close current path
    CGContextStrokePath (context);                                            // Stroke current path (paint the path)
/*==============================================================================*/
/* Now draw (plot) cardiac waveforms using using pre-stored ECG sample values.  */
/*==============================================================================*/
    for (i = 0; i < 127; i++)                                                // Iterate for number ECGY[] entries
    {
/*==============================================================================*/
/* Erase the prior ECG A/D plot value.                                            */
/*==============================================================================*/
#if 0    // NOT NEEDED CUZ WE SELECTED CLEAR CONTEXT IN EcgViewController.xib              
        CGContextSetStrokeColorWithColor (context,                             // Set color of 'pen'
                                          [UIColor blackColor].CGColor);    // Black to erase old plot
        CGContextMoveToPoint (context,                                        // Update current position to specified point
                              ECGX[x],                                        // Starting X-coordinate of prior position
                              (HH - ECGS[x]));                                // Starting Y-corrdinate with height bias
        CGContextAddLineToPoint (context,                                     // Draw line from current position
                                 ECGX[(x + 1)],                                // Ending X-coordinate
                                 (HH - ECGS[((x + 1))]));                    // Ending Y-coordinate using saved Y-axis (with height bias)
        CGContextClosePath  (context);                                         // Close current path
        CGContextStrokePath (context);                                        // Stroke current path (paint the path)
#endif    // NOT NEEDED CUZ WE SELECTED CLEAR CONTEXT IN EcgViewController.xib              
/*==============================================================================*/
/* Plot the next ECG A/D plot value.                                            */
/*==============================================================================*/
        CGContextSetStrokeColorWithColor (context,                             // Set color of 'pen'
                                          [UIColor greenColor].CGColor);    // White to draw new plot
        CGContextMoveToPoint (context,                                        // Update current position to specified point
                              ECGX[x],                                        // Starting X-coordinate of new position
                              (HH - ECGY[y]));                                // Starting Y-coordinate with height bias
        CGContextAddLineToPoint (context,                                    // Draw line & prevent overrun
                                 ECGX[(x + 1)],                                // Ending X-coordinate
                                 (HH - ECGY[((y + 1) % 127)]));                // Ending Y-axis (with height bias)
        CGContextClosePath  (context);                                         // Close current path
        CGContextStrokePath (context);                                        // Stroke current path (paint the path)
        ECGS[x] = ECGY[y];                                                    // Save last plot value for erase
        x++;                                                                // Next ECGX[] (y-axis) plot value index
/*==============================================================================*/
/* Below as we increment 'y' it will eventually roll to zero and when we get    */
/* to the end of the above 'for' loop 'y' will have its starting value.         */              
/*==============================================================================*/
        y = ((y + 1) % 127);                                                // Next ECGY[] (y-axis) plot value & prevent overrun
        ulPlots++;                                                            // Count number of plots
    }    // end - for (i = 0; i < 127; i++)
    y = ((y + 16) % 127);                                                // Next starting y-axis 'ECGY' index
    YY = y;                                                                    // Save it for next iteration
    EcgCount = 0;                                                            // Reset skip count (inc. by 'CommThread'
}    // end - if (fEcgErase == YES)
//    UIGraphicsPopContext();   
fEcgDraw = NO;                                                                // Show 'drawRect' not running
//    [NSThread sleepForTimeInterval : 0.1];                                        // Delay a little

}    // end - 'drawRect'
/*===============================END OF FUNCTION================================*/

2 个答案:

答案 0 :(得分:1)

请记住,绘图只发生在主线程上,这意味着当drawRect:正在运行时,整个UI都会阻塞。如果此绘图代码很慢,那么在绘图进行时您的应用程序将无响应。根据您在此方法中执行的代码大小和单个绘图操作的数量,这似乎就是这里发生的事情。

drawRect:的名称来源于您应该将绘图限制在rect所描述的范围内。如果整个图表每次都发生变化,或者只是添加了一点点新数据,那么您的问题并不是100%清楚。如果有一种方法可以重构代码,那么每次都不需要重绘整个代码,这几乎肯定会消除你的急动问题。

例如,您还可以避免在每次刷新时绘制网格线,方法是将它们放在包含绘图的视图后面的单独视图中。网格线(我猜)不需要经常重绘,所以如果你让你的情节视图透明,UIKit会为你合成它们,你可以避免绘图操作。可能节省一点,但任何事情都有帮助。这也意味着您可以通过填写[UIColor clearColor]来删除您的观点。用背景颜色绘制旧图是一种非常昂贵的操作,但矩形填充是便宜的。它的代码更少,运行速度更快。

如果这还不够,您还可以将绘图放入单独的UIImage屏幕外,然后只需用此图像替换视图内容即可。这样可以提供更好的性能,因为您可以在单独的线程中进行图像绘制(这是一个昂贵的部分),因此绘图操作不会阻止主应用程序线程,这将消除急动。

答案 1 :(得分:0)

我有一个类似的问题,我想根据触摸手势通过特定数量的弧度旋转对象。轮换是紧张的。我通过使用CADisplayLink将更新与运行循环同步来解决问题:

@property (nonatomic, strong) CADisplayLink *displayLink;

 self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(rotate)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

每次运行循环更新UI时,都会调用我的'rotate'方法。旋转平稳,我没有注意到任何表演命中。

这是一次仿射变换,这不是一项非常昂贵的操作。我想你可以将setNeedsDisplay放在一个由CADisplayLink对象调用的方法中,但这可能是一个昂贵的操作。不过,也许值得一试。