在 iOS 端自研股票分时图时,Core Graphics 是最常用也最灵活的底层绘图方案。它允许我们在 UIView 的 drawRect 方法中拿到当前的 CGContextRef,通过一系列 C 函数调用完成线条、文字与填充的绘制。相比集成成熟的图表框架,直接使用 Core Graphics 能把绘制逻辑压缩到最少依赖中,同时也要求开发者自己处理坐标映射、刻度计算和网格线样式。本文围绕时间轴标签、价格轴刻度以及网格线渲染三个核心部分,详细说明实现思路与代码细节。

时间轴标签的排布与绘制
股票分时图的时间轴通常覆盖一个交易日的连续交易时段,例如 A 股的分时从 09:30 到 11:30,再从 13:00 到 15:00,中间有午休缺口。我们在绘制时间轴标签时,首先要确定可用宽度,然后根据需要展示的标签数量做均分。常见做法是固定显示 5 到 7 个时间点,如 09:30、10:30、11:30、13:00、14:00、15:00,避免标签过于密集导致文字重叠。
具体绘制时,可以用 CGContextSetTextMatrix 与 CGContextSetTextDrawingMode 配合 Core Text 或者更简单的 NSString 的 drawInRect:withAttributes: 方法。由于 Core Graphics 的上下文坐标系原点在左上角,而 Core Text 默认排版是从左下角起算,若混用需要注意翻转。下面示例在 drawRect 中计算每一个时间标签的 x 坐标并绘制:
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
NSArray *timeLabels = @[@"09:30", @"10:30", @"11:30", @"13:00", @"14:00", @"15:00"];
CGFloat plotWidth = rect.size.width - self.leftMargin - self.rightMargin;
NSDictionary *attr = @{NSFontAttributeName:[UIFont systemFontOfSize:11],
NSForegroundColorAttributeName:[UIColor grayColor]};
for (NSInteger i = 0; i < timeLabels.count; i++) {
CGFloat x = self.leftMargin + plotWidth * (i * 1.0 / (timeLabels.count - 1));
NSString *text = timeLabels[i];
CGSize size = [text sizeWithAttributes:attr];
CGFloat drawX = x - size.width / 2.0;
if (drawX < self.leftMargin) drawX = self.leftMargin;
[text drawAtPoint:CGPointMake(drawX, rect.size.height - 20) withAttributes:attr];
}
}
上面的代码把时间标签在水平方向按索引均分,并对最左侧标签做了越界修正。实际项目中,如果交易时段不连续,应该把午休缺口从宽度中剔除,或单独用两段线性映射来计算 x 坐标,否则时间轴会显得中间被拉平。标签字体大小也建议随视图宽度动态调整,在窄屏上减少到 10pt 能保证可读性。
价格轴刻度与坐标映射
价格轴的难点在于把业务价格映射到屏幕纵坐标。分时图一般以昨日收盘价为中轴,向上是上涨区间,向下是下跌区间。我们需要先确定最大涨跌幅度,例如普通股票为 ±10%,然后把这一区间映射到图表绘制区域的高度上。假设图表绘制区高度为 h,中轴 y 坐标为 midY,则价格 p 对应的 y 可用公式 y = midY - (p - preClose) / (preClose * 0.1) * (h / 2) 计算。
刻度线通常画在左侧,并标注对应价格。为了避免刻度过于密集,可以每 2% 或 5% 画一条横向参考线,并在左边缘写价格文本。下面示例展示如何根据昨收价与幅度生成价格刻度并绘制:
- (void)drawPriceAxisInContext:(CGContextRef)ctx rect:(CGRect)rect {
CGFloat preClose = 10.0;
CGFloat top = rect.origin.y + self.topMargin;
CGFloat bottom = rect.origin.y + rect.size.height - self.bottomMargin;
CGFloat midY = (top + bottom) / 2.0;
CGFloat halfH = (bottom - top) / 2.0;
NSDictionary *attr = @{NSFontAttributeName:[UIFont systemFontOfSize:11],
NSForegroundColorAttributeName:[UIColor darkGrayColor]};
for (NSInteger percent = -10; percent <= 10; percent += 5) {
CGFloat price = preClose * (1 + percent / 100.0);
CGFloat y = midY - (price - preClose) / (preClose * 0.1) * halfH;
NSString *text = [NSString stringWithFormat:@"%.2f", price];
[text drawAtPoint:CGPointMake(2, y - 6) withAttributes:attr];
}
}
这种映射方式把价格轴与涨跌幅度绑定,能直观反映当前价格相对昨收的位置。如果是个股停牌或科创板 ±20% 限制,只需修改分母的 0.1 为对应幅度即可。要注意的是,绘制价格文本时应避免与网格线重叠,可让文本右对齐到固定左边距内,保证数字清晰。
网格线渲染与性能注意事项
网格线用于辅助读取坐标,通常包含横向的价格参考线和纵向的时间参考线。在 Core Graphics 中,我们可以用 CGContextSetLineDash 画出虚线,避免实线过于抢眼。绘制网格应在 drawRect 中统一进行,并且只画必要数量,减少不必要的路径操作。下面给出横向网格线示例:
- (void)drawGridInContext:(CGContextRef)ctx rect:(CGRect)rect {
CGFloat preClose = 10.0;
CGFloat top = rect.origin.y + self.topMargin;
CGFloat bottom = rect.origin.y + rect.size.height - self.bottomMargin;
CGFloat midY = (top + bottom) / 2.0;
CGFloat halfH = (bottom - top) / 2.0;
CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
CGContextSetLineWidth(ctx, 0.5);
CGFloat dash[] = {4, 4};
CGContextSetLineDash(ctx, 0, dash, 2);
for (NSInteger percent = -10; percent <= 10; percent += 5) {
CGFloat price = preClose * (1 + percent / 100.0);
CGFloat y = midY - (price - preClose) / (preClose * 0.1) * halfH;
CGContextMoveToPoint(ctx, self.leftMargin, y);
CGContextAddLineToPoint(ctx, rect.size.width - self.rightMargin, y);
CGContextStrokePath(ctx);
}
CGContextSetLineDash(ctx, 0, NULL, 0);
}
在性能方面,drawRect 会在每次视图标记为需要显示时被调用,因此刻度计算和标签数组不应在绘制时反复创建临时对象。建议把时间标签数组、价格区间等缓存在属性中,只在数据更新时重算。若分时图需要随手势拖动,可以用 setNeedsDisplayInRect: 局部重绘,而不是整个视图重画,从而降低 CPU 占用。
另一个常见误区是开发者把分时折线数据和坐标轴绘制放在同一个循环里,导致坐标逻辑和曲线逻辑耦合,难以维护。更好的做法是分层:先画网格,再画坐标轴标签,最后画分时折线,每层使用独立方法,既清晰又方便单独优化。通过这些细节处理,Core Graphics 完全能胜任流畅的本地股票分时图渲染。
Core_GraphicsiOS绘图股票分时图修改时间:2026-08-18 13:10:31