// // KBPersonInfoVC.m // keyBoard // // Created by Mac on 2025/11/11. // 个人资料 // #import "KBPersonInfoVC.h" #import #import "KBPersonInfoItemCell.h" #import #import "LSTPopView.h" #import "KBChangeNicknamePopView.h" #import "KBGenderPickerPopView.h" #import "KBMyVM.h" #import "KBAlert.h" #import "KBCancelAccountVC.h" #import "KBLocalizationManager.h" #import "KBConfig.h" #import "KBSkinInstallBridge.h" #import "KBInputProfileManager.h" static NSInteger const kKBPersonInfoRowNickname = 0; static NSInteger const kKBPersonInfoRowGender = 1; static NSInteger const kKBPersonInfoRowLanguage = 2; static NSInteger const kKBPersonInfoRowUserID = 3; typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *layoutVariant, NSString *profileId); @interface KBKeyboardLayoutSelectVC : BaseViewController @property (nonatomic, strong) BaseTableView *tableView; @property (nonatomic, strong) UIButton *confirmButton; @property (nonatomic, copy) NSDictionary *languageConfig; @property (nonatomic, copy) NSString *selectedLayoutVariant; @property (nonatomic, copy) NSString *selectedProfileId; @property (nonatomic, copy) NSString *pendingLayoutVariant; @property (nonatomic, copy) NSString *pendingProfileId; @property (nonatomic, copy) KBInputProfileSelectHandler onSelect; @end @implementation KBKeyboardLayoutSelectVC - (void)viewDidLoad { [super viewDidLoad]; /// 1:控件初始化 [self setupUI]; /// 2:初始化选中态 [self prepareDefaultSelection]; } - (void)setupUI { self.kb_titleLabel.text = KBLocalized(@"Choose Layout"); self.view.backgroundColor = [UIColor colorWithHex:0xF8F8F8]; [self.view addSubview:self.tableView]; [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.view); make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT + 10); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-72); }]; [self.view addSubview:self.confirmButton]; [self.confirmButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).offset(16); make.right.equalTo(self.view).offset(-16); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12); make.height.mas_equalTo(50); }]; } - (void)prepareDefaultSelection { NSArray *layouts = [self.languageConfig[@"layouts"] isKindOfClass:NSArray.class] ? self.languageConfig[@"layouts"] : @[]; NSDictionary *matched = nil; for (NSDictionary *layout in layouts) { NSString *variant = [layout[@"variant"] isKindOfClass:NSString.class] ? layout[@"variant"] : @""; if (self.selectedLayoutVariant.length > 0 && [variant isEqualToString:self.selectedLayoutVariant]) { matched = layout; break; } } if (!matched) { matched = layouts.firstObject; } self.pendingLayoutVariant = [matched[@"variant"] isKindOfClass:NSString.class] ? matched[@"variant"] : @""; self.pendingProfileId = [matched[@"profileId"] isKindOfClass:NSString.class] ? matched[@"profileId"] : @""; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSArray *layouts = [self.languageConfig[@"layouts"] isKindOfClass:NSArray.class] ? self.languageConfig[@"layouts"] : @[]; return layouts.count; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 56.0; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cid = @"KBKeyboardLayoutCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cid]; cell.textLabel.font = [KBFont medium:16]; cell.detailTextLabel.font = [KBFont regular:12]; cell.detailTextLabel.textColor = [UIColor colorWithHex:0x999999]; } NSArray *layouts = [self.languageConfig[@"layouts"] isKindOfClass:NSArray.class] ? self.languageConfig[@"layouts"] : @[]; NSDictionary *layout = (indexPath.row < layouts.count) ? layouts[indexPath.row] : @{}; NSString *title = [layout[@"title"] isKindOfClass:NSString.class] ? layout[@"title"] : @""; NSString *variant = [layout[@"variant"] isKindOfClass:NSString.class] ? layout[@"variant"] : @""; NSString *profileId = [layout[@"profileId"] isKindOfClass:NSString.class] ? layout[@"profileId"] : @""; cell.textLabel.text = title; cell.detailTextLabel.text = profileId; BOOL selected = (self.pendingLayoutVariant.length > 0 && [self.pendingLayoutVariant isEqualToString:variant]); cell.accessoryType = selected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; NSArray *layouts = [self.languageConfig[@"layouts"] isKindOfClass:NSArray.class] ? self.languageConfig[@"layouts"] : @[]; NSDictionary *layout = (indexPath.row < layouts.count) ? layouts[indexPath.row] : nil; if (![layout isKindOfClass:NSDictionary.class]) { return; } self.pendingLayoutVariant = [layout[@"variant"] isKindOfClass:NSString.class] ? layout[@"variant"] : @""; self.pendingProfileId = [layout[@"profileId"] isKindOfClass:NSString.class] ? layout[@"profileId"] : @""; [self.tableView reloadData]; } - (void)onTapConfirm { NSString *code = [self.languageConfig[@"code"] isKindOfClass:NSString.class] ? self.languageConfig[@"code"] : KBLanguageCodeEnglish; if (self.onSelect && code.length > 0 && self.pendingLayoutVariant.length > 0 && self.pendingProfileId.length > 0) { self.onSelect(code, self.pendingLayoutVariant, self.pendingProfileId); } } - (BaseTableView *)tableView { if (!_tableView) { _tableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _tableView.backgroundColor = UIColor.clearColor; _tableView.dataSource = self; _tableView.delegate = self; _tableView.separatorInset = UIEdgeInsetsMake(0, 16, 0, 16); } return _tableView; } - (UIButton *)confirmButton { if (!_confirmButton) { _confirmButton = [UIButton buttonWithType:UIButtonTypeSystem]; [_confirmButton setTitle:KBLocalized(@"Confirm") forState:UIControlStateNormal]; [_confirmButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; _confirmButton.titleLabel.font = [KBFont medium:16]; _confirmButton.backgroundColor = [UIColor colorWithHex:0x111111]; _confirmButton.layer.cornerRadius = 12; _confirmButton.layer.masksToBounds = YES; [_confirmButton addTarget:self action:@selector(onTapConfirm) forControlEvents:UIControlEventTouchUpInside]; } return _confirmButton; } @end @interface KBKeyboardLanguageSelectVC : BaseViewController @property (nonatomic, strong) BaseTableView *tableView; @property (nonatomic, strong) UIButton *confirmButton; @property (nonatomic, copy) NSArray *languageConfigs; @property (nonatomic, copy) NSString *selectedLanguageCode; @property (nonatomic, copy) NSString *selectedLayoutVariant; @property (nonatomic, copy) NSString *pendingLanguageCode; @property (nonatomic, copy) NSString *pendingLayoutVariant; @property (nonatomic, copy) NSString *pendingProfileId; @property (nonatomic, copy) KBInputProfileSelectHandler onSelect; @end @implementation KBKeyboardLanguageSelectVC - (void)viewDidLoad { [super viewDidLoad]; /// 1:控件初始化 [self setupUI]; /// 2:初始化选中态 [self prepareDefaultSelection]; [self updateConfirmVisibility]; } - (void)setupUI { self.kb_titleLabel.text = KBLocalized(@"Input Language"); self.view.backgroundColor = [UIColor colorWithHex:0xF8F8F8]; [self.view addSubview:self.tableView]; [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.view); make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT + 10); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-72); }]; [self.view addSubview:self.confirmButton]; [self.confirmButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).offset(16); make.right.equalTo(self.view).offset(-16); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12); make.height.mas_equalTo(50); }]; } - (void)prepareDefaultSelection { NSDictionary *config = [self languageConfigForCode:self.selectedLanguageCode]; if (!config) { config = self.languageConfigs.firstObject; } NSString *code = [config[@"code"] isKindOfClass:NSString.class] ? config[@"code"] : KBLanguageCodeEnglish; NSArray *layouts = [config[@"layouts"] isKindOfClass:NSArray.class] ? config[@"layouts"] : @[]; NSDictionary *layoutMatched = nil; for (NSDictionary *layout in layouts) { NSString *variant = [layout[@"variant"] isKindOfClass:NSString.class] ? layout[@"variant"] : @""; if (self.selectedLayoutVariant.length > 0 && [variant isEqualToString:self.selectedLayoutVariant]) { layoutMatched = layout; break; } } if (!layoutMatched) { layoutMatched = layouts.firstObject; } self.pendingLanguageCode = code; self.pendingLayoutVariant = [layoutMatched[@"variant"] isKindOfClass:NSString.class] ? layoutMatched[@"variant"] : @"qwerty"; self.pendingProfileId = [layoutMatched[@"profileId"] isKindOfClass:NSString.class] ? layoutMatched[@"profileId"] : @"en_US_qwerty"; } - (nullable NSDictionary *)languageConfigForCode:(NSString *)code { for (NSDictionary *item in self.languageConfigs) { NSString *langCode = [item[@"code"] isKindOfClass:NSString.class] ? item[@"code"] : @""; if ([langCode isEqualToString:code]) { return item; } } return nil; } - (void)updateConfirmVisibility { NSDictionary *config = [self languageConfigForCode:self.pendingLanguageCode]; NSArray *layouts = [config[@"layouts"] isKindOfClass:NSArray.class] ? config[@"layouts"] : @[]; BOOL hasMultiLayout = (layouts.count > 1); self.confirmButton.hidden = hasMultiLayout; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.languageConfigs.count; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 56.0; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cid = @"KBKeyboardLanguageCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cid]; cell.textLabel.font = [KBFont medium:16]; cell.detailTextLabel.font = [KBFont regular:12]; cell.detailTextLabel.textColor = [UIColor colorWithHex:0x999999]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } NSDictionary *lang = (indexPath.row < self.languageConfigs.count) ? self.languageConfigs[indexPath.row] : @{}; NSString *name = [lang[@"name"] isKindOfClass:NSString.class] ? lang[@"name"] : @""; NSString *code = [lang[@"code"] isKindOfClass:NSString.class] ? lang[@"code"] : @""; NSArray *layouts = [lang[@"layouts"] isKindOfClass:NSArray.class] ? lang[@"layouts"] : @[]; BOOL multi = layouts.count > 1; BOOL isSelected = (self.pendingLanguageCode.length > 0 && [self.pendingLanguageCode isEqualToString:code]); cell.textLabel.text = name; cell.detailTextLabel.text = multi ? KBLocalized(@"Multiple Keyboard Layouts") : @"QWERTY"; cell.accessoryType = multi ? UITableViewCellAccessoryDisclosureIndicator : (isSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone); return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; NSDictionary *lang = (indexPath.row < self.languageConfigs.count) ? self.languageConfigs[indexPath.row] : nil; if (![lang isKindOfClass:NSDictionary.class]) { return; } NSString *code = [lang[@"code"] isKindOfClass:NSString.class] ? lang[@"code"] : KBLanguageCodeEnglish; NSArray *layouts = [lang[@"layouts"] isKindOfClass:NSArray.class] ? lang[@"layouts"] : @[]; if (layouts.count > 1) { self.pendingLanguageCode = code; [self updateConfirmVisibility]; [self.tableView reloadData]; KBKeyboardLayoutSelectVC *layoutVC = [[KBKeyboardLayoutSelectVC alloc] init]; layoutVC.languageConfig = lang; layoutVC.selectedLayoutVariant = self.selectedLanguageCode.length > 0 && [self.selectedLanguageCode isEqualToString:code] ? self.selectedLayoutVariant : @""; __weak typeof(self) weakSelf = self; layoutVC.onSelect = ^(NSString *languageCode, NSString *layoutVariant, NSString *profileId) { weakSelf.pendingLanguageCode = languageCode; weakSelf.pendingLayoutVariant = layoutVariant; weakSelf.pendingProfileId = profileId; if (weakSelf.onSelect) { weakSelf.onSelect(languageCode, layoutVariant, profileId); } }; [self.navigationController pushViewController:layoutVC animated:YES]; return; } NSDictionary *layout = layouts.firstObject; self.pendingLanguageCode = code; self.pendingLayoutVariant = [layout[@"variant"] isKindOfClass:NSString.class] ? layout[@"variant"] : @"qwerty"; self.pendingProfileId = [layout[@"profileId"] isKindOfClass:NSString.class] ? layout[@"profileId"] : @"en_US_qwerty"; [self updateConfirmVisibility]; [self.tableView reloadData]; } - (BaseTableView *)tableView { if (!_tableView) { _tableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _tableView.backgroundColor = UIColor.clearColor; _tableView.dataSource = self; _tableView.delegate = self; _tableView.separatorInset = UIEdgeInsetsMake(0, 16, 0, 16); } return _tableView; } - (void)onTapConfirm { if (self.confirmButton.hidden) { return; } if (self.onSelect && self.pendingLanguageCode.length > 0 && self.pendingLayoutVariant.length > 0 && self.pendingProfileId.length > 0) { self.onSelect(self.pendingLanguageCode, self.pendingLayoutVariant, self.pendingProfileId); } } - (UIButton *)confirmButton { if (!_confirmButton) { _confirmButton = [UIButton buttonWithType:UIButtonTypeSystem]; [_confirmButton setTitle:KBLocalized(@"Confirm") forState:UIControlStateNormal]; [_confirmButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; _confirmButton.titleLabel.font = [KBFont medium:16]; _confirmButton.backgroundColor = [UIColor colorWithHex:0x111111]; _confirmButton.layer.cornerRadius = 12; _confirmButton.layer.masksToBounds = YES; [_confirmButton addTarget:self action:@selector(onTapConfirm) forControlEvents:UIControlEventTouchUpInside]; } return _confirmButton; } @end @interface KBPersonInfoVC () // 列表 @property (nonatomic, strong) BaseTableView *tableView; // 懒加载 // 头像区(表头) @property (nonatomic, strong) UIView *headerView; @property (nonatomic, strong) UIImageView *avatarView; // 头像 @property (nonatomic, strong) UIButton *editBadge; // 头像右下角的小铅笔 @property (nonatomic, strong) UILabel *modifyLabel; // “Modify” 文案 @property (nonatomic, strong) UILabel *userIdLabel; // 用户 ID @property (nonatomic, strong) UIButton *userIdCopyButton; // 复制用户 ID // 底部退出登录按钮 @property (nonatomic, strong) UIButton *logoutBtn; // 数据 @property (nonatomic, copy) NSArray *items; // {title,value,arrow,copy} // 压缩后的头像 JPEG 数据(可用于上传) @property (nonatomic, strong) NSData *avatarJPEGData; @property (nonatomic, strong) KBMyVM *myVM; @property (nonatomic, strong) KBMyVM *viewModel; // 我的页面 VM @property (nonatomic, strong) KBUser *userModel; @property (nonatomic, copy) NSString *selectedLanguageCode; @property (nonatomic, copy) NSString *selectedLayoutVariant; @property (nonatomic, copy) NSString *selectedProfileId; @end @implementation KBPersonInfoVC - (void)viewDidLoad { [super viewDidLoad]; /// 1:控件初始化 [self setupUI]; /// 2:恢复输入配置 [self restoreInputProfile]; /// 3:加载用户资料 [self loadData]; /// 4:监听语言变化 [self bindNotifications]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)setupUI { self.kb_titleLabel.text = KBLocalized(@"Settings"); // 导航标题 self.kb_navView.backgroundColor = [UIColor clearColor]; self.view.backgroundColor = [UIColor colorWithHex:0xF8F8F8]; [self.view addSubview:self.tableView]; [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.bottom.equalTo(self.view); make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT + 10); }]; // 表头 self.tableView.tableHeaderView = self.headerView; // 底部退出登录按钮 [self.view addSubview:self.logoutBtn]; [self.logoutBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).offset(16); make.right.equalTo(self.view).offset(-16); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12); make.height.mas_equalTo(56); }]; // 列表底部腾出空间,避免被按钮挡住 UIEdgeInsets inset = self.tableView.contentInset; inset.bottom = 56 + 24; // 按钮高度 + 额外间距 self.tableView.contentInset = inset; } - (void)loadData { self.viewModel = [[KBMyVM alloc] init]; __weak typeof(self) weakSelf = self; [self.viewModel fetchUserDetailWithCompletion:^(KBUser * _Nullable user, NSError * _Nullable error) { if (user) { weakSelf.userModel = user; [weakSelf.avatarView kb_setAvatarURL:weakSelf.userModel.avatarUrl placeholder:KBAvatarPlaceholderImage]; weakSelf.modifyLabel.text = weakSelf.userModel.nickName; weakSelf.userIdLabel.text = weakSelf.userModel.userId ?: @""; } [weakSelf rebuildItems]; }]; } - (void)bindNotifications { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleLanguageChanged) name:KBLocalizationDidChangeNotification object:nil]; } - (void)handleLanguageChanged { self.kb_titleLabel.text = KBLocalized(@"Settings"); [self.logoutBtn setTitle:KBLocalized(@"Log Out") forState:UIControlStateNormal]; [self rebuildItems]; } - (void)rebuildItems { NSString *nickname = self.userModel.nickName ?: @""; NSString *genderText = [self kb_genderDisplayText]; NSString *languageText = [self currentInputProfileDisplayText]; self.items = @[ @{ @"title": KBLocalized(@"Nickname"), @"value": nickname, @"arrow": @YES, @"copy": @NO }, @{ @"title": KBLocalized(@"Gender"), @"value": genderText, @"arrow": @YES, @"copy": @NO }, @{ @"title": KBLocalized(@"Input Language"), @"value": languageText, @"arrow": @YES, @"copy": @NO }, ]; [self.tableView reloadData]; } /// 根据 userModel.gender 生成展示用的性别文案(带多语言) - (NSString *)kb_genderDisplayText { if (!self.userModel) { return KBLocalized(@"Choose"); } switch (self.userModel.gender) { case UserSexMan: return KBLocalized(@"Male"); case UserSexWeman: return KBLocalized(@"Female"); case UserSexTwoSex: return KBLocalized(@"The Third Gender"); default: return KBLocalized(@"Choose"); } } - (void)restoreInputProfile { NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; NSString *savedCode = [shared stringForKey:AppGroup_SelectedKeyboardLanguageCode]; NSString *savedVariant = [shared stringForKey:AppGroup_SelectedKeyboardLayoutVariant]; NSString *savedProfile = [shared stringForKey:AppGroup_SelectedKeyboardProfileId]; NSString *currentCode = [KBLocalizationManager shared].currentLanguageCode ?: KBLanguageCodeEnglish; NSDictionary *config = [self languageConfigForCode:(savedCode.length ? savedCode : currentCode)]; if (!config) { config = [self languageConfigForCode:KBLanguageCodeEnglish]; } NSArray *layouts = [config[@"layouts"] isKindOfClass:NSArray.class] ? config[@"layouts"] : @[]; NSDictionary *layout = nil; for (NSDictionary *item in layouts) { NSString *variant = [item[@"variant"] isKindOfClass:NSString.class] ? item[@"variant"] : @""; if (savedVariant.length > 0 && [variant isEqualToString:savedVariant]) { layout = item; break; } } if (!layout) { layout = layouts.firstObject; } self.selectedLanguageCode = [config[@"code"] isKindOfClass:NSString.class] ? config[@"code"] : KBLanguageCodeEnglish; self.selectedLayoutVariant = [layout[@"variant"] isKindOfClass:NSString.class] ? layout[@"variant"] : @"qwerty"; self.selectedProfileId = savedProfile.length > 0 ? savedProfile : ([layout[@"profileId"] isKindOfClass:NSString.class] ? layout[@"profileId"] : @"en_US_qwerty"); } - (NSArray *)languageConfigs { NSArray *profiles = [[KBInputProfileManager sharedManager] allProfiles]; NSMutableArray *configs = [NSMutableArray array]; for (KBInputProfile *profile in profiles) { NSMutableArray *layouts = [NSMutableArray array]; for (KBInputProfileLayout *layout in profile.layouts) { [layouts addObject:@{ @"variant": layout.variant, @"title": layout.title, @"profileId": layout.profileId }]; } // STTODO 繁体拼音下个版本更新 if ([profile.code isEqualToString:@"zh-Hant-Pinyin"]) { // NSLog(@"==="); continue; } [configs addObject:@{ @"code": profile.code, @"name": profile.name, @"layouts": [layouts copy] }]; } return [configs copy]; } - (nullable NSDictionary *)languageConfigForCode:(NSString *)languageCode { for (NSDictionary *item in [self languageConfigs]) { NSString *code = [item[@"code"] isKindOfClass:NSString.class] ? item[@"code"] : @""; if ([code isEqualToString:languageCode]) { return item; } } return nil; } - (NSString *)layoutTitleForLanguageCode:(NSString *)languageCode variant:(NSString *)variant { NSDictionary *config = [self languageConfigForCode:languageCode]; NSArray *layouts = [config[@"layouts"] isKindOfClass:NSArray.class] ? config[@"layouts"] : @[]; for (NSDictionary *layout in layouts) { NSString *v = [layout[@"variant"] isKindOfClass:NSString.class] ? layout[@"variant"] : @""; if ([v isEqualToString:variant]) { return [layout[@"title"] isKindOfClass:NSString.class] ? layout[@"title"] : @""; } } return @""; } - (NSString *)currentInputProfileDisplayText { NSDictionary *config = [self languageConfigForCode:self.selectedLanguageCode ?: KBLanguageCodeEnglish]; NSString *languageName = [config[@"name"] isKindOfClass:NSString.class] ? config[@"name"] : @"English"; if ([self.selectedLanguageCode isEqualToString:KBLanguageCodeSpanish]) { languageName = @"Español"; } NSString *layoutTitle = [self layoutTitleForLanguageCode:self.selectedLanguageCode variant:self.selectedLayoutVariant]; if (layoutTitle.length == 0) { return languageName; } NSString *variant = self.selectedLayoutVariant ?: @""; if ([variant.lowercaseString isEqualToString:@"qwerty"] || [layoutTitle.lowercaseString isEqualToString:@"qwerty"]) { return languageName; } return [NSString stringWithFormat:@"%@ · %@", languageName, layoutTitle]; } - (void)openLanguageSelector { KBKeyboardLanguageSelectVC *vc = [[KBKeyboardLanguageSelectVC alloc] init]; vc.languageConfigs = [self languageConfigs]; vc.selectedLanguageCode = self.selectedLanguageCode ?: KBLanguageCodeEnglish; vc.selectedLayoutVariant = self.selectedLayoutVariant ?: @"qwerty"; __weak typeof(self) weakSelf = self; vc.onSelect = ^(NSString *languageCode, NSString *layoutVariant, NSString *profileId) { [weakSelf applyInputProfileWithLanguageCode:languageCode layoutVariant:layoutVariant profileId:profileId completion:^(BOOL success) { if (success) { [weakSelf.navigationController popToViewController:weakSelf animated:YES]; } }]; }; [self.navigationController pushViewController:vc animated:YES]; } - (void)applyInputProfileWithLanguageCode:(NSString *)languageCode layoutVariant:(NSString *)layoutVariant profileId:(NSString *)profileId completion:(void(^ _Nullable)(BOOL success))completion { if (languageCode.length == 0 || layoutVariant.length == 0 || profileId.length == 0) { if (completion) { completion(NO); } return; } KBInputProfile *profile = [[KBInputProfileManager sharedManager] profileForLanguageCode:languageCode]; NSString *zipName = profile.defaultSkinZip; if (zipName.length == 0) { NSLog(@"[KBPersonInfoVC] No defaultSkinZip configured for %@, skipping skin installation", languageCode); [self commitInputProfileSwitchWithLanguageCode:languageCode layoutVariant:layoutVariant profileId:profileId]; if (completion) { completion(YES); } return; } __weak typeof(self) weakSelf = self; [self installDefaultSkinForLanguageCode:languageCode completion:^(BOOL skinOK) { if (!skinOK) { [KBHUD showInfo:KBLocalized(@"Default skin install failed. Please check skin resource configuration.")]; if (completion) { completion(NO); } return; } [weakSelf commitInputProfileSwitchWithLanguageCode:languageCode layoutVariant:layoutVariant profileId:profileId]; if (completion) { completion(YES); } }]; } - (void)commitInputProfileSwitchWithLanguageCode:(NSString *)languageCode layoutVariant:(NSString *)layoutVariant profileId:(NSString *)profileId { self.selectedLanguageCode = languageCode; self.selectedLayoutVariant = layoutVariant; self.selectedProfileId = profileId; [self rebuildItems]; NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; [shared setObject:languageCode forKey:AppGroup_SelectedKeyboardLanguageCode]; [shared setObject:layoutVariant forKey:AppGroup_SelectedKeyboardLayoutVariant]; [shared setObject:profileId forKey:AppGroup_SelectedKeyboardProfileId]; [shared setBool:YES forKey:AppGroup_DidUserSelectKeyboardProfile]; [shared synchronize]; [[KBLocalizationManager shared] setCurrentLanguageCode:languageCode persist:YES]; NSString *message = KBLocalized(@"Changing language will reload the Home screen."); [KBHUD showInfo:message]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.35 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ id appDelegate = UIApplication.sharedApplication.delegate; if ([appDelegate respondsToSelector:@selector(setupRootVC)]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [appDelegate performSelector:@selector(setupRootVC)]; #pragma clang diagnostic pop } }); } - (void)installDefaultSkinForLanguageCode:(NSString *)languageCode completion:(void(^)(BOOL success))completion { KBInputProfile *profile = [[KBInputProfileManager sharedManager] profileForLanguageCode:languageCode]; NSString *zipName = profile.defaultSkinZip; if (zipName.length == 0) { if (completion) { completion(NO); } return; } NSString *skinId = [NSString stringWithFormat:@"bundle_skin_default_%@", languageCode]; NSDictionary *iconShortNames = [KBSkinInstallBridge iconShortNamesForLanguageCode:languageCode]; NSLog(@"[KBPersonInfoVC] Installing skin %@ with %lu icon mappings for language %@", skinId, (unsigned long)iconShortNames.count, languageCode); [KBSkinInstallBridge publishBundleSkinRequestWithId:skinId name:skinId zipName:zipName iconShortNames:iconShortNames]; [KBSkinInstallBridge consumePendingRequestFromBundle:NSBundle.mainBundle completion:^(BOOL success, NSError * _Nullable error) { if (!success && error) { NSLog(@"[KBPersonInfoVC] default skin install failed: %@", error); } else if (success) { NSLog(@"[KBPersonInfoVC] default skin installed: %@", skinId); } if (completion) { completion(success); } }]; } #pragma mark - UITableView - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 2; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return section == 0 ? self.items.count : 1; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 56.0; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return section == 0 ? 12.0 : 15.0; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { return [UIView new]; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 0.01; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cid = @"KBPersonInfoItemCell"; KBPersonInfoItemCell *cell = [tableView dequeueReusableCellWithIdentifier:cid]; if (!cell) { cell = [[KBPersonInfoItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cid]; } if (indexPath.section == 0) { NSDictionary *it = self.items[indexPath.row]; BOOL isTop = (indexPath.row == 0); BOOL isBottom = (indexPath.row == self.items.count - 1); [cell configWithTitle:it[@"title"] value:it[@"value"] showArrow:[it[@"arrow"] boolValue] showCopy:[it[@"copy"] boolValue] isTop:isTop isBottom:isBottom]; } else { [cell configWithTitle:KBLocalized(@"Cancel Account") value:@"" showArrow:YES showCopy:NO isTop:YES isBottom:YES]; } return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 1) { KBCancelAccountVC *vc = [[KBCancelAccountVC alloc] init]; [self.navigationController pushViewController:vc animated:YES]; return; } if (indexPath.row == kKBPersonInfoRowNickname) { // 昵称编辑 -> 弹窗 CGFloat width = KB_SCREEN_WIDTH; KBChangeNicknamePopView *content = [[KBChangeNicknamePopView alloc] initWithFrame:CGRectMake(0, 0, width, 230)]; content.prefillNickname = self.items.firstObject[@"value"] ?: @""; LSTPopView *pop = [LSTPopView initWithCustomView:content parentView:self.view popStyle:LSTPopStyleSmoothFromBottom dismissStyle:LSTDismissStyleSmoothToBottom]; pop.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; pop.hemStyle = LSTHemStyleBottom; // 居中 pop.isClickBgDismiss = YES; // 点击背景关闭 pop.isAvoidKeyboard = YES; // 规避键盘 pop.avoidKeyboardSpace = 10; __weak typeof(self) weakSelf = self; __weak typeof(pop) weakPop = pop; content.closeHandler = ^{ [weakPop dismiss]; }; content.saveHandler = ^(NSString *nickname) { if (nickname.length > 0) { // 更新本地模型,避免返回再进入还是旧数据 weakSelf.userModel.nickName = nickname; weakSelf.modifyLabel.text = nickname; [weakSelf rebuildItems]; // 将修改后的用户信息同步到服务端 [weakSelf.myVM updateUserInfo:weakSelf.userModel completion:^(BOOL success, NSError * _Nullable error) { if (error) { [KBHUD showError:error.localizedDescription ?: KBLocalized(@"Network error")]; } }]; } [weakPop dismiss]; }; [pop pop]; // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [content focusInput]; }); } else if (indexPath.row == kKBPersonInfoRowGender) { // 性别选择 -> 弹窗(性别文案支持多语言) NSArray *genders = @[ @{@"id":@"1",@"name":KBLocalized(@"Male")}, @{@"id":@"2",@"name":KBLocalized(@"Female")}, @{@"id":@"3",@"name":KBLocalized(@"The Third Gender")}, ]; CGFloat width = KB_SCREEN_WIDTH; KBGenderPickerPopView *content = [[KBGenderPickerPopView alloc] initWithFrame:CGRectMake(0, 0, width, 300)]; content.items = genders; // 取当前展示值对应的 id(如果有的话) NSString *curName = self.items[kKBPersonInfoRowGender][@"value"]; NSString *selId = nil; for (NSDictionary *d in genders) { if ([d[@"name"] isEqualToString:curName]) { selId = d[@"id"]; break; } } content.selectedId = selId; LSTPopView *pop = [LSTPopView initWithCustomView:content parentView:nil popStyle:LSTPopStyleSmoothFromBottom dismissStyle:LSTDismissStyleSmoothToBottom]; pop.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; pop.hemStyle = LSTHemStyleBottom; pop.isClickBgDismiss = YES; __weak typeof(self) weakSelf = self; __weak typeof(pop) weakPop = pop; content.closeHandler = ^{ [weakPop dismiss]; }; content.saveHandler = ^(NSDictionary *selected) { NSString *name = selected[@"name"] ?: @""; // 将选择结果同步到本地缓存,供后续登录接口使用 NSString *genderId = selected[@"id"]; NSInteger genderValue = 0; if ([genderId isKindOfClass:NSString.class]) { NSInteger v = [genderId integerValue]; // 后端/模型使用 0/1/2,对应弹窗里的 1/2/3 if (v >= 1 && v <= 3) { genderValue = v - 1; } } [[NSUserDefaults standardUserDefaults] setInteger:genderValue forKey:KBSexSelectedGenderKey]; [[NSUserDefaults standardUserDefaults] synchronize]; // 同步更新本地 userModel,避免再次进入页面还是旧的性别 weakSelf.userModel.gender = (UserSex)genderValue; [weakSelf rebuildItems]; // 将修改后的用户信息同步到服务端 [weakSelf.myVM updateUserInfo:weakSelf.userModel completion:^(BOOL success, NSError * _Nullable error) { if (error) { [KBHUD showError:error.localizedDescription ?: KBLocalized(@"Network error")]; } }]; [weakPop dismiss]; }; [pop pop]; } else if (indexPath.row == kKBPersonInfoRowLanguage) { [self openLanguageSelector]; } else if (indexPath.row == kKBPersonInfoRowUserID) { NSString *userID = self.items[kKBPersonInfoRowUserID][@"value"]; if (userID.length == 0) return; UIPasteboard.generalPasteboard.string = userID; [KBHUD showInfo:KBLocalized(@"Copy Success")]; } } #pragma mark - Actions - (void)onTapAvatarEdit { [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_avatar_edit" pageId:@"person_info" elementId:@"avatar_edit" extra:nil completion:nil]; [self presentImagePicker]; } - (void)onTapLogout { [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_logout_btn" pageId:@"person_info" elementId:@"logout_btn" extra:nil completion:nil]; [self.myVM logout]; } - (void)onTapUserIdCopy { NSString *userId = self.userModel.userId ?: @""; if (userId.length == 0) { return; } UIPasteboard.generalPasteboard.string = userId; [KBHUD showInfo:KBLocalized(@"Copied")]; } #pragma mark - Lazy UI(懒加载) - (UITableView *)tableView { if (!_tableView) { // 使用 Plain,自行实现卡片留白与圆角,更贴近设计图 _tableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _tableView.backgroundColor = UIColor.clearColor; _tableView.showsVerticalScrollIndicator = NO; _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // 自定义分割 _tableView.dataSource = self; _tableView.delegate = self; _tableView.contentInset = UIEdgeInsetsMake(8, 0, 16, 0); } return _tableView; } - (UIView *)headerView { if (!_headerView) { CGFloat w = UIScreen.mainScreen.bounds.size.width; UIView *hv = [[UIView alloc] initWithFrame:CGRectMake(0, 0, w, 200)]; hv.backgroundColor = UIColor.clearColor; [hv addSubview:self.avatarView]; [hv addSubview:self.editBadge]; [hv addSubview:self.modifyLabel]; [hv addSubview:self.userIdLabel]; [hv addSubview:self.userIdCopyButton]; [self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(hv); make.top.equalTo(hv).offset(12); make.width.height.mas_equalTo(96); }]; [self.editBadge mas_makeConstraints:^(MASConstraintMaker *make) { make.width.height.mas_equalTo(24); make.centerX.equalTo(self.avatarView.mas_right).offset(-15); make.centerY.equalTo(self.avatarView.mas_bottom).offset(-15); }]; [self.modifyLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.avatarView.mas_bottom).offset(10); make.centerX.equalTo(hv); }]; [self.userIdLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.modifyLabel.mas_bottom).offset(10); make.centerX.equalTo(hv).offset(-KBFit(12.0)); }]; [self.userIdCopyButton mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(self.userIdLabel); make.left.equalTo(self.userIdLabel.mas_right).offset(6); make.width.height.mas_equalTo(18); }]; // 头像可点击:弹系统相册 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapAvatarEdit)]; [self.avatarView addGestureRecognizer:tap]; _headerView = hv; } return _headerView; } - (UIImageView *)avatarView { if (!_avatarView) { _avatarView = [[UIImageView alloc] init]; _avatarView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0]; _avatarView.layer.cornerRadius = 48; _avatarView.layer.masksToBounds = YES; // 外白描边 _avatarView.layer.borderWidth = 3.0; _avatarView.layer.borderColor = UIColor.whiteColor.CGColor; // 占位图 UIGraphicsBeginImageContextWithOptions(CGSizeMake(96, 96), NO, 0); CGContextRef ctx = UIGraphicsGetCurrentContext(); [[UIColor colorWithRed:0.86 green:0.95 blue:0.90 alpha:1] setFill]; CGContextFillEllipseInRect(ctx, CGRectMake(0, 0, 96, 96)); UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); _avatarView.image = img; _avatarView.userInteractionEnabled = YES; } return _avatarView; } - (UIButton *)editBadge { if (!_editBadge) { _editBadge = [UIButton buttonWithType:UIButtonTypeCustom]; _editBadge.layer.cornerRadius = 12; _editBadge.layer.masksToBounds = YES; UIImage *img = [UIImage imageNamed:@"myperson_edit_icon"]; [_editBadge setImage:img forState:UIControlStateNormal]; [_editBadge addTarget:self action:@selector(onTapAvatarEdit) forControlEvents:UIControlEventTouchUpInside]; } return _editBadge; } - (UILabel *)modifyLabel { if (!_modifyLabel) { _modifyLabel = [UILabel new]; _modifyLabel.text = @"Modify"; _modifyLabel.textColor = [UIColor colorWithHex:KBBlackValue]; _modifyLabel.font = [KBFont bold:18]; } return _modifyLabel; } - (UILabel *)userIdLabel { if (!_userIdLabel) { _userIdLabel = [UILabel new]; _userIdLabel.textColor = [UIColor colorWithHex:0x9B9B9B]; _userIdLabel.font = [KBFont regular:12]; } return _userIdLabel; } - (UIButton *)userIdCopyButton { if (!_userIdCopyButton) { _userIdCopyButton = [UIButton buttonWithType:UIButtonTypeCustom]; if (@available(iOS 13.0, *)) { UIImage *image = [UIImage systemImageNamed:@"doc.on.doc"]; if (image) { [_userIdCopyButton setImage:image forState:UIControlStateNormal]; _userIdCopyButton.tintColor = [UIColor colorWithHex:KBBlackValue]; } } if (!_userIdCopyButton.currentImage) { [_userIdCopyButton setTitle:KBLocalized(@"Copy") forState:UIControlStateNormal]; [_userIdCopyButton setTitleColor:[UIColor colorWithHex:KBBlackValue] forState:UIControlStateNormal]; _userIdCopyButton.titleLabel.font = [KBFont regular:12]; } [_userIdCopyButton addTarget:self action:@selector(onTapUserIdCopy) forControlEvents:UIControlEventTouchUpInside]; } return _userIdCopyButton; } - (UIButton *)logoutBtn { if (!_logoutBtn) { _logoutBtn = [UIButton buttonWithType:UIButtonTypeSystem]; [_logoutBtn setTitle:KBLocalized(@"Log Out") forState:UIControlStateNormal]; [_logoutBtn setTitleColor:[UIColor colorWithHex:0xFF0000] forState:UIControlStateNormal]; _logoutBtn.titleLabel.font = [KBFont medium:16]; _logoutBtn.backgroundColor = UIColor.whiteColor; _logoutBtn.layer.cornerRadius = 12; _logoutBtn.layer.masksToBounds = YES; [_logoutBtn addTarget:self action:@selector(onTapLogout) forControlEvents:UIControlEventTouchUpInside]; } return _logoutBtn; } #pragma mark - Image Picker - (void)presentImagePicker { if (@available(iOS 14.0, *)) { PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init]; config.selectionLimit = 1; // 只选一张 config.filter = [PHPickerFilter imagesFilter]; PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:config]; picker.delegate = self; [self presentViewController:picker animated:YES completion:nil]; } else { UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; picker.delegate = self; [self presentViewController:picker animated:YES completion:nil]; } } #pragma mark - PHPickerViewControllerDelegate - (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14.0)) { [picker dismissViewControllerAnimated:YES completion:nil]; PHPickerResult *first = results.firstObject; if (!first) return; NSItemProvider *p = first.itemProvider; if ([p canLoadObjectOfClass:UIImage.class]) { __weak typeof(self) weakSelf = self; [p loadObjectOfClass:UIImage.class completionHandler:^(__kindof id _Nullable object, NSError * _Nullable error) { UIImage *img = ([object isKindOfClass:UIImage.class] ? (UIImage *)object : nil); if (!img) return; NSUInteger targetKB = 50; NSData *compressedData = [weakSelf kb_compressImage:img targetKB:targetKB]; if (!compressedData) return; UIImage *compressedImage = [UIImage imageWithData:compressedData]; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.myVM upLoadAvatarWithData:compressedData completion:^(BOOL success, NSError * _Nullable error) { if (error) { [KBHUD showError:error.localizedDescription]; return; } dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.avatarView.image = compressedImage; }); }]; }); }]; } } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *img = info[UIImagePickerControllerEditedImage] ?: info[UIImagePickerControllerOriginalImage]; if (img) { UIImage *compressed = [self kb_compressImage:img maxPixel:512 quality:0.85]; self.avatarView.image = compressed; self.avatarJPEGData = UIImageJPEGRepresentation(compressed, 0.85); } [picker dismissViewControllerAnimated:YES completion:nil]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [picker dismissViewControllerAnimated:YES completion:nil]; } /// 压缩图片:最长边不超过 maxPixel,输出近似 quality 的 JPEG - (UIImage *)kb_compressImage:(UIImage *)image maxPixel:(CGFloat)maxPixel quality:(CGFloat)quality { if (!image) return nil; maxPixel = MAX(64, maxPixel); CGSize size = image.size; CGFloat maxSide = MAX(size.width, size.height); CGSize target = size; if (maxSide > maxPixel) { CGFloat scale = maxPixel / maxSide; target = CGSizeMake(floor(size.width * scale), floor(size.height * scale)); } UIGraphicsBeginImageContextWithOptions(target, YES, 1.0); [image drawInRect:CGRectMake(0, 0, target.width, target.height)]; UIImage *scaled = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData *jpeg = UIImageJPEGRepresentation(scaled ?: image, MIN(MAX(quality, 0.2), 0.95)); UIImage *result = [UIImage imageWithData:jpeg] ?: scaled ?: image; return result; } /// 按目标大小(KB)压缩图片: /// 1. 先逐步降低 JPEG 质量; /// 2. 如果质量已经降到下限仍然太大,再按比例缩小尺寸,并重复尝试。 - (nullable NSData *)kb_compressImage:(UIImage *)image targetKB:(NSUInteger)targetKB { if (!image || targetKB == 0) { return nil; } NSUInteger maxBytes = targetKB * 1024; // 初始质量参数 CGFloat compression = 0.9f; CGFloat minCompression = 0.1f; // 不建议再低了,太低会糊 NSData *data = UIImageJPEGRepresentation(image, compression); if (!data) return nil; // 如果一开始就小于目标大小,直接返回 if (data.length <= maxBytes) { return data; } // 1) 先通过降低质量来压缩 while (data.length > maxBytes && compression > minCompression + 0.01f) { compression -= 0.1f; data = UIImageJPEGRepresentation(image, compression); if (!data) return nil; } if (data.length <= maxBytes) { return data; } // 2) 质量降到下限还是太大 -> 等比缩放尺寸 UIImage *currentImage = image; // 防止死循环,限定最多缩放几次 NSInteger maxResizeCount = 6; NSInteger resizeCount = 0; while (data.length > maxBytes && resizeCount < maxResizeCount) { resizeCount++; // 按面积比例来算缩放因子:新的面积约等于 (maxBytes / 当前字节数) * 原面积 CGFloat ratio = (CGFloat)maxBytes / (CGFloat)data.length; // 为了更激进一点,可以乘个经验系数,比如 0.8 ratio *= 0.8f; if (ratio <= 0.0f) { ratio = 0.5f; // 兜底,至少缩小一半 } CGFloat scale = sqrt(ratio); if (scale >= 1.0f) { scale = 0.5f; // 理论上不会走到这里,兜底 } CGSize newSize = CGSizeMake(currentImage.size.width * scale, currentImage.size.height * scale); if (newSize.width < 10 || newSize.height < 10) { // 太小就没意义了,直接 break,返回目前能做到的最小值 break; } // 绘制缩小后的图片 UIGraphicsBeginImageContextWithOptions(newSize, NO, currentImage.scale); [currentImage drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if (!resizedImage) { break; } currentImage = resizedImage; // 每次尺寸缩小之后,再重新从稍高一点的质量开始压一轮 compression = 0.9f; data = UIImageJPEGRepresentation(currentImage, compression); if (!data) return nil; // 再次在质量范围内往下压 while (data.length > maxBytes && compression > minCompression + 0.01f) { compression -= 0.1f; data = UIImageJPEGRepresentation(currentImage, compression); if (!data) return nil; } } // 最终返回当前能做到的最小数据(可能略大于目标,但已经尽力) return data; } - (KBMyVM *)myVM{ if (!_myVM) { _myVM = [[KBMyVM alloc] init]; } return _myVM; } @end