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
592 lines
26 KiB
// ChartViewController.m
|
|
// HC
|
|
//
|
|
// Created by huilinLi on 2025/11/27.
|
|
//
|
|
|
|
#import "ChartViewController.h"
|
|
#import "StockKLineModel.h"
|
|
|
|
@interface ChartViewController ()
|
|
|
|
// 上方卡片容器
|
|
@property (nonatomic, strong) UIView *cardContainer;
|
|
// k线选择项容器
|
|
@property (nonatomic, strong) UIView *kSelectContainer;
|
|
// K线图表容器
|
|
@property (nonatomic, strong) UIView *kLineContainer;
|
|
// K线图表滚动视图
|
|
@property (nonatomic, strong) UIScrollView *kLineScrollView;
|
|
|
|
// K线数据
|
|
@property (nonatomic, strong) NSArray *kLineData;
|
|
// 价格刻度标签
|
|
@property (nonatomic, strong) NSArray *priceLabels;
|
|
// 日期刻度标签
|
|
@property (nonatomic, strong) NSArray *dateLabels;
|
|
|
|
// 默认展示的K线数量
|
|
@property (nonatomic, assign) NSInteger visibleKLineCount;
|
|
|
|
@end
|
|
|
|
@implementation ChartViewController
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
self.view.backgroundColor = [UIColor blackColor];
|
|
|
|
self.visibleKLineCount = 40;
|
|
|
|
[self generateMockData];
|
|
[self setupSubviews];
|
|
[self setupConstraints];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self drawKLineChart];
|
|
|
|
if (self.kLineScrollView.contentSize.width > self.kLineScrollView.bounds.size.width) {
|
|
CGPoint offset = CGPointMake(self.kLineScrollView.contentSize.width - self.kLineScrollView.bounds.size.width, 0);
|
|
[self.kLineScrollView setContentOffset:offset animated:NO];
|
|
}
|
|
});
|
|
}
|
|
|
|
#pragma mark - UI Setup
|
|
-(void) setupSubviews{
|
|
// 卡片容器
|
|
_cardContainer = [[UIView alloc] init];
|
|
_cardContainer.backgroundColor = [UIColor colorWithRed:26.0/255.0
|
|
green:26.0/255.0
|
|
blue:2.0/255.0
|
|
alpha:1.0];
|
|
_cardContainer.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.view addSubview:_cardContainer];
|
|
|
|
UILabel *cardLabel1 = [[UILabel alloc] init];
|
|
cardLabel1.text = @"1617.060";
|
|
cardLabel1.textColor = [UIColor redColor];
|
|
cardLabel1.font = [UIFont systemFontOfSize:15];
|
|
cardLabel1.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabel1];
|
|
|
|
UILabel *cardLabel2 = [[UILabel alloc] init];
|
|
cardLabel2.text = @"-7.470";
|
|
cardLabel2.textColor = [UIColor redColor];
|
|
cardLabel2.font = [UIFont systemFontOfSize:8];
|
|
cardLabel2.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabel2];
|
|
|
|
UILabel *cardLabel3 = [[UILabel alloc] init];
|
|
cardLabel3.text = @"-0.461%";
|
|
cardLabel3.textColor = [UIColor redColor];
|
|
cardLabel3.font = [UIFont systemFontOfSize:8];
|
|
cardLabel3.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabel3];
|
|
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[cardLabel1.topAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:25],
|
|
[cardLabel1.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:10],
|
|
|
|
[cardLabel2.topAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:50],
|
|
[cardLabel2.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant: 8],
|
|
|
|
[cardLabel3.topAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:50],
|
|
[cardLabel3.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant: 45]
|
|
]];
|
|
|
|
UILabel *cardLabelHeight = [[UILabel alloc] init];
|
|
cardLabelHeight.text = @"高";
|
|
cardLabelHeight.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
|
|
cardLabelHeight.font = [UIFont systemFontOfSize:15];
|
|
cardLabelHeight.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabelHeight];
|
|
|
|
UILabel *cardLabelStart = [[UILabel alloc] init];
|
|
cardLabelStart.text = @"开";
|
|
cardLabelStart.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
|
|
cardLabelStart.font = [UIFont systemFontOfSize:15];
|
|
cardLabelStart.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabelStart];
|
|
|
|
UILabel *cardLabelVolume = [[UILabel alloc] init];
|
|
cardLabelVolume.text = @"量";
|
|
cardLabelVolume.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
|
|
cardLabelVolume.font = [UIFont systemFontOfSize:15];
|
|
cardLabelVolume.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabelVolume];
|
|
|
|
UILabel *cardLabelLow = [[UILabel alloc] init];
|
|
cardLabelLow.text = @"低";
|
|
cardLabelLow.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
|
|
cardLabelLow.font = [UIFont systemFontOfSize:15];
|
|
cardLabelLow.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabelLow];
|
|
|
|
UILabel *cardLabelChange = [[UILabel alloc] init];
|
|
cardLabelChange.text = @"换";
|
|
cardLabelChange.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
|
|
cardLabelChange.font = [UIFont systemFontOfSize:15];
|
|
cardLabelChange.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabelChange];
|
|
|
|
UILabel *cardLabelQuota = [[UILabel alloc] init];
|
|
cardLabelQuota.text = @"额";
|
|
cardLabelQuota.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1];
|
|
cardLabelQuota.font = [UIFont systemFontOfSize:15];
|
|
cardLabelQuota.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardLabelQuota];
|
|
|
|
UILabel *cardNumHeight = [[UILabel alloc] init];
|
|
cardNumHeight.text = @"1617.260";
|
|
cardNumHeight.textColor = [UIColor redColor];
|
|
cardNumHeight.font = [UIFont systemFontOfSize:12];
|
|
cardNumHeight.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardNumHeight];
|
|
|
|
UILabel *cardNumStart = [[UILabel alloc] init];
|
|
cardNumStart.text = @"1616.750";
|
|
cardNumStart.textColor = [UIColor redColor];
|
|
cardNumStart.font = [UIFont systemFontOfSize:12];
|
|
cardNumStart.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardNumStart];
|
|
|
|
UILabel *cardNumVolume = [[UILabel alloc] init];
|
|
cardNumVolume.text = @"1.511亿";
|
|
cardNumVolume.textColor = [UIColor whiteColor];
|
|
cardNumVolume.font = [UIFont systemFontOfSize:12];
|
|
cardNumVolume.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardNumVolume];
|
|
|
|
UILabel *cardNumLow = [[UILabel alloc] init];
|
|
cardNumLow.text = @"1608.850";
|
|
cardNumLow.textColor = [UIColor whiteColor];
|
|
cardNumLow.font = [UIFont systemFontOfSize:12];
|
|
cardNumLow.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardNumLow];
|
|
|
|
UILabel *cardNumChange = [[UILabel alloc] init];
|
|
cardNumChange.text = @"--";
|
|
cardNumChange.textColor = [UIColor whiteColor];
|
|
cardNumChange.font = [UIFont systemFontOfSize:12];
|
|
cardNumChange.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardNumChange];
|
|
|
|
UILabel *cardNumQuota = [[UILabel alloc] init];
|
|
cardNumQuota.text = @"--";
|
|
cardNumQuota.textColor = [UIColor whiteColor];
|
|
cardNumQuota.font = [UIFont systemFontOfSize:12];
|
|
cardNumQuota.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[_cardContainer addSubview:cardNumQuota];
|
|
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[cardLabelHeight.bottomAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-75],
|
|
|
|
[cardLabelStart.bottomAnchor constraintEqualToAnchor:cardLabelHeight.bottomAnchor],
|
|
|
|
[cardLabelVolume.bottomAnchor constraintEqualToAnchor:cardLabelHeight.bottomAnchor],
|
|
|
|
[cardLabelLow.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-50],
|
|
[cardLabelLow.leadingAnchor constraintEqualToAnchor:cardLabelHeight.leadingAnchor],
|
|
|
|
[cardLabelChange.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-50],
|
|
[cardLabelChange.leadingAnchor constraintEqualToAnchor:cardLabelStart.leadingAnchor],
|
|
|
|
[cardLabelQuota.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-50],
|
|
[cardLabelQuota.leadingAnchor constraintEqualToAnchor:cardLabelVolume.leadingAnchor],
|
|
|
|
[cardNumHeight.bottomAnchor constraintEqualToAnchor:_cardContainer.topAnchor constant:45],
|
|
[cardNumHeight.leadingAnchor constraintEqualToAnchor:cardLabelHeight.leadingAnchor],
|
|
|
|
[cardNumStart.bottomAnchor constraintEqualToAnchor:cardNumHeight.bottomAnchor],
|
|
[cardNumStart.leadingAnchor constraintEqualToAnchor:cardLabelStart.leadingAnchor],
|
|
|
|
[cardNumVolume.bottomAnchor constraintEqualToAnchor:cardNumHeight.bottomAnchor],
|
|
[cardNumVolume.leadingAnchor constraintEqualToAnchor:cardLabelVolume.leadingAnchor],
|
|
|
|
[cardNumLow.bottomAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:-10],
|
|
[cardNumLow.leadingAnchor constraintEqualToAnchor:cardLabelLow.leadingAnchor],
|
|
|
|
[cardNumChange.bottomAnchor constraintEqualToAnchor:cardNumLow.bottomAnchor],
|
|
[cardNumChange.leadingAnchor constraintEqualToAnchor:cardLabelChange.leadingAnchor],
|
|
|
|
[cardNumQuota.bottomAnchor constraintEqualToAnchor:cardNumLow.bottomAnchor],
|
|
[cardNumQuota.leadingAnchor constraintEqualToAnchor:cardLabelVolume.leadingAnchor]
|
|
]];
|
|
|
|
[NSLayoutConstraint constraintWithItem:cardLabelHeight
|
|
attribute:NSLayoutAttributeLeading
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:_cardContainer
|
|
attribute:NSLayoutAttributeTrailing
|
|
multiplier:0.25
|
|
constant:0].active = YES;
|
|
[NSLayoutConstraint constraintWithItem:cardLabelStart
|
|
attribute:NSLayoutAttributeLeading
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:_cardContainer
|
|
attribute:NSLayoutAttributeTrailing
|
|
multiplier:0.5
|
|
constant:0].active = YES;
|
|
[NSLayoutConstraint constraintWithItem:cardLabelVolume
|
|
attribute:NSLayoutAttributeLeading
|
|
relatedBy:NSLayoutRelationEqual
|
|
toItem:_cardContainer
|
|
attribute:NSLayoutAttributeTrailing
|
|
multiplier:0.75
|
|
constant:0].active = YES;
|
|
|
|
// K线选择项容器
|
|
_kSelectContainer = [[UIView alloc] init];
|
|
_kSelectContainer.backgroundColor = [UIColor colorWithRed:26.0/255.0
|
|
green:26.0/255.0
|
|
blue:2.0/255.0
|
|
alpha:1.0];
|
|
_kSelectContainer.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.view addSubview:_kSelectContainer];
|
|
[self addKSelectOptions];
|
|
|
|
// K线图表容器
|
|
self.kLineContainer = [[UIView alloc] init];
|
|
self.kLineContainer.backgroundColor = [UIColor colorWithRed:26.0/255.0 green:26.0/255.0 blue:26.0/255.0 alpha:1.0];
|
|
self.kLineContainer.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.view addSubview:self.kLineContainer];
|
|
|
|
// K线图表滚动视图
|
|
self.kLineScrollView = [[UIScrollView alloc] init];
|
|
self.kLineScrollView.backgroundColor = [UIColor clearColor];
|
|
self.kLineScrollView.showsHorizontalScrollIndicator = NO;
|
|
self.kLineScrollView.userInteractionEnabled = YES;
|
|
self.kLineScrollView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
[self.kLineContainer addSubview:self.kLineScrollView];
|
|
|
|
CGFloat priceLabelAreaWidth = 50.0;
|
|
|
|
// 滚动视图的约束
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[self.kLineScrollView.topAnchor constraintEqualToAnchor:self.kLineContainer.topAnchor],
|
|
[self.kLineScrollView.bottomAnchor constraintEqualToAnchor:self.kLineContainer.bottomAnchor],
|
|
[self.kLineScrollView.leadingAnchor constraintEqualToAnchor:self.kLineContainer.leadingAnchor constant:priceLabelAreaWidth],
|
|
[self.kLineScrollView.trailingAnchor constraintEqualToAnchor:self.kLineContainer.trailingAnchor],
|
|
[self.kLineScrollView.heightAnchor constraintEqualToAnchor:self.kLineContainer.heightAnchor]
|
|
]];
|
|
|
|
// 边框
|
|
UIView *borderView = [[UIView alloc] init];
|
|
borderView.backgroundColor = [UIColor clearColor];
|
|
borderView.layer.borderWidth = 1;
|
|
borderView.layer.borderColor = [[UIColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] CGColor];
|
|
borderView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.kLineContainer addSubview:borderView];
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[borderView.leadingAnchor constraintEqualToAnchor:self.kLineContainer.leadingAnchor],
|
|
[borderView.trailingAnchor constraintEqualToAnchor:self.kLineContainer.trailingAnchor],
|
|
[borderView.topAnchor constraintEqualToAnchor:self.kLineContainer.topAnchor],
|
|
[borderView.bottomAnchor constraintEqualToAnchor:self.kLineContainer.bottomAnchor]
|
|
]];
|
|
|
|
// 动态计算模型数据的价格区间
|
|
CGFloat maxPrice = 0;
|
|
CGFloat minPrice = CGFLOAT_MAX;
|
|
for (id item in self.kLineData) {
|
|
if ([item isKindOfClass:[StockKLineModel class]]) {
|
|
StockKLineModel *model = (StockKLineModel *)item;
|
|
maxPrice = MAX(maxPrice, model.high);
|
|
minPrice = MIN(minPrice, model.low);
|
|
} else if ([item isKindOfClass:[NSDictionary class]]) {
|
|
NSDictionary *dict = (NSDictionary *)item;
|
|
maxPrice = MAX(maxPrice, [dict[@"high"] floatValue]);
|
|
minPrice = MIN(minPrice, [dict[@"low"] floatValue]);
|
|
}
|
|
}
|
|
maxPrice = maxPrice + (maxPrice - minPrice) * 0.1;
|
|
minPrice = minPrice - (maxPrice - minPrice) * 0.1;
|
|
|
|
// 价格刻度
|
|
NSMutableArray *priceLabelArr = [NSMutableArray array];
|
|
for (NSInteger i = 0; i < 5; i++) {
|
|
CGFloat price = maxPrice - (maxPrice - minPrice) / 4 * i;
|
|
UILabel *label = [[UILabel alloc] init];
|
|
label.text = [NSString stringWithFormat:@"%.1f", price];
|
|
label.textColor = [UIColor lightGrayColor];
|
|
label.font = [UIFont systemFontOfSize:10];
|
|
label.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[self.kLineContainer addSubview:label];
|
|
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[label.leadingAnchor constraintEqualToAnchor:self.kLineContainer.leadingAnchor constant:5],
|
|
[label.centerYAnchor constraintEqualToAnchor:self.kLineContainer.centerYAnchor constant:-80 + 40 * i]
|
|
]];
|
|
[priceLabelArr addObject:label];
|
|
}
|
|
self.priceLabels = priceLabelArr;
|
|
|
|
self.dateLabels = @[];
|
|
}
|
|
|
|
- (void)addKSelectOptions {
|
|
NSArray *titles = @[@"日k", @"周k", @"月k", @"更多", @"设置"];
|
|
|
|
UIStackView *stackView = [[UIStackView alloc] init];
|
|
stackView.axis = UILayoutConstraintAxisHorizontal;
|
|
stackView.distribution = UIStackViewDistributionFillEqually;
|
|
stackView.alignment = UIStackViewAlignmentCenter;
|
|
stackView.spacing = 5.0;
|
|
stackView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
for (NSString *title in titles) {
|
|
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
[button setTitle:title forState:UIControlStateNormal];
|
|
button.titleLabel.font = [UIFont systemFontOfSize:14];
|
|
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
|
|
|
if ([title isEqualToString:@"日k"]) {
|
|
[button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
|
|
button.layer.borderColor = [[UIColor yellowColor] CGColor];
|
|
button.layer.borderWidth = 1.0;
|
|
} else {
|
|
[button setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
|
|
button.layer.borderWidth = 0;
|
|
}
|
|
|
|
button.layer.cornerRadius = 3.0;
|
|
button.clipsToBounds = YES;
|
|
|
|
[stackView addArrangedSubview:button];
|
|
}
|
|
|
|
[_kSelectContainer addSubview:stackView];
|
|
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[stackView.leadingAnchor constraintEqualToAnchor:_kSelectContainer.leadingAnchor constant:10],
|
|
[stackView.trailingAnchor constraintEqualToAnchor:_kSelectContainer.trailingAnchor constant:-10],
|
|
[stackView.topAnchor constraintEqualToAnchor:_kSelectContainer.topAnchor constant:5],
|
|
[stackView.bottomAnchor constraintEqualToAnchor:_kSelectContainer.bottomAnchor constant:-5]
|
|
]];
|
|
}
|
|
|
|
#pragma mark - Constraints
|
|
- (void)setupConstraints{
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[_cardContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
|
|
[_cardContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
|
|
[_cardContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
|
|
[_cardContainer.heightAnchor constraintEqualToConstant:100],
|
|
|
|
[_kSelectContainer.topAnchor constraintEqualToAnchor:_cardContainer.bottomAnchor constant:5],
|
|
[_kSelectContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
|
|
[_kSelectContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
|
|
[_kSelectContainer.heightAnchor constraintEqualToConstant:40],
|
|
|
|
[self.kLineContainer.topAnchor constraintEqualToAnchor:self.kSelectContainer.bottomAnchor constant:5],
|
|
[self.kLineContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
|
|
[self.kLineContainer.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
|
|
[self.kLineContainer.heightAnchor constraintEqualToConstant:200]
|
|
]];
|
|
}
|
|
|
|
#pragma mark - KLine Drawing
|
|
- (void)drawKLineChart {
|
|
// 移除旧的 K 线
|
|
for (UIView *subview in self.kLineScrollView.subviews) {
|
|
if (![subview isKindOfClass:[UILabel class]]) {
|
|
[subview removeFromSuperview];
|
|
}
|
|
}
|
|
|
|
CGFloat chartHeight = self.kLineContainer.bounds.size.height;
|
|
if (chartHeight == 0) {
|
|
chartHeight = 200;
|
|
}
|
|
|
|
// K线宽度计算
|
|
CGFloat kLineUnitWidth = 5.0;
|
|
CGFloat kLineRatio = 0.8;
|
|
CGFloat kLineWidth = kLineUnitWidth * kLineRatio;
|
|
CGFloat space = kLineUnitWidth * (1.0 - kLineRatio);
|
|
CGFloat totalChartWidth = kLineUnitWidth * self.kLineData.count;
|
|
|
|
// 设置contentSize
|
|
self.kLineScrollView.contentSize = CGSizeMake(totalChartWidth, chartHeight);
|
|
|
|
// 动态计算价格区间
|
|
CGFloat maxPrice = 0;
|
|
CGFloat minPrice = CGFLOAT_MAX;
|
|
for (id item in self.kLineData) {
|
|
if ([item isKindOfClass:[StockKLineModel class]]) {
|
|
StockKLineModel *model = (StockKLineModel *)item;
|
|
maxPrice = MAX(maxPrice, model.high);
|
|
minPrice = MIN(minPrice, model.low);
|
|
} else if ([item isKindOfClass:[NSDictionary class]]) {
|
|
NSDictionary *dict = (NSDictionary *)item;
|
|
maxPrice = MAX(maxPrice, [dict[@"high"] floatValue]);
|
|
minPrice = MIN(minPrice, [dict[@"low"] floatValue]);
|
|
}
|
|
}
|
|
maxPrice = maxPrice + (maxPrice - minPrice) * 0.1;
|
|
minPrice = minPrice - (maxPrice - minPrice) * 0.1;
|
|
CGFloat priceRange = maxPrice - minPrice;
|
|
|
|
// 绘制K线
|
|
for (NSInteger i = 0; i < self.kLineData.count; i++) {
|
|
CGFloat open = 0, high = 0, low = 0, close = 0;
|
|
|
|
if ([self.kLineData[i] isKindOfClass:[StockKLineModel class]]) {
|
|
StockKLineModel *model = (StockKLineModel *)self.kLineData[i];
|
|
open = model.open;
|
|
high = model.high;
|
|
low = model.low;
|
|
close = model.close;
|
|
} else if ([self.kLineData[i] isKindOfClass:[NSDictionary class]]) {
|
|
NSDictionary *dict = (NSDictionary *)self.kLineData[i];
|
|
open = [dict[@"open"] floatValue];
|
|
high = [dict[@"high"] floatValue];
|
|
low = [dict[@"low"] floatValue];
|
|
close = [dict[@"close"] floatValue];
|
|
}
|
|
|
|
// 坐标转换
|
|
CGFloat yOpen = chartHeight * (maxPrice - open) / priceRange;
|
|
CGFloat yHigh = chartHeight * (maxPrice - high) / priceRange;
|
|
CGFloat yLow = chartHeight * (maxPrice - low) / priceRange;
|
|
CGFloat yClose = chartHeight * (maxPrice - close) / priceRange;
|
|
|
|
// 计算X坐标
|
|
CGFloat xPosition = space/2 + kLineUnitWidth * i;
|
|
|
|
// 绘制K线实体
|
|
UIView *bodyView = [[UIView alloc] init];
|
|
bodyView.backgroundColor = (close >= open) ? [UIColor greenColor] : [UIColor redColor];
|
|
bodyView.frame = CGRectMake(
|
|
xPosition,
|
|
MIN(yOpen, yClose),
|
|
kLineWidth,
|
|
MAX(1.0, fabs(yClose - yOpen))
|
|
);
|
|
[self.kLineScrollView addSubview:bodyView];
|
|
|
|
// 绘制影线
|
|
UIView *shadowView = [[UIView alloc] init];
|
|
shadowView.backgroundColor = bodyView.backgroundColor;
|
|
shadowView.frame = CGRectMake(
|
|
bodyView.center.x - 0.5,
|
|
yHigh,
|
|
1,
|
|
yLow - yHigh
|
|
);
|
|
[self.kLineScrollView addSubview:shadowView];
|
|
}
|
|
|
|
// 日期刻度
|
|
for (UILabel *label in self.dateLabels) {
|
|
[label removeFromSuperview];
|
|
}
|
|
|
|
NSMutableArray *dateLabelArr = [NSMutableArray array];
|
|
NSInteger dateInterval = 10;
|
|
|
|
for (NSInteger i = 0; i < self.kLineData.count; i += dateInterval) {
|
|
NSString *dateStr = @"";
|
|
if ([self.kLineData[i] isKindOfClass:[StockKLineModel class]]) {
|
|
StockKLineModel *model = (StockKLineModel *)self.kLineData[i];
|
|
dateStr = model.date;
|
|
} else if ([self.kLineData[i] isKindOfClass:[NSDictionary class]]) {
|
|
NSDictionary *dict = (NSDictionary *)self.kLineData[i];
|
|
dateStr = dict[@"date"];
|
|
}
|
|
UILabel *label = [[UILabel alloc] init];
|
|
label.text = dateStr;
|
|
label.textColor = [UIColor lightGrayColor];
|
|
label.font = [UIFont systemFontOfSize:10];
|
|
label.translatesAutoresizingMaskIntoConstraints = YES;
|
|
|
|
CGFloat xCenter = kLineUnitWidth * (i + 0.5);
|
|
[label sizeToFit];
|
|
|
|
label.frame = CGRectMake(
|
|
xCenter - label.bounds.size.width / 2,
|
|
chartHeight - 15,
|
|
label.bounds.size.width,
|
|
label.bounds.size.height
|
|
);
|
|
|
|
[self.kLineScrollView addSubview:label];
|
|
[dateLabelArr addObject:label];
|
|
}
|
|
self.dateLabels = dateLabelArr;
|
|
|
|
NSLog(@"ScrollView Bounds Width: %f", self.kLineScrollView.bounds.size.width);
|
|
NSLog(@"Content Size Width: %f", self.kLineScrollView.contentSize.width);
|
|
|
|
if (self.kLineScrollView.contentSize.width <= self.kLineScrollView.bounds.size.width) {
|
|
NSLog(@"⚠️ 警告:ContentSize 不够大,无法滑动。");
|
|
}
|
|
}
|
|
|
|
#pragma mark - Mock Data
|
|
- (void) setupData{
|
|
self.kLineData = @[
|
|
@{@"date":@"2025/11/18", @"open":@1620.0, @"high":@1643.0, @"low":@1605.0, @"close":@1630.0, @"volume":@2.5},
|
|
@{@"date":@"2025/11/19", @"open":@1630.0, @"high":@1635.0, @"low":@1615.0, @"close":@1620.0, @"volume":@2.2},
|
|
@{@"date":@"2025/11/20", @"open":@1620.0, @"high":@1628.0, @"low":@1608.0, @"close":@1615.0, @"volume":@1.8},
|
|
@{@"date":@"2025/11/21", @"open":@1615.0, @"high":@1625.0, @"low":@1610.0, @"close":@1622.0, @"volume":@2.0},
|
|
@{@"date":@"2025/11/22", @"open":@1622.0, @"high":@1630.0, @"low":@1618.0, @"close":@1625.0, @"volume":@2.3},
|
|
@{@"date":@"2025/11/25", @"open":@1625.0, @"high":@1632.0, @"low":@1620.0, @"close":@1628.0, @"volume":@1.9},
|
|
@{@"date":@"2025/11/26", @"open":@1628.0, @"high":@1635.0, @"low":@1622.0, @"close":@1630.0, @"volume":@2.1},
|
|
@{@"date":@"2025/11/27", @"open":@1630.0, @"high":@1638.0, @"low":@1625.0, @"close":@1628.0, @"volume":@2.4},
|
|
@{@"date":@"2025/11/28", @"open":@1628.0, @"high":@1632.0, @"low":@1609.0, @"close":@1610.0, @"volume":@2.6},
|
|
@{@"date":@"2025/11/29", @"open":@1610.0, @"high":@1620.0, @"low":@1608.0, @"close":@1615.0, @"volume":@1.7}
|
|
];
|
|
}
|
|
|
|
- (void)generateMockData {
|
|
NSMutableArray *arr = [NSMutableArray array];
|
|
CGFloat lastClose = 100.0;
|
|
NSLog(@"生成模拟数据中。。。");
|
|
|
|
// 生成200条数据(减少数量,避免contentSize过大)
|
|
for (int i = 0; i < 200; i++) {
|
|
StockKLineModel *model = [[StockKLineModel alloc] init];
|
|
|
|
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-(200 - i) * 24 * 3600];
|
|
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
|
|
[fmt setDateFormat:@"MM-dd"];
|
|
model.date = [fmt stringFromDate:date];
|
|
|
|
// 价格生成逻辑
|
|
CGFloat volatility = lastClose * 0.02;
|
|
CGFloat randomChange = ((arc4random() % 100) / 100.0 - 0.5) * 2 * volatility;
|
|
|
|
model.open = lastClose + ((arc4random() % 100) / 100.0 - 0.5) * volatility * 0.5;
|
|
model.close = model.open + randomChange;
|
|
|
|
CGFloat maxOC = MAX(model.open, model.close);
|
|
CGFloat minOC = MIN(model.open, model.close);
|
|
|
|
model.high = maxOC + (arc4random() % 100) / 100.0 * 1.0;
|
|
model.low = minOC - (arc4random() % 100) / 100.0 * 1.0;
|
|
|
|
// 成交量生成逻辑
|
|
CGFloat baseVolume = 1000000.0;
|
|
CGFloat randomVolFactor = (arc4random() % 200) / 100.0 + 0.5;
|
|
|
|
if (fabs(model.close - model.open) / model.open > 0.03) {
|
|
randomVolFactor *= 1.5;
|
|
}
|
|
|
|
model.volume = baseVolume * randomVolFactor;
|
|
|
|
if (model.low < 0) model.low = 0.01;
|
|
|
|
[arr addObject:model];
|
|
|
|
lastClose = model.close;
|
|
}
|
|
|
|
NSLog(@"生成模拟数据完毕,共%ld条", arr.count);
|
|
|
|
self.kLineData = arr;
|
|
}
|
|
|
|
@end
|