diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 97d174b..7279c87 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ 049FB2232EC311F900FAB05D /* KBPersonInfoVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2222EC311F900FAB05D /* KBPersonInfoVC.m */; }; 049FB2262EC3136D00FAB05D /* KBPersonInfoItemCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2252EC3136D00FAB05D /* KBPersonInfoItemCell.m */; }; 049FB2292EC31BB000FAB05D /* KBChangeNicknamePopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2282EC31BB000FAB05D /* KBChangeNicknamePopView.m */; }; + 049FB22C2EC31F8800FAB05D /* KBGenderPickerPopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB22B2EC31F8800FAB05D /* KBGenderPickerPopView.m */; }; 049FB31D2EC21BCD00FAB05D /* KBMyKeyboardCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */; }; 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; }; 04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; }; @@ -296,6 +297,8 @@ 049FB2252EC3136D00FAB05D /* KBPersonInfoItemCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPersonInfoItemCell.m; sourceTree = ""; }; 049FB2272EC31BB000FAB05D /* KBChangeNicknamePopView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChangeNicknamePopView.h; sourceTree = ""; }; 049FB2282EC31BB000FAB05D /* KBChangeNicknamePopView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChangeNicknamePopView.m; sourceTree = ""; }; + 049FB22A2EC31F8800FAB05D /* KBGenderPickerPopView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBGenderPickerPopView.h; sourceTree = ""; }; + 049FB22B2EC31F8800FAB05D /* KBGenderPickerPopView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBGenderPickerPopView.m; sourceTree = ""; }; 049FB31B2EC21BCD00FAB05D /* KBMyKeyboardCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyKeyboardCell.h; sourceTree = ""; }; 049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyKeyboardCell.m; sourceTree = ""; }; 04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = ""; }; @@ -890,6 +893,8 @@ 049FB2252EC3136D00FAB05D /* KBPersonInfoItemCell.m */, 049FB2272EC31BB000FAB05D /* KBChangeNicknamePopView.h */, 049FB2282EC31BB000FAB05D /* KBChangeNicknamePopView.m */, + 049FB22A2EC31F8800FAB05D /* KBGenderPickerPopView.h */, + 049FB22B2EC31F8800FAB05D /* KBGenderPickerPopView.m */, ); path = V; sourceTree = ""; @@ -1426,6 +1431,7 @@ 048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */, 0477BDF32EBB7B850055D639 /* KBDirectionIndicatorView.m in Sources */, 049FB21A2EC20A9E00FAB05D /* KBMyKeyBoardVC.m in Sources */, + 049FB22C2EC31F8800FAB05D /* KBGenderPickerPopView.m in Sources */, 048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */, 04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */, 0477BDF72EBC63A80055D639 /* KBTestVC.m in Sources */, diff --git a/keyBoard/Class/Me/V/KBGenderPickerPopView.h b/keyBoard/Class/Me/V/KBGenderPickerPopView.h new file mode 100644 index 0000000..f062408 --- /dev/null +++ b/keyBoard/Class/Me/V/KBGenderPickerPopView.h @@ -0,0 +1,33 @@ +// +// KBGenderPickerPopView.h +// keyBoard +// +// Created by Codex on 2025/11/11. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 性别选择弹窗自定义视图(给 LSTPopView 使用) +/// - 使用 UIPickerView 实现上下滑动 +/// - Masonry 约束、懒加载、中文注释 +@interface KBGenderPickerPopView : UIView + +/// 数据源(元素为 NSDictionary,包含 key: id, name) +@property (nonatomic, copy) NSArray *items; +/// 预选中的 id(可为空) +@property (nonatomic, copy, nullable) NSString *selectedId; + +/// 保存回调,返回选中的字典 +@property (nonatomic, copy, nullable) void (^saveHandler)(NSDictionary *selected); +/// 关闭回调 +@property (nonatomic, copy, nullable) void (^closeHandler)(void); + +/// 将选择器滚动到 selectedId 对应的行 +- (void)syncSelection; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/keyBoard/Class/Me/V/KBGenderPickerPopView.m b/keyBoard/Class/Me/V/KBGenderPickerPopView.m new file mode 100644 index 0000000..97b1af3 --- /dev/null +++ b/keyBoard/Class/Me/V/KBGenderPickerPopView.m @@ -0,0 +1,233 @@ +// +// KBGenderPickerPopView.m +// keyBoard +// +// Created by Codex on 2025/11/11. +// + +@import UIKit; +#import "KBGenderPickerPopView.h" +#import + +@interface KBGenderPickerPopView () + +// 白底圆角卡片容器 +@property (nonatomic, strong) UIView *cardView; +// 标题 +@property (nonatomic, strong) UILabel *titleLabel; +// 右上角关闭(下拉箭头) +@property (nonatomic, strong) UIButton *closeButton; +// 选择器 +@property (nonatomic, strong) UIPickerView *picker; +// 选中区域的上/下分隔线(模拟系统选择线) +@property (nonatomic, strong) UIView *lineTop; +@property (nonatomic, strong) UIView *lineBottom; +// 保存按钮 +@property (nonatomic, strong) UIButton *saveButton; + +@end + +@implementation KBGenderPickerPopView + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.backgroundColor = UIColor.clearColor; + // 默认三项 + _items = @[ + @{ @"id": @"1", @"name": @"Male" }, + @{ @"id": @"2", @"name": @"Female" }, + @{ @"id": @"3", @"name": @"The Third Gender" }, + ]; + [self buildUI]; + [self makeConstraints]; + } + return self; +} + +#pragma mark - Public + +- (void)setItems:(NSArray *)items { + _items = [items copy]; + [self.picker reloadAllComponents]; + [self syncSelection]; +} + +- (void)setSelectedId:(NSString *)selectedId { + _selectedId = [selectedId copy]; + [self syncSelection]; +} + +- (void)syncSelection { + // 找到选中 id 对应的行(避免 __block,可直接用 indexOfObjectPassingTest) + NSInteger row = 0; + if (self.selectedId.length > 0) { + NSUInteger idx = [self.items indexOfObjectPassingTest:^BOOL(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + return [[obj objectForKey:@"id"] ?: @"" isEqualToString:self.selectedId]; + }]; + if (idx != NSNotFound) row = (NSInteger)idx; + } + if (row < (NSInteger)self.items.count) { + [self.picker selectRow:row inComponent:0 animated:NO]; + } + [self.picker reloadAllComponents]; +} + +#pragma mark - Actions + +- (void)onTapClose { if (self.closeHandler) self.closeHandler(); } + +- (void)onTapSave { + NSInteger row = [self.picker selectedRowInComponent:0]; + if (row >= 0 && row < (NSInteger)self.items.count) { + NSDictionary *sel = self.items[row]; + if (self.saveHandler) self.saveHandler(sel); + } +} + +#pragma mark - UI + +- (void)buildUI { + [self addSubview:self.cardView]; + [self.cardView addSubview:self.titleLabel]; + [self.cardView addSubview:self.closeButton]; + [self.cardView addSubview:self.picker]; + [self.cardView addSubview:self.lineTop]; + [self.cardView addSubview:self.lineBottom]; + [self.cardView addSubview:self.saveButton]; +} + +- (void)makeConstraints { + [self.cardView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self); }]; + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(20); + make.top.equalTo(self.cardView).offset(18); + }]; + [self.titleLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [self.titleLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.titleLabel); + make.right.equalTo(self.cardView).offset(-16); + make.width.height.mas_equalTo(36); + }]; + [self.picker mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(10); + make.right.equalTo(self.cardView).offset(-10); + make.top.equalTo(self.titleLabel.mas_bottom).offset(6); + make.bottom.equalTo(self.saveButton.mas_top).offset(0); + }]; + // 选中区域分隔线放在 picker 上方 + CGFloat rowH = 48; + [self.lineTop mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.equalTo(self.picker); + make.centerY.equalTo(self.picker).offset(-rowH/2.0); + make.height.mas_equalTo(1); + }]; + [self.lineBottom mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.equalTo(self.picker); + make.centerY.equalTo(self.picker).offset(rowH/2.0); + make.height.mas_equalTo(1); + }]; + [self.saveButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.cardView).offset(16); + make.right.equalTo(self.cardView).offset(-16); + make.bottom.equalTo(self.cardView).offset(-18); + make.height.mas_equalTo(46); + }]; +} + +#pragma mark - Picker + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; } +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return self.items.count; } + +- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component { return 48.0; } + +- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view { + UILabel *label = (UILabel *)view; + if (![label isKindOfClass:UILabel.class]) { + label = [[UILabel alloc] init]; + label.textAlignment = NSTextAlignmentCenter; + label.font = [UIFont systemFontOfSize:20 weight:UIFontWeightSemibold]; + } + NSString *name = self.items[row][@"name"] ?: @""; + label.text = name; + BOOL selected = (row == [pickerView selectedRowInComponent:0]); + label.textColor = selected ? [UIColor colorWithRed:0.02 green:0.75 blue:0.67 alpha:1.0] + : [UIColor colorWithWhite:0.65 alpha:1.0]; + return label; +} + +- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + // 选中变化后刷新颜色 + [pickerView reloadAllComponents]; +} + +#pragma mark - Lazy UI + +- (UIView *)cardView { + if (!_cardView) { + _cardView = [UIView new]; + _cardView.backgroundColor = UIColor.whiteColor; + _cardView.layer.cornerRadius = 18.0; _cardView.layer.masksToBounds = YES; + } + return _cardView; +} + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [UILabel new]; + _titleLabel.text = @"Modify Gender"; + _titleLabel.textColor = [UIColor blackColor]; + _titleLabel.font = [UIFont systemFontOfSize:22 weight:UIFontWeightBold]; + } + return _titleLabel; +} + +- (UIButton *)closeButton { + if (!_closeButton) { + _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; + _closeButton.backgroundColor = [UIColor colorWithWhite:1 alpha:0.95]; + _closeButton.layer.cornerRadius = 18.0; _closeButton.layer.masksToBounds = YES; + UIImage *img = nil; if (@available(iOS 13.0, *)) img = [UIImage systemImageNamed:@"chevron.down"]; + [_closeButton setImage:img forState:UIControlStateNormal]; + if (!img) { [_closeButton setTitle:@"∨" forState:UIControlStateNormal]; } + [_closeButton setTitleColor:[UIColor colorWithWhite:0.3 alpha:1] forState:UIControlStateNormal]; + _closeButton.tintColor = [UIColor colorWithWhite:0.3 alpha:1]; + [_closeButton addTarget:self action:@selector(onTapClose) forControlEvents:UIControlEventTouchUpInside]; + } + return _closeButton; +} + +- (UIPickerView *)picker { + if (!_picker) { + _picker = [[UIPickerView alloc] init]; + _picker.dataSource = self; _picker.delegate = self; + _picker.backgroundColor = UIColor.clearColor; + } + return _picker; +} + +- (UIView *)lineTop { + if (!_lineTop) { _lineTop = [self makeLine]; } + return _lineTop; +} +- (UIView *)lineBottom { + if (!_lineBottom) { _lineBottom = [self makeLine]; } + return _lineBottom; +} +- (UIView *)makeLine { UIView *v = [UIView new]; v.backgroundColor = [UIColor colorWithWhite:0.88 alpha:1.0]; return v; } + +- (UIButton *)saveButton { + if (!_saveButton) { + _saveButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_saveButton setTitle:@"Save" forState:UIControlStateNormal]; + [_saveButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; + _saveButton.titleLabel.font = [UIFont systemFontOfSize:20 weight:UIFontWeightSemibold]; + _saveButton.backgroundColor = [UIColor colorWithRed:0.02 green:0.75 blue:0.67 alpha:1.0]; + _saveButton.layer.cornerRadius = 23.0; _saveButton.layer.masksToBounds = YES; + [_saveButton addTarget:self action:@selector(onTapSave) forControlEvents:UIControlEventTouchUpInside]; + } + return _saveButton; +} + +@end diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m index e3c62f2..f4a6b1c 100644 --- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m +++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m @@ -16,6 +16,7 @@ #import #import "LSTPopView.h" #import "KBChangeNicknamePopView.h" +#import "KBGenderPickerPopView.h" @interface KBPersonInfoVC () @@ -107,10 +108,9 @@ } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - // 示例:点击昵称/性别执行操作;用户 ID 行点击不处理(有复制按钮) if (indexPath.row == 0) { // 昵称编辑 -> 弹窗 - CGFloat width = MIN(KB_SCREEN_WIDTH - 48, 360); + CGFloat width = KB_SCREEN_WIDTH; KBChangeNicknamePopView *content = [[KBChangeNicknamePopView alloc] initWithFrame:CGRectMake(0, 0, width, 230)]; content.prefillNickname = self.items.firstObject[@"value"] ?: @""; @@ -119,7 +119,7 @@ popStyle:LSTPopStyleSmoothFromBottom dismissStyle:LSTDismissStyleScale]; pop.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; - pop.hemStyle = LSTHemStyleCenter; // 居中 + pop.hemStyle = LSTHemStyleBottom; // 居中 pop.isClickBgDismiss = YES; // 点击背景关闭 pop.isAvoidKeyboard = YES; // 规避键盘 pop.avoidKeyboardSpace = 10; @@ -139,9 +139,39 @@ }; [pop pop]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [content focusInput]; }); +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [content focusInput]; }); } else if (indexPath.row == 1) { - // TODO: 性别选择 + // 性别选择 -> 弹窗 + NSArray *genders = @[ @{@"id":@"1",@"name":@"Male"}, @{@"id":@"2",@"name":@"Female"}, @{@"id":@"3",@"name":@"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[1][@"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"] ?: @""; + NSMutableArray *m = [weakSelf.items mutableCopy]; + NSMutableDictionary *d1 = [m[1] mutableCopy]; + d1[@"value"] = name; m[1] = d1; weakSelf.items = m; + [weakSelf.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone]; + [weakPop dismiss]; + }; + + [pop pop]; } }