Files
keyboard/keyBoard/Class/Pay/VC/KBVipPay.m
2025-11-15 14:27:41 +08:00

317 lines
14 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBVipPay.m
// keyBoard
//
// Created by Mac on 2025/11/14.
//
#import "KBVipPay.h"
#import "KBVipPayHeaderView.h"
#import "KBVipSubscribeCell.h"
#import "KBVipReviewListCell.h"
static NSString * const kKBVipHeaderId = @"kKBVipHeaderId";
static NSString * const kKBVipSubscribeCellId = @"kKBVipSubscribeCellId";
static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
@interface KBVipPay () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView; // 主列表(竖向滚动)
@property (nonatomic, strong) NSArray<NSDictionary *> *plans; // 订阅方案数组
@property (nonatomic, assign) NSInteger selectedIndex; // 当前选中的方案索引
@property (nonatomic, strong) UIButton *closeButton; // 当前选中的方案索引
@property (nonatomic, strong) UIImageView *bgImageView; // 全屏背景图
// Header 自适应测量
@property (nonatomic, strong) KBVipPayHeaderView *sizingHeader;
@property (nonatomic, assign) CGFloat headerHeight;
// 底部支付与协议
@property (nonatomic, strong) UIButton *payButton; // 支付按钮(背景图)
@property (nonatomic, strong) UILabel *agreementLabel; // 协议提示
@property (nonatomic, strong) UIButton *agreementButton; // 《Embership Agreement》
@end
@implementation KBVipPay
- (void)viewDidLoad {
[super viewDidLoad];
// 标题与导航样式
// self.kb_titleLabel.text = @"VIP";
// self.kb_navView.backgroundColor = [UIColor clearColor];
self.view.backgroundColor = [UIColor colorWithHex:0xF6F7FB];
self.kb_enableCustomNavBar = NO;
self.bgImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_vipbg_icon"]];
self.bgImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:self.bgImageView];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.right.equalTo(self.view);
make.height.mas_equalTo(224);
}];
// 初始化数据(简单演示)
self.plans = @[
@{@"title":@"Monthly Subscription", @"price":@"$4.49", @"strike":@"$4.49"},
@{@"title":@"Monthly Subscription", @"price":@"$4.49", @"strike":@"$4.49"},
@{@"title":@"Monthly Subscription", @"price":@"$4.49", @"strike":@"$4.49"},
];
self.selectedIndex = 1; // 默认选中第二项
// 组装主列表
[self.view addSubview:self.collectionView];
// 先添加底部按钮与协议,再让 collectionView 的底部在按钮之上
[self.view addSubview:self.payButton];
[self.view addSubview:self.agreementLabel];
[self.view addSubview:self.agreementButton];
[self.payButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(24);
make.right.equalTo(self.view).offset(-24);
make.bottom.equalTo(self.agreementLabel.mas_top).offset(-14);
make.height.mas_equalTo(58);
}];
[self.agreementButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.bottom.equalTo(self.view).offset(-KB_SAFE_BOTTOM - 15);
}];
[self.agreementLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.bottom.equalTo(self.agreementButton.mas_top).offset(-8);
}];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.top.equalTo(self.view).offset(0);
make.bottom.equalTo(self.payButton.mas_top).offset(-16);
}];
[self.view addSubview:self.closeButton];
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT - 30);
make.left.equalTo(self.view).offset(15);
make.width.height.mas_equalTo(36);
}];
// 预计算 Header 高度(由内部约束决定)
self.headerHeight = [self kb_calcHeaderHeightForWidth:KB_SCREEN_WIDTH];
[self.collectionView reloadData];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 首次进入,确保订阅项保持选中态(避免首屏仅显示 Header待滚动出现时没有选中样式
NSIndexPath *ip = [NSIndexPath indexPathForItem:self.selectedIndex inSection:1];
if (!ip) { return; }
// 系统层面也置为选中
[self.collectionView selectItemAtIndexPath:ip animated:NO scrollPosition:UICollectionViewScrollPositionNone];
// 若此时 cell 不可见willDisplay 再兜底
KBVipSubscribeCell *cell = (KBVipSubscribeCell *)[self.collectionView cellForItemAtIndexPath:ip];
if (cell) { [cell applySelected:YES animated:NO]; }
}
#pragma mark - Header Height Calc
- (CGFloat)kb_calcHeaderHeightForWidth:(CGFloat)width {
if (width <= 0) { width = KB_SCREEN_WIDTH; }
if (!self.sizingHeader) {
self.sizingHeader = [[KBVipPayHeaderView alloc] initWithFrame:CGRectMake(0, 0, width, 1)];
}
// 更新目标宽度并触发布局
self.sizingHeader.bounds = CGRectMake(0, 0, width, self.sizingHeader.bounds.size.height);
[self.sizingHeader setNeedsLayout];
[self.sizingHeader layoutIfNeeded];
CGSize size = [self.sizingHeader systemLayoutSizeFittingSize:CGSizeMake(width, UILayoutFittingCompressedSize.height)
withHorizontalFittingPriority:UILayoutPriorityRequired
verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return MAX(1, ceil(size.height));
}
#pragma mark - Action
- (void)onTapClose{
[self.navigationController popViewControllerAnimated:true];
}
#pragma mark - Bottom Actions
- (void)onTapPayButton {
// TODO: 接入支付,这里仅做 UI
[KBHUD showInfo:@"Pay clicked"];
}
- (void)agreementButtonAction{
[KBHUD showInfo:@"跳转协议"];
}
#pragma mark - UICollectionView DataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
// 0头部1订阅选项2底部横滑好评
return 3;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if (section == 1) { return self.plans.count; }
if (section == 2) { return 1; }
return 0; // 头部仅使用 header
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 1) {
KBVipSubscribeCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBVipSubscribeCellId forIndexPath:indexPath];
NSDictionary *plan = self.plans[indexPath.item];
[cell configTitle:plan[@"title"] price:plan[@"price"] strike:plan[@"strike"]];
[cell applySelected:(indexPath.item == self.selectedIndex) animated:NO];
return cell;
} else {
KBVipReviewListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBVipReviewListCellId forIndexPath:indexPath];
return cell;
}
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 && [kind isEqualToString:UICollectionElementKindSectionHeader]) {
KBVipPayHeaderView *header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kKBVipHeaderId forIndexPath:indexPath];
return header;
}
return [UICollectionReusableView new];
}
#pragma mark - UICollectionView Delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section != 1) { return; }
if (self.selectedIndex == indexPath.item) { return; }
NSInteger old = self.selectedIndex;
self.selectedIndex = indexPath.item;
KBVipSubscribeCell *newCell = (KBVipSubscribeCell *)[collectionView cellForItemAtIndexPath:indexPath];
[newCell applySelected:YES animated:YES];
if (old >= 0 && old < self.plans.count) {
NSIndexPath *oldIP = [NSIndexPath indexPathForItem:old inSection:1];
KBVipSubscribeCell *oldCell = (KBVipSubscribeCell *)[collectionView cellForItemAtIndexPath:oldIP];
[oldCell applySelected:NO animated:YES];
}
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
// 兜底:当订阅项第一次出现在屏幕上,强制同步选中样式
if (indexPath.section == 1 && [cell isKindOfClass:KBVipSubscribeCell.class]) {
BOOL sel = (indexPath.item == self.selectedIndex);
KBVipSubscribeCell *c = (KBVipSubscribeCell *)cell;
if (sel) {
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
[c applySelected:sel animated:NO];
}
}
#pragma mark - FlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat w = KB_SCREEN_WIDTH - 32;
if (indexPath.section == 1) {
return CGSizeMake(w, KBFit(75 + 6));
} else {
return CGSizeMake(w, 140);
}
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
if (section == 0) {
// 动态返回测量好的 Header 高度
CGFloat w = collectionView.bounds.size.width ?: KB_SCREEN_WIDTH;
if (self.headerHeight <= 1) { self.headerHeight = [self kb_calcHeaderHeightForWidth:w]; }
return CGSizeMake(w, self.headerHeight);
}
return CGSizeZero;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
if (section == 1) { return 14; }
return 0;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
if (section == 1) {
// 留出顶部间距,避免第一个订阅 cell 的边框被 header 盖住
return UIEdgeInsetsMake(16, 16, 10, 16);
} else if (section == 2) {
return UIEdgeInsetsMake(10, 16, 20, 16);
}
return UIEdgeInsetsZero;
}
#pragma mark - Lazy
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
// 每次宽度变化时让布局失效,便于 header 重算高度
layout.sectionHeadersPinToVisibleBounds = NO;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.alwaysBounceVertical = YES;
if (@available(iOS 11.0, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[_collectionView registerClass:KBVipPayHeaderView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kKBVipHeaderId];
[_collectionView registerClass:KBVipSubscribeCell.class forCellWithReuseIdentifier:kKBVipSubscribeCellId];
[_collectionView registerClass:KBVipReviewListCell.class forCellWithReuseIdentifier:kKBVipReviewListCellId];
}
return _collectionView;
}
- (UIButton *)closeButton {
if (!_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_closeButton setImage:[UIImage imageNamed:@"close_white2_icon"] forState:UIControlStateNormal];
[_closeButton addTarget:self action:@selector(onTapClose) forControlEvents:UIControlEventTouchUpInside];
}
return _closeButton;
}
- (UIButton *)payButton {
if (!_payButton) {
_payButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_payButton setTitle:@"Recharge Now" forState:UIControlStateNormal];
[_payButton setTitleColor:[UIColor colorWithHex:KBBlackValue] forState:UIControlStateNormal];
_payButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
UIImage *bg = [UIImage imageNamed:@"recharge_now_icon"];
if (bg) {
[_payButton setBackgroundImage:bg forState:UIControlStateNormal];
} else {
// 兜底:纯代码渐变
UIImage *fallback = [UIImage kb_gradientImageWithColors:@[[UIColor colorWithHex:0xC7F8F0], [UIColor colorWithHex:0xE8FFF6]] size:CGSizeMake(10, 58) direction:KBGradientDirectionLeftToRight];
[_payButton setBackgroundImage:[fallback resizableImageWithCapInsets:UIEdgeInsetsMake(29, 29, 29, 29) resizingMode:UIImageResizingModeStretch] forState:UIControlStateNormal];
}
[_payButton addTarget:self action:@selector(onTapPayButton) forControlEvents:UIControlEventTouchUpInside];
}
return _payButton;
}
- (UILabel *)agreementLabel {
if (!_agreementLabel) {
_agreementLabel = [UILabel new];
_agreementLabel.text = @"By clicking \"Pay\", you indicate your agreement to the";
_agreementLabel.font = [UIFont systemFontOfSize:12];
_agreementLabel.textColor = [UIColor colorWithHex:KBBlackValue];
}
return _agreementLabel;
}
- (UIButton *)agreementButton {
if (!_agreementButton) {
_agreementButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_agreementButton setTitle:@"《Embership Agreement》" forState:UIControlStateNormal];
[_agreementButton setTitleColor:[UIColor colorWithHex:KBColorValue] forState:UIControlStateNormal];
_agreementButton.titleLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightSemibold];
[_agreementButton addTarget:self action:@selector(agreementButtonAction) forControlEvents:UIControlEventTouchUpInside];
}
return _agreementButton;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// 宽度变化时重算 Header 高度并刷新布局
CGFloat w = self.collectionView.bounds.size.width ?: KB_SCREEN_WIDTH;
CGFloat newH = [self kb_calcHeaderHeightForWidth:w];
if (fabs(newH - self.headerHeight) > 0.5) {
self.headerHeight = newH;
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
[layout invalidateLayout];
}
}
@end