// // DemoViewController.m // NewHttpDNSTestDemo // #import "DemoViewController.h" #import "DemoResolveModel.h" #import "DemoLogViewController.h" #import "DemoHttpdnsScenario.h" @interface DemoViewController () @property (nonatomic, strong) DemoHttpdnsScenario *scenario; @property (nonatomic, strong) DemoHttpdnsScenarioConfig *scenarioConfig; @property (nonatomic, strong) DemoResolveModel *model; @property (nonatomic, strong) UIScrollView *scrollView; @property (nonatomic, strong) UIStackView *stack; @property (nonatomic, strong) UITextField *hostField; @property (nonatomic, strong) UISegmentedControl *queryTypeSeg; @property (nonatomic, strong) UILabel *elapsedLabel; @property (nonatomic, strong) UILabel *ttlLabel; @property (nonatomic, strong) UITextView *resultTextView; @property (nonatomic, weak) DemoLogViewController *presentedLogVC; @end @implementation DemoViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"HTTPDNS Demo"; self.view.backgroundColor = [UIColor systemBackgroundColor]; self.scenario = [[DemoHttpdnsScenario alloc] initWithDelegate:self]; self.model = self.scenario.model; self.scenarioConfig = [[DemoHttpdnsScenarioConfig alloc] init]; [self buildUI]; [self reloadUIFromModel:self.model]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)]; } - (void)buildUI { self.scrollView = [[UIScrollView alloc] init]; self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.scrollView]; [NSLayoutConstraint activateConstraints:@[ [self.scrollView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], [self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], [self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], [self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor] ]]; self.stack = [[UIStackView alloc] init]; self.stack.axis = UILayoutConstraintAxisVertical; self.stack.spacing = 12.0; self.stack.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:self.stack]; [NSLayoutConstraint activateConstraints:@[ [self.stack.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor constant:16], [self.stack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16], [self.stack.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16], [self.stack.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor constant:-16], [self.stack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-32] ]]; // Host 输入 UIStackView *row1 = [self labeledRow:@"Host"]; self.hostField = [[UITextField alloc] init]; self.hostField.placeholder = @"demodemo.cloudxdr.com"; self.hostField.text = self.scenarioConfig.host; self.hostField.borderStyle = UITextBorderStyleRoundedRect; [row1 addArrangedSubview:self.hostField]; [self.stack addArrangedSubview:row1]; // 查询类型 UIStackView *row2 = [self labeledRow:@"类型"]; self.queryTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"A (IPv4)", @"AAAA (IPv6)", @"both"]]; self.queryTypeSeg.selectedSegmentIndex = 0; [self.queryTypeSeg addTarget:self action:@selector(onQueryTypeChanged:) forControlEvents:UIControlEventValueChanged]; [row2 addArrangedSubview:self.queryTypeSeg]; [self.stack addArrangedSubview:row2]; // 解析按钮 UIButton *btnResolve = [self filledButton:@"Resolve" action:@selector(onResolve)]; [self.stack addArrangedSubview:btnResolve]; // 耗时 / TTL UIStackView *info = [self labeledRow:@"Info"]; self.elapsedLabel = [self monoLabel:@"elapsed: - ms"]; self.ttlLabel = [self monoLabel:@"ttl: -"]; [info addArrangedSubview:self.elapsedLabel]; [info addArrangedSubview:self.ttlLabel]; [self.stack addArrangedSubview:info]; // 结果 UILabel *resultTitle = [[UILabel alloc] init]; resultTitle.text = @"结果"; resultTitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; [self.stack addArrangedSubview:resultTitle]; self.resultTextView = [[UITextView alloc] init]; self.resultTextView.translatesAutoresizingMaskIntoConstraints = NO; self.resultTextView.editable = NO; if (@available(iOS 13.0, *)) { self.resultTextView.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular]; self.resultTextView.textColor = [UIColor labelColor]; } self.resultTextView.backgroundColor = [UIColor clearColor]; self.resultTextView.textContainerInset = UIEdgeInsetsMake(8, 12, 8, 12); [self.stack addArrangedSubview:self.resultTextView]; [self.resultTextView.heightAnchor constraintEqualToConstant:320].active = YES; } #pragma mark - Actions - (void)onQueryTypeChanged:(UISegmentedControl *)seg { NSArray *types = @[@"A", @"AAAA", @"both"]; self.scenarioConfig.queryType = types[seg.selectedSegmentIndex]; [self.scenario applyConfig:self.scenarioConfig]; } - (void)onResolve { [self.view endEditing:YES]; NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"demodemo.cloudxdr.com"; self.scenarioConfig.host = host; [self.scenario applyConfig:self.scenarioConfig]; [self.scenario resolve]; } - (void)onShowLog { DemoLogViewController *logVC = [DemoLogViewController new]; [logVC setInitialText:[self.scenario logSnapshot]]; self.presentedLogVC = logVC; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:logVC]; nav.modalPresentationStyle = UIModalPresentationAutomatic; [self presentViewController:nav animated:YES completion:nil]; } - (void)reloadUIFromModel:(DemoResolveModel *)model { self.model = model; if (![self.hostField isFirstResponder]) { self.hostField.text = model.host; } self.elapsedLabel.text = [NSString stringWithFormat:@"elapsed: %.0f ms", model.elapsedMs]; self.ttlLabel.text = [NSString stringWithFormat:@"ttl v4/v6: %.0f/%.0f s", model.ttlV4, model.ttlV6]; self.resultTextView.text = [self buildResultText:model]; } - (NSString *)buildResultText:(DemoResolveModel *)model { if (model.error != nil) { return [NSString stringWithFormat:@"Error:\n%@", model.error.localizedDescription]; } NSDictionary *dict = @{ @"host": model.host ?: @"", @"elapsed": [NSString stringWithFormat:@"%.0f ms", model.elapsedMs], @"ttl": @{ @"v4": @(model.ttlV4), @"v6": @(model.ttlV6) }, @"ipv4": model.ipv4s ?: @[], @"ipv6": model.ipv6s ?: @[] }; NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil]; if (data == nil) return @"{}"; return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } #pragma mark - Helpers - (UIStackView *)labeledRow:(NSString *)title { UIStackView *row = [[UIStackView alloc] init]; row.axis = UILayoutConstraintAxisHorizontal; row.alignment = UIStackViewAlignmentCenter; row.spacing = 8.0; UILabel *label = [[UILabel alloc] init]; label.text = title; [label.widthAnchor constraintGreaterThanOrEqualToConstant:64].active = YES; [row addArrangedSubview:label]; return row; } - (UILabel *)monoLabel:(NSString *)text { UILabel *l = [[UILabel alloc] init]; l.text = text; if (@available(iOS 13.0, *)) { l.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular]; } return l; } - (UIButton *)filledButton:(NSString *)title action:(SEL)sel { UIButton *b = [UIButton buttonWithType:UIButtonTypeSystem]; [b setTitle:title forState:UIControlStateNormal]; b.backgroundColor = [UIColor systemBlueColor]; [b setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; b.layer.cornerRadius = 8; [b.heightAnchor constraintEqualToConstant:44].active = YES; [b addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside]; return b; } #pragma mark - DemoHttpdnsScenarioDelegate - (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model { [self reloadUIFromModel:model]; } - (void)scenario:(DemoHttpdnsScenario *)scenario didAppendLogLine:(NSString *)line { if (self.presentedLogVC != nil) { [self.presentedLogVC appendLine:line]; } } @end