You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

592 lines
26 KiB

  1. // ChartViewController.m
  2. // HC
  3. //
  4. // Created by huilinLi on 2025/11/27.
  5. //
  6. #import "ChartViewController.h"
  7. #import "StockKLineModel.h"
  8. @interface ChartViewController ()
  9. // 上方卡片容器
  10. @property (nonatomic, strong) UIView *cardContainer;
  11. // k线选择项容器
  12. @property (nonatomic, strong) UIView *kSelectContainer;
  13. // K线图表容器
  14. @property (nonatomic, strong) UIView *kLineContainer;
  15. // K线图表滚动视图
  16. @property (nonatomic, strong) UIScrollView *kLineScrollView;
  17. // K线数据
  18. @property (nonatomic, strong) NSArray *kLineData;
  19. // 价格刻度标签
  20. @property (nonatomic, strong) NSArray *priceLabels;
  21. // 日期刻度标签
  22. @property (nonatomic, strong) NSArray *dateLabels;
  23. // 默认展示的K线数量
  24. @property (nonatomic, assign) NSInteger visibleKLineCount;
  25. @end
  26. @implementation ChartViewController
  27. - (void)viewDidLoad {
  28. [super viewDidLoad];
  29. self.view.backgroundColor = [UIColor blackColor];
  30. self.visibleKLineCount = 40;
  31. [self generateMockData];
  32. [self setupSubviews];
  33. [self setupConstraints];
  34. dispatch_async(dispatch_get_main_queue(), ^{
  35. [self drawKLineChart];
  36. if (self.kLineScrollView.contentSize.width > self.kLineScrollView.bounds.size.width) {
  37. CGPoint offset = CGPointMake(self.kLineScrollView.contentSize.width - self.kLineScrollView.bounds.size.width, 0);
  38. [self.kLineScrollView setContentOffset:offset animated:NO];
  39. }
  40. });
  41. }
  42. #pragma mark - UI Setup
  43. -(void) setupSubviews{
  44. // 卡片容器
  45. _cardContainer = [[UIView alloc] init];
  46. _cardContainer.backgroundColor = [UIColor colorWithRed:26.0/255.0
  47. green:26.0/255.0
  48. blue:2.0/255.0
  49. alpha:1.0];
  50. _cardContainer.translatesAutoresizingMaskIntoConstraints = NO;
  51. [self.view addSubview:_cardContainer];
  52. UILabel *cardLabel1 = [[UILabel alloc] init];
  53. cardLabel1.text = @"1617.060";
  54. cardLabel1.textColor = [UIColor redColor];
  55. cardLabel1.font = [UIFont systemFontOfSize:15];
  56. cardLabel1.translatesAutoresizingMaskIntoConstraints = NO;
  57. [_cardContainer addSubview:cardLabel1];
  58. UILabel *cardLabel2 = [[UILabel alloc] init];
  59. cardLabel2.text = @"-7.470";
  60. cardLabel2.textColor = [UIColor redColor];
  61. cardLabel2.font = [UIFont systemFontOfSize:8];
  62. cardLabel2.translatesAutoresizingMaskIntoConstraints = NO;
  63. [_cardContainer addSubview:cardLabel2];
  64. UILabel *cardLabel3 = [[UILabel alloc] init];
  65. cardLabel3.text = @"-0.461%";
  66. cardLabel3.textColor = [UIColor redColor];
  67. cardLabel3.font = [UIFont systemFontOfSize:8];
  68. cardLabel3.translatesAutoresizingMaskIntoConstraints = NO;
  69. [_cardContainer addSubview:cardLabel3];
  70. [NSLayoutConstraint activateConstraints:@[
  71. [cardLabel1.topAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:25],
  72. [cardLabel1.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:10],
  73. [cardLabel2.topAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:50],
  74. [cardLabel2.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant: 8],
  75. [cardLabel3.topAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:50],
  76. [cardLabel3.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant: 45]
  77. ]];
  78. UILabel *cardLabelHeight = [[UILabel alloc] init];
  79. cardLabelHeight.text = @"高";
  80. cardLabelHeight.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
  81. cardLabelHeight.font = [UIFont systemFontOfSize:15];
  82. cardLabelHeight.translatesAutoresizingMaskIntoConstraints = NO;
  83. [_cardContainer addSubview:cardLabelHeight];
  84. UILabel *cardLabelStart = [[UILabel alloc] init];
  85. cardLabelStart.text = @"开";
  86. cardLabelStart.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
  87. cardLabelStart.font = [UIFont systemFontOfSize:15];
  88. cardLabelStart.translatesAutoresizingMaskIntoConstraints = NO;
  89. [_cardContainer addSubview:cardLabelStart];
  90. UILabel *cardLabelVolume = [[UILabel alloc] init];
  91. cardLabelVolume.text = @"量";
  92. cardLabelVolume.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
  93. cardLabelVolume.font = [UIFont systemFontOfSize:15];
  94. cardLabelVolume.translatesAutoresizingMaskIntoConstraints = NO;
  95. [_cardContainer addSubview:cardLabelVolume];
  96. UILabel *cardLabelLow = [[UILabel alloc] init];
  97. cardLabelLow.text = @"低";
  98. cardLabelLow.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
  99. cardLabelLow.font = [UIFont systemFontOfSize:15];
  100. cardLabelLow.translatesAutoresizingMaskIntoConstraints = NO;
  101. [_cardContainer addSubview:cardLabelLow];
  102. UILabel *cardLabelChange = [[UILabel alloc] init];
  103. cardLabelChange.text = @"换";
  104. cardLabelChange.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
  105. cardLabelChange.font = [UIFont systemFontOfSize:15];
  106. cardLabelChange.translatesAutoresizingMaskIntoConstraints = NO;
  107. [_cardContainer addSubview:cardLabelChange];
  108. UILabel *cardLabelQuota = [[UILabel alloc] init];
  109. cardLabelQuota.text = @"额";
  110. cardLabelQuota.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
  111. cardLabelQuota.font = [UIFont systemFontOfSize:15];
  112. cardLabelQuota.translatesAutoresizingMaskIntoConstraints = NO;
  113. [_cardContainer addSubview:cardLabelQuota];
  114. UILabel *cardNumHeight = [[UILabel alloc] init];
  115. cardNumHeight.text = @"1617.260";
  116. cardNumHeight.textColor = [UIColor redColor];
  117. cardNumHeight.font = [UIFont systemFontOfSize:12];
  118. cardNumHeight.translatesAutoresizingMaskIntoConstraints = NO;
  119. [_cardContainer addSubview:cardNumHeight];
  120. UILabel *cardNumStart = [[UILabel alloc] init];
  121. cardNumStart.text = @"1616.750";
  122. cardNumStart.textColor = [UIColor redColor];
  123. cardNumStart.font = [UIFont systemFontOfSize:12];
  124. cardNumStart.translatesAutoresizingMaskIntoConstraints = NO;
  125. [_cardContainer addSubview:cardNumStart];
  126. UILabel *cardNumVolume = [[UILabel alloc] init];
  127. cardNumVolume.text = @"1.511亿";
  128. cardNumVolume.textColor = [UIColor whiteColor];
  129. cardNumVolume.font = [UIFont systemFontOfSize:12];
  130. cardNumVolume.translatesAutoresizingMaskIntoConstraints = NO;
  131. [_cardContainer addSubview:cardNumVolume];
  132. UILabel *cardNumLow = [[UILabel alloc] init];
  133. cardNumLow.text = @"1608.850";
  134. cardNumLow.textColor = [UIColor whiteColor];
  135. cardNumLow.font = [UIFont systemFontOfSize:12];
  136. cardNumLow.translatesAutoresizingMaskIntoConstraints = NO;
  137. [_cardContainer addSubview:cardNumLow];
  138. UILabel *cardNumChange = [[UILabel alloc] init];
  139. cardNumChange.text = @"--";
  140. cardNumChange.textColor = [UIColor whiteColor];
  141. cardNumChange.font = [UIFont systemFontOfSize:12];
  142. cardNumChange.translatesAutoresizingMaskIntoConstraints = NO;
  143. [_cardContainer addSubview:cardNumChange];
  144. UILabel *cardNumQuota = [[UILabel alloc] init];
  145. cardNumQuota.text = @"--";
  146. cardNumQuota.textColor = [UIColor whiteColor];
  147. cardNumQuota.font = [UIFont systemFontOfSize:12];
  148. cardNumQuota.translatesAutoresizingMaskIntoConstraints = NO;
  149. [_cardContainer addSubview:cardNumQuota];
  150. [NSLayoutConstraint activateConstraints:@[
  151. [cardLabelHeight.bottomAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-75],
  152. [cardLabelStart.bottomAnchor constraintEqualToAnchor:cardLabelHeight.bottomAnchor],
  153. [cardLabelVolume.bottomAnchor constraintEqualToAnchor:cardLabelHeight.bottomAnchor],
  154. [cardLabelLow.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-50],
  155. [cardLabelLow.leadingAnchor constraintEqualToAnchor:cardLabelHeight.leadingAnchor],
  156. [cardLabelChange.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-50],
  157. [cardLabelChange.leadingAnchor constraintEqualToAnchor:cardLabelStart.leadingAnchor],
  158. [cardLabelQuota.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-50],
  159. [cardLabelQuota.leadingAnchor constraintEqualToAnchor:cardLabelVolume.leadingAnchor],
  160. [cardNumHeight.bottomAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:45],
  161. [cardNumHeight.leadingAnchor constraintEqualToAnchor:cardLabelHeight.leadingAnchor],
  162. [cardNumStart.bottomAnchor constraintEqualToAnchor:cardNumHeight.bottomAnchor],
  163. [cardNumStart.leadingAnchor constraintEqualToAnchor:cardLabelStart.leadingAnchor],
  164. [cardNumVolume.bottomAnchor constraintEqualToAnchor:cardNumHeight.bottomAnchor],
  165. [cardNumVolume.leadingAnchor constraintEqualToAnchor:cardLabelVolume.leadingAnchor],
  166. [cardNumLow.bottomAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-10],
  167. [cardNumLow.leadingAnchor constraintEqualToAnchor:cardLabelLow.leadingAnchor],
  168. [cardNumChange.bottomAnchor constraintEqualToAnchor:cardNumLow.bottomAnchor],
  169. [cardNumChange.leadingAnchor constraintEqualToAnchor:cardLabelChange.leadingAnchor],
  170. [cardNumQuota.bottomAnchor constraintEqualToAnchor:cardNumLow.bottomAnchor],
  171. [cardNumQuota.leadingAnchor constraintEqualToAnchor:cardLabelVolume.leadingAnchor]
  172. ]];
  173. [NSLayoutConstraint constraintWithItem:cardLabelHeight
  174. attribute:NSLayoutAttributeLeading
  175. relatedBy:NSLayoutRelationEqual
  176. toItem:_cardContainer
  177. attribute:NSLayoutAttributeTrailing
  178. multiplier:0.25
  179. constant:0].active = YES;
  180. [NSLayoutConstraint constraintWithItem:cardLabelStart
  181. attribute:NSLayoutAttributeLeading
  182. relatedBy:NSLayoutRelationEqual
  183. toItem:_cardContainer
  184. attribute:NSLayoutAttributeTrailing
  185. multiplier:0.5
  186. constant:0].active = YES;
  187. [NSLayoutConstraint constraintWithItem:cardLabelVolume
  188. attribute:NSLayoutAttributeLeading
  189. relatedBy:NSLayoutRelationEqual
  190. toItem:_cardContainer
  191. attribute:NSLayoutAttributeTrailing
  192. multiplier:0.75
  193. constant:0].active = YES;
  194. // K线选择项容器
  195. _kSelectContainer = [[UIView alloc] init];
  196. _kSelectContainer.backgroundColor = [UIColor colorWithRed:26.0/255.0
  197. green:26.0/255.0
  198. blue:2.0/255.0
  199. alpha:1.0];
  200. _kSelectContainer.translatesAutoresizingMaskIntoConstraints = NO;
  201. [self.view addSubview:_kSelectContainer];
  202. [self addKSelectOptions];
  203. // K线图表容器
  204. self.kLineContainer = [[UIView alloc] init];
  205. self.kLineContainer.backgroundColor = [UIColor colorWithRed:26.0/255.0 green:26.0/255.0 blue:26.0/255.0 alpha:1.0];
  206. self.kLineContainer.translatesAutoresizingMaskIntoConstraints = NO;
  207. [self.view addSubview:self.kLineContainer];
  208. // K线图表滚动视图
  209. self.kLineScrollView = [[UIScrollView alloc] init];
  210. self.kLineScrollView.backgroundColor = [UIColor clearColor];
  211. self.kLineScrollView.showsHorizontalScrollIndicator = NO;
  212. self.kLineScrollView.userInteractionEnabled = YES;
  213. self.kLineScrollView.translatesAutoresizingMaskIntoConstraints = NO;
  214. [self.kLineContainer addSubview:self.kLineScrollView];
  215. CGFloat priceLabelAreaWidth = 50.0;
  216. // 滚动视图的约束
  217. [NSLayoutConstraint activateConstraints:@[
  218. [self.kLineScrollView.topAnchor constraintEqualToAnchor:self.kLineContainer.topAnchor],
  219. [self.kLineScrollView.bottomAnchor constraintEqualToAnchor:self.kLineContainer.bottomAnchor],
  220. [self.kLineScrollView.leadingAnchor constraintEqualToAnchor:self.kLineContainer.leadingAnchor constant:priceLabelAreaWidth],
  221. [self.kLineScrollView.trailingAnchor constraintEqualToAnchor:self.kLineContainer.trailingAnchor],
  222. [self.kLineScrollView.heightAnchor constraintEqualToAnchor:self.kLineContainer.heightAnchor]
  223. ]];
  224. // 边框
  225. UIView *borderView = [[UIView alloc] init];
  226. borderView.backgroundColor = [UIColor clearColor];
  227. borderView.layer.borderWidth = 1;
  228. borderView.layer.borderColor = [[UIColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] CGColor];
  229. borderView.translatesAutoresizingMaskIntoConstraints = NO;
  230. [self.kLineContainer addSubview:borderView];
  231. [NSLayoutConstraint activateConstraints:@[
  232. [borderView.leadingAnchor constraintEqualToAnchor:self.kLineContainer.leadingAnchor],
  233. [borderView.trailingAnchor constraintEqualToAnchor:self.kLineContainer.trailingAnchor],
  234. [borderView.topAnchor constraintEqualToAnchor:self.kLineContainer.topAnchor],
  235. [borderView.bottomAnchor constraintEqualToAnchor:self.kLineContainer.bottomAnchor]
  236. ]];
  237. // 动态计算模型数据的价格区间
  238. CGFloat maxPrice = 0;
  239. CGFloat minPrice = CGFLOAT_MAX;
  240. for (id item in self.kLineData) {
  241. if ([item isKindOfClass:[StockKLineModel class]]) {
  242. StockKLineModel *model = (StockKLineModel *)item;
  243. maxPrice = MAX(maxPrice, model.high);
  244. minPrice = MIN(minPrice, model.low);
  245. } else if ([item isKindOfClass:[NSDictionary class]]) {
  246. NSDictionary *dict = (NSDictionary *)item;
  247. maxPrice = MAX(maxPrice, [dict[@"high"] floatValue]);
  248. minPrice = MIN(minPrice, [dict[@"low"] floatValue]);
  249. }
  250. }
  251. maxPrice = maxPrice + (maxPrice - minPrice) * 0.1;
  252. minPrice = minPrice - (maxPrice - minPrice) * 0.1;
  253. // 价格刻度
  254. NSMutableArray *priceLabelArr = [NSMutableArray array];
  255. for (NSInteger i = 0; i < 5; i++) {
  256. CGFloat price = maxPrice - (maxPrice - minPrice) / 4 * i;
  257. UILabel *label = [[UILabel alloc] init];
  258. label.text = [NSString stringWithFormat:@"%.1f", price];
  259. label.textColor = [UIColor lightGrayColor];
  260. label.font = [UIFont systemFontOfSize:10];
  261. label.translatesAutoresizingMaskIntoConstraints = NO;
  262. [self.kLineContainer addSubview:label];
  263. [NSLayoutConstraint activateConstraints:@[
  264. [label.leadingAnchor constraintEqualToAnchor:self.kLineContainer.leadingAnchor constant:5],
  265. [label.centerYAnchor constraintEqualToAnchor:self.kLineContainer.centerYAnchor constant:-80 + 40 * i]
  266. ]];
  267. [priceLabelArr addObject:label];
  268. }
  269. self.priceLabels = priceLabelArr;
  270. self.dateLabels = @[];
  271. }
  272. - (void)addKSelectOptions {
  273. NSArray *titles = @[@"日k", @"周k", @"月k", @"更多", @"设置"];
  274. UIStackView *stackView = [[UIStackView alloc] init];
  275. stackView.axis = UILayoutConstraintAxisHorizontal;
  276. stackView.distribution = UIStackViewDistributionFillEqually;
  277. stackView.alignment = UIStackViewAlignmentCenter;
  278. stackView.spacing = 5.0;
  279. stackView.translatesAutoresizingMaskIntoConstraints = NO;
  280. for (NSString *title in titles) {
  281. UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
  282. [button setTitle:title forState:UIControlStateNormal];
  283. button.titleLabel.font = [UIFont systemFontOfSize:14];
  284. [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
  285. if ([title isEqualToString:@"日k"]) {
  286. [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
  287. button.layer.borderColor = [[UIColor yellowColor] CGColor];
  288. button.layer.borderWidth = 1.0;
  289. } else {
  290. [button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
  291. button.layer.borderWidth = 0;
  292. }
  293. button.layer.cornerRadius = 3.0;
  294. button.clipsToBounds = YES;
  295. [stackView addArrangedSubview:button];
  296. }
  297. [_kSelectContainer addSubview:stackView];
  298. [NSLayoutConstraint activateConstraints:@[
  299. [stackView.leadingAnchor constraintEqualToAnchor:_kSelectContainer.leadingAnchor constant:10],
  300. [stackView.trailingAnchor constraintEqualToAnchor:_kSelectContainer.trailingAnchor constant:-10],
  301. [stackView.topAnchor constraintEqualToAnchor:_kSelectContainer.topAnchor constant:5],
  302. [stackView.bottomAnchor constraintEqualToAnchor:_kSelectContainer.bottomAnchor constant:-5]
  303. ]];
  304. }
  305. #pragma mark - Constraints
  306. - (void)setupConstraints{
  307. [NSLayoutConstraint activateConstraints:@[
  308. [_cardContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
  309. [_cardContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  310. [_cardContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
  311. [_cardContainer.heightAnchor constraintEqualToConstant:100],
  312. [_kSelectContainer.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:5],
  313. [_kSelectContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  314. [_kSelectContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
  315. [_kSelectContainer.heightAnchor constraintEqualToConstant:40],
  316. [self.kLineContainer.topAnchor constraintEqualToAnchor:self.kSelectContainer.bottomAnchor constant:5],
  317. [self.kLineContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  318. [self.kLineContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
  319. [self.kLineContainer.heightAnchor constraintEqualToConstant:200]
  320. ]];
  321. }
  322. #pragma mark - KLine Drawing
  323. - (void)drawKLineChart {
  324. // 移除旧的 K 线
  325. for (UIView *subview in self.kLineScrollView.subviews) {
  326. if (![subview isKindOfClass:[UILabel class]]) {
  327. [subview removeFromSuperview];
  328. }
  329. }
  330. CGFloat chartHeight = self.kLineContainer.bounds.size.height;
  331. if (chartHeight == 0) {
  332. chartHeight = 200;
  333. }
  334. // K线宽度计算
  335. CGFloat kLineUnitWidth = 5.0;
  336. CGFloat kLineRatio = 0.8;
  337. CGFloat kLineWidth = kLineUnitWidth * kLineRatio;
  338. CGFloat space = kLineUnitWidth * (1.0 - kLineRatio);
  339. CGFloat totalChartWidth = kLineUnitWidth * self.kLineData.count;
  340. // 设置contentSize
  341. self.kLineScrollView.contentSize = CGSizeMake(totalChartWidth, chartHeight);
  342. // 动态计算价格区间
  343. CGFloat maxPrice = 0;
  344. CGFloat minPrice = CGFLOAT_MAX;
  345. for (id item in self.kLineData) {
  346. if ([item isKindOfClass:[StockKLineModel class]]) {
  347. StockKLineModel *model = (StockKLineModel *)item;
  348. maxPrice = MAX(maxPrice, model.high);
  349. minPrice = MIN(minPrice, model.low);
  350. } else if ([item isKindOfClass:[NSDictionary class]]) {
  351. NSDictionary *dict = (NSDictionary *)item;
  352. maxPrice = MAX(maxPrice, [dict[@"high"] floatValue]);
  353. minPrice = MIN(minPrice, [dict[@"low"] floatValue]);
  354. }
  355. }
  356. maxPrice = maxPrice + (maxPrice - minPrice) * 0.1;
  357. minPrice = minPrice - (maxPrice - minPrice) * 0.1;
  358. CGFloat priceRange = maxPrice - minPrice;
  359. // 绘制K线
  360. for (NSInteger i = 0; i < self.kLineData.count; i++) {
  361. CGFloat open = 0, high = 0, low = 0, close = 0;
  362. if ([self.kLineData[i] isKindOfClass:[StockKLineModel class]]) {
  363. StockKLineModel *model = (StockKLineModel *)self.kLineData[i];
  364. open = model.open;
  365. high = model.high;
  366. low = model.low;
  367. close = model.close;
  368. } else if ([self.kLineData[i] isKindOfClass:[NSDictionary class]]) {
  369. NSDictionary *dict = (NSDictionary *)self.kLineData[i];
  370. open = [dict[@"open"] floatValue];
  371. high = [dict[@"high"] floatValue];
  372. low = [dict[@"low"] floatValue];
  373. close = [dict[@"close"] floatValue];
  374. }
  375. // 坐标转换
  376. CGFloat yOpen = chartHeight * (maxPrice - open) / priceRange;
  377. CGFloat yHigh = chartHeight * (maxPrice - high) / priceRange;
  378. CGFloat yLow = chartHeight * (maxPrice - low) / priceRange;
  379. CGFloat yClose = chartHeight * (maxPrice - close) / priceRange;
  380. // 计算X坐标
  381. CGFloat xPosition = space/2 + kLineUnitWidth * i;
  382. // 绘制K线实体
  383. UIView *bodyView = [[UIView alloc] init];
  384. bodyView.backgroundColor = (close >= open) ? [UIColor greenColor] : [UIColor redColor];
  385. bodyView.frame = CGRectMake(
  386. xPosition,
  387. MIN(yOpen, yClose),
  388. kLineWidth,
  389. MAX(1.0, fabs(yClose - yOpen))
  390. );
  391. [self.kLineScrollView addSubview:bodyView];
  392. // 绘制影线
  393. UIView *shadowView = [[UIView alloc] init];
  394. shadowView.backgroundColor = bodyView.backgroundColor;
  395. shadowView.frame = CGRectMake(
  396. bodyView.center.x - 0.5,
  397. yHigh,
  398. 1,
  399. yLow - yHigh
  400. );
  401. [self.kLineScrollView addSubview:shadowView];
  402. }
  403. // 日期刻度
  404. for (UILabel *label in self.dateLabels) {
  405. [label removeFromSuperview];
  406. }
  407. NSMutableArray *dateLabelArr = [NSMutableArray array];
  408. NSInteger dateInterval = 10;
  409. for (NSInteger i = 0; i < self.kLineData.count; i += dateInterval) {
  410. NSString *dateStr = @"";
  411. if ([self.kLineData[i] isKindOfClass:[StockKLineModel class]]) {
  412. StockKLineModel *model = (StockKLineModel *)self.kLineData[i];
  413. dateStr = model.date;
  414. } else if ([self.kLineData[i] isKindOfClass:[NSDictionary class]]) {
  415. NSDictionary *dict = (NSDictionary *)self.kLineData[i];
  416. dateStr = dict[@"date"];
  417. }
  418. UILabel *label = [[UILabel alloc] init];
  419. label.text = dateStr;
  420. label.textColor = [UIColor lightGrayColor];
  421. label.font = [UIFont systemFontOfSize:10];
  422. label.translatesAutoresizingMaskIntoConstraints = YES;
  423. CGFloat xCenter = kLineUnitWidth * (i + 0.5);
  424. [label sizeToFit];
  425. label.frame = CGRectMake(
  426. xCenter - label.bounds.size.width / 2,
  427. chartHeight - 15,
  428. label.bounds.size.width,
  429. label.bounds.size.height
  430. );
  431. [self.kLineScrollView addSubview:label];
  432. [dateLabelArr addObject:label];
  433. }
  434. self.dateLabels = dateLabelArr;
  435. NSLog(@"ScrollView Bounds Width: %f", self.kLineScrollView.bounds.size.width);
  436. NSLog(@"Content Size Width: %f", self.kLineScrollView.contentSize.width);
  437. if (self.kLineScrollView.contentSize.width <= self.kLineScrollView.bounds.size.width) {
  438. NSLog(@"⚠️ 警告:ContentSize 不够大,无法滑动。");
  439. }
  440. }
  441. #pragma mark - Mock Data
  442. - (void) setupData{
  443. self.kLineData = @[
  444. @{@"date":@"2025/11/18", @"open":@1620.0, @"high":@1643.0, @"low":@1605.0, @"close":@1630.0, @"volume":@2.5},
  445. @{@"date":@"2025/11/19", @"open":@1630.0, @"high":@1635.0, @"low":@1615.0, @"close":@1620.0, @"volume":@2.2},
  446. @{@"date":@"2025/11/20", @"open":@1620.0, @"high":@1628.0, @"low":@1608.0, @"close":@1615.0, @"volume":@1.8},
  447. @{@"date":@"2025/11/21", @"open":@1615.0, @"high":@1625.0, @"low":@1610.0, @"close":@1622.0, @"volume":@2.0},
  448. @{@"date":@"2025/11/22", @"open":@1622.0, @"high":@1630.0, @"low":@1618.0, @"close":@1625.0, @"volume":@2.3},
  449. @{@"date":@"2025/11/25", @"open":@1625.0, @"high":@1632.0, @"low":@1620.0, @"close":@1628.0, @"volume":@1.9},
  450. @{@"date":@"2025/11/26", @"open":@1628.0, @"high":@1635.0, @"low":@1622.0, @"close":@1630.0, @"volume":@2.1},
  451. @{@"date":@"2025/11/27", @"open":@1630.0, @"high":@1638.0, @"low":@1625.0, @"close":@1628.0, @"volume":@2.4},
  452. @{@"date":@"2025/11/28", @"open":@1628.0, @"high":@1632.0, @"low":@1609.0, @"close":@1610.0, @"volume":@2.6},
  453. @{@"date":@"2025/11/29", @"open":@1610.0, @"high":@1620.0, @"low":@1608.0, @"close":@1615.0, @"volume":@1.7}
  454. ];
  455. }
  456. - (void)generateMockData {
  457. NSMutableArray *arr = [NSMutableArray array];
  458. CGFloat lastClose = 100.0;
  459. NSLog(@"生成模拟数据中。。。");
  460. // 生成200条数据(减少数量,避免contentSize过大
  461. for (int i = 0; i < 200; i++) {
  462. StockKLineModel *model = [[StockKLineModel alloc] init];
  463. NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-(200 - i) * 24 * 3600];
  464. NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
  465. [fmt setDateFormat:@"MM-dd"];
  466. model.date = [fmt stringFromDate:date];
  467. // 价格生成逻辑
  468. CGFloat volatility = lastClose * 0.02;
  469. CGFloat randomChange = ((arc4random() % 100) / 100.0 - 0.5) * 2 * volatility;
  470. model.open = lastClose + ((arc4random() % 100) / 100.0 - 0.5) * volatility * 0.5;
  471. model.close = model.open + randomChange;
  472. CGFloat maxOC = MAX(model.open, model.close);
  473. CGFloat minOC = MIN(model.open, model.close);
  474. model.high = maxOC + (arc4random() % 100) / 100.0 * 1.0;
  475. model.low = minOC - (arc4random() % 100) / 100.0 * 1.0;
  476. // 成交量生成逻辑
  477. CGFloat baseVolume = 1000000.0;
  478. CGFloat randomVolFactor = (arc4random() % 200) / 100.0 + 0.5;
  479. if (fabs(model.close - model.open) / model.open > 0.03) {
  480. randomVolFactor *= 1.5;
  481. }
  482. model.volume = baseVolume * randomVolFactor;
  483. if (model.low < 0) model.low = 0.01;
  484. [arr addObject:model];
  485. lastClose = model.close;
  486. }
  487. NSLog(@"生成模拟数据完毕,共%ld条", arr.count);
  488. self.kLineData = arr;
  489. }
  490. @end