This commit is contained in:
2025-11-14 19:48:15 +08:00
parent 4f2e80e482
commit dace0a9309
21 changed files with 792 additions and 19 deletions

View File

@@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
04122FAA2EC73C0100EF7AB3 /* KBVipPayHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122FA92EC73C0100EF7AB3 /* KBVipPayHeaderView.m */; };
04122FAD2EC73C0100EF7AB3 /* KBVipSubscribeCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122FAC2EC73C0100EF7AB3 /* KBVipSubscribeCell.m */; };
04122FB02EC73C0100EF7AB3 /* KBVipReviewItemCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122FAF2EC73C0100EF7AB3 /* KBVipReviewItemCell.m */; };
04122FB32EC73C0100EF7AB3 /* KBVipReviewListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122FB22EC73C0100EF7AB3 /* KBVipReviewListCell.m */; };
04122F5D2EC5E5A900EF7AB3 /* KBLoginVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F5B2EC5E5A900EF7AB3 /* KBLoginVM.m */; };
04122F622EC5F41D00EF7AB3 /* KBUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F612EC5F41D00EF7AB3 /* KBUser.m */; };
04122F6D2EC5F40800EF7AB3 /* NSObject+FGIsNullOrEmpty.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F6B2EC5F40800EF7AB3 /* NSObject+FGIsNullOrEmpty.m */; };
@@ -189,6 +193,14 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
04122FA82EC73C0100EF7AB3 /* KBVipPayHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBVipPayHeaderView.h; sourceTree = "<group>"; };
04122FA92EC73C0100EF7AB3 /* KBVipPayHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVipPayHeaderView.m; sourceTree = "<group>"; };
04122FAB2EC73C0100EF7AB3 /* KBVipSubscribeCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBVipSubscribeCell.h; sourceTree = "<group>"; };
04122FAC2EC73C0100EF7AB3 /* KBVipSubscribeCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVipSubscribeCell.m; sourceTree = "<group>"; };
04122FAE2EC73C0100EF7AB3 /* KBVipReviewItemCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBVipReviewItemCell.h; sourceTree = "<group>"; };
04122FAF2EC73C0100EF7AB3 /* KBVipReviewItemCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVipReviewItemCell.m; sourceTree = "<group>"; };
04122FB12EC73C0100EF7AB3 /* KBVipReviewListCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBVipReviewListCell.h; sourceTree = "<group>"; };
04122FB22EC73C0100EF7AB3 /* KBVipReviewListCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVipReviewListCell.m; sourceTree = "<group>"; };
04122F592EC5D40000EF7AB3 /* KBAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAPI.h; sourceTree = "<group>"; };
04122F5A2EC5E5A900EF7AB3 /* KBLoginVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLoginVM.h; sourceTree = "<group>"; };
04122F5B2EC5E5A900EF7AB3 /* KBLoginVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLoginVM.m; sourceTree = "<group>"; };
@@ -552,6 +564,14 @@
children = (
04122F7B2EC5FC5500EF7AB3 /* KBJfPayCell.h */,
04122F7C2EC5FC5500EF7AB3 /* KBJfPayCell.m */,
04122FA82EC73C0100EF7AB3 /* KBVipPayHeaderView.h */,
04122FA92EC73C0100EF7AB3 /* KBVipPayHeaderView.m */,
04122FAB2EC73C0100EF7AB3 /* KBVipSubscribeCell.h */,
04122FAC2EC73C0100EF7AB3 /* KBVipSubscribeCell.m */,
04122FAE2EC73C0100EF7AB3 /* KBVipReviewItemCell.h */,
04122FAF2EC73C0100EF7AB3 /* KBVipReviewItemCell.m */,
04122FB12EC73C0100EF7AB3 /* KBVipReviewListCell.h */,
04122FB22EC73C0100EF7AB3 /* KBVipReviewListCell.m */,
);
path = V;
sourceTree = "<group>";
@@ -1591,6 +1611,7 @@
04122F882EC6F07F00EF7AB3 /* KBFullAccessManager.m in Sources */,
04122F622EC5F41D00EF7AB3 /* KBUser.m in Sources */,
04122F8B2EC6F7C800EF7AB3 /* IAPVerifyTransactionObj.m in Sources */,
04122FAD2EC73C0100EF7AB3 /* KBVipSubscribeCell.m in Sources */,
049FB31D2EC21BCD00FAB05D /* KBMyKeyboardCell.m in Sources */,
048909F62EC0AAAA00FABA60 /* KBCategoryTitleCell.m in Sources */,
048909F72EC0AAAA00FABA60 /* KBCategoryTitleView.m in Sources */,
@@ -1656,12 +1677,14 @@
04122F6E2EC5F40800EF7AB3 /* FGIAPProductsFilter.m in Sources */,
04122F6F2EC5F40800EF7AB3 /* FGIAPManager.m in Sources */,
04122F702EC5F40800EF7AB3 /* FGIAPService.m in Sources */,
04122FB32EC73C0100EF7AB3 /* KBVipReviewListCell.m in Sources */,
048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */,
04C6EABF2EAF86530089C901 /* main.m in Sources */,
04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */,
047C65502EBCBA9E0035E841 /* KBShopVC.m in Sources */,
0477BE042EBC83130055D639 /* HomeMainVC.m in Sources */,
0477BDFD2EBC6A170055D639 /* HomeHotVC.m in Sources */,
04122FAA2EC73C0100EF7AB3 /* KBVipPayHeaderView.m in Sources */,
049FB2432EC4BBB700FAB05D /* KBLoginPopView.m in Sources */,
048908CC2EBE373500FABA60 /* KBSearchBarView.m in Sources */,
04122F872EC6198C00EF7AB3 /* WMDragView.m in Sources */,
@@ -1679,6 +1702,7 @@
A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */,
047C65532EBCBAC60035E841 /* KBCommunityVC.m in Sources */,
A1B2C4212EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */,
04122FB02EC73C0100EF7AB3 /* KBVipReviewItemCell.m in Sources */,
04122F822EC5FC6F00EF7AB3 /* KBJfPay.m in Sources */,
04122F5D2EC5E5A900EF7AB3 /* KBLoginVM.m in Sources */,
0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */,

View File

@@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "home_left_bg 1.png",
"filename" : "home_left_bg.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "home_left_bg 2.png",
"filename" : "home_left_bg 1.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "home_right_bg 1.png",
"filename" : "home_right_bg.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "home_right_bg 2.png",
"filename" : "home_right_bg 1.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -8,7 +8,7 @@
#import "HomeHeadView.h"
#import "UIImage+KBColor.h"
#import "KBTopImageButton.h"
#import "KBJfPay.h"
#import "KBVipPay.h"
@interface HomeHeadView()
@@ -163,7 +163,7 @@
#pragma mark - Actions
- (void)onTapBuyAction {
// if (self.onTapBuy) { self.onTapBuy(); }
KBJfPay *vc = [[KBJfPay alloc] init];
KBVipPay *vc = [[KBVipPay alloc] init];
[KB_CURRENT_NAV pushViewController:vc animated:true];
}

View File

@@ -180,11 +180,11 @@
[self.leftBgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.equalTo(self.secWhiteContentView);
// make.bottom.equalTo(self.secWhiteContentView);
make.height.mas_equalTo(466);
}];
[self.rightBgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.equalTo(self.secWhiteContentView);
// make.bottom.equalTo(self.secWhiteContentView);
make.height.mas_equalTo(466);
}];

View File

@@ -0,0 +1,19 @@
//
// KBVipPayHeaderView.h
// keyBoard
//
// 顶部大头部视图:包含 5 张图片
// - 高度484 + 导航总高度
// - 图片资源pay_vip_icon、pay_ai_icon、pay_keyboard_icon、pay_chat_icon、pay_emotion_icon
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface KBVipPayHeaderView : UICollectionReusableView
NS_ASSUME_NONNULL_END
@end

View File

@@ -0,0 +1,147 @@
//
// KBVipPayHeaderView.m
// keyBoard
//
// 使 Masonry 5
//
#import "KBVipPayHeaderView.h"
@interface KBVipPayHeaderView ()
// 便 KB_NAV_TOTAL_HEIGHT
@property (nonatomic, strong) UIView *containerView;
// pay_vip_icon
@property (nonatomic, strong) UIImageView *vipImageView;
//
@property (nonatomic, strong) UIImageView *aiImageView;
@property (nonatomic, strong) UIImageView *keyboardImageView;
@property (nonatomic, strong) UIImageView *chatImageView;
@property (nonatomic, strong) UIImageView *emotionImageView;
@end
@implementation KBVipPayHeaderView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
[self addSubview:self.containerView];
[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
// 1.
[self.containerView addSubview:self.vipImageView];
[self.vipImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.containerView).offset(16);
make.right.equalTo(self.containerView).offset(-16);
make.top.equalTo(self.containerView).offset(KB_NAV_TOTAL_HEIGHT + 8);
make.height.mas_equalTo(KBFit(180));
}];
// 2.
UIView *g1 = [self gridItemWithImageView:self.aiImageView];
UIView *g2 = [self gridItemWithImageView:self.keyboardImageView];
UIView *g3 = [self gridItemWithImageView:self.chatImageView];
UIView *g4 = [self gridItemWithImageView:self.emotionImageView];
[self.containerView addSubview:g1];
[self.containerView addSubview:g2];
[self.containerView addSubview:g3];
[self.containerView addSubview:g4];
CGFloat spacing = 12;
[g1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.vipImageView);
make.top.equalTo(self.vipImageView.mas_bottom).offset(18);
make.height.mas_equalTo(KBFit(90));
}];
[g2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.vipImageView);
make.top.equalTo(g1);
make.height.equalTo(g1);
make.left.equalTo(g1.mas_right).offset(spacing);
make.width.equalTo(g1);
}];
[g3 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(g1);
make.top.equalTo(g1.mas_bottom).offset(spacing);
make.height.equalTo(g1);
make.width.equalTo(g1);
}];
[g4 mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.vipImageView);
make.top.equalTo(g3);
make.left.equalTo(g3.mas_right).offset(spacing);
make.height.equalTo(g1);
make.width.equalTo(g1);
make.bottom.lessThanOrEqualTo(self.containerView).offset(-12);
}];
}
return self;
}
#pragma mark - Helpers
- (UIView *)gridItemWithImageView:(UIImageView *)iv {
//
UIView *v = [UIView new];
v.backgroundColor = [UIColor colorWithWhite:0.97 alpha:1.0];
v.layer.cornerRadius = 12;
v.layer.masksToBounds = YES;
[v addSubview:iv];
[iv mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(v);
make.width.height.mas_equalTo(40);
}];
return v;
}
#pragma mark - Lazy
- (UIView *)containerView {
if (!_containerView) {
_containerView = [UIView new];
_containerView.backgroundColor = [UIColor clearColor];
}
return _containerView;
}
- (UIImageView *)vipImageView {
if (!_vipImageView) {
_vipImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_vip_icon"]];
_vipImageView.contentMode = UIViewContentModeScaleAspectFill;
_vipImageView.clipsToBounds = YES;
_vipImageView.layer.cornerRadius = 14;
}
return _vipImageView;
}
- (UIImageView *)aiImageView {
if (!_aiImageView) {
_aiImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_ai_icon"]];
_aiImageView.contentMode = UIViewContentModeScaleAspectFit;
}
return _aiImageView;
}
- (UIImageView *)keyboardImageView {
if (!_keyboardImageView) {
_keyboardImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_keyboard_icon"]];
_keyboardImageView.contentMode = UIViewContentModeScaleAspectFit;
}
return _keyboardImageView;
}
- (UIImageView *)chatImageView {
if (!_chatImageView) {
_chatImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_chat_icon"]];
_chatImageView.contentMode = UIViewContentModeScaleAspectFit;
}
return _chatImageView;
}
- (UIImageView *)emotionImageView {
if (!_emotionImageView) {
_emotionImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_emotion_icon"]];
_emotionImageView.contentMode = UIViewContentModeScaleAspectFit;
}
return _emotionImageView;
}
@end

View File

@@ -0,0 +1,17 @@
//
// KBVipReviewItemCell.h
// keyBoard
//
// 横向列表中的 item包含 5 个爱心图标pay_5aixin_icon
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface KBVipReviewItemCell : UICollectionViewCell
- (void)configWithName:(NSString *)name text:(NSString *)text;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
//
// KBVipReviewItemCell.m
// keyBoard
//
// / 5
//
#import "KBVipReviewItemCell.h"
@interface KBVipReviewItemCell ()
@property (nonatomic, strong) UIView *cardView;
@property (nonatomic, strong) UIImageView *avatarView;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UIStackView *heartsStack;
@property (nonatomic, strong) UILabel *contentLabel;
@end
@implementation KBVipReviewItemCell
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.contentView.backgroundColor = UIColor.clearColor;
[self.contentView addSubview:self.cardView];
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
[self.cardView addSubview:self.avatarView];
[self.cardView addSubview:self.nameLabel];
[self.cardView addSubview:self.heartsStack];
[self.cardView addSubview:self.contentLabel];
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.equalTo(self.cardView).offset(12);
make.width.height.mas_equalTo(28);
}];
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.avatarView);
make.left.equalTo(self.avatarView.mas_right).offset(8);
make.right.lessThanOrEqualTo(self.cardView).offset(-12);
}];
[self.heartsStack mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.avatarView);
make.top.equalTo(self.avatarView.mas_bottom).offset(8);
}];
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.avatarView);
make.right.equalTo(self.cardView).offset(-12);
make.top.equalTo(self.heartsStack.mas_bottom).offset(6);
make.bottom.lessThanOrEqualTo(self.cardView).offset(-12);
}];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.cardView.layer.cornerRadius = 12;
self.cardView.layer.masksToBounds = YES;
}
- (void)configWithName:(NSString *)name text:(NSString *)text {
self.nameLabel.text = name.length ? name : @"User";
self.contentLabel.text = text.length ? text : @"I highly recommend this app.";
}
#pragma mark - Lazy
- (UIView *)cardView {
if (!_cardView) {
_cardView = [UIView new];
_cardView.backgroundColor = [UIColor whiteColor];
}
return _cardView;
}
- (UIImageView *)avatarView {
if (!_avatarView) {
_avatarView = [UIImageView new];
_avatarView.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0];
_avatarView.layer.cornerRadius = 14;
_avatarView.layer.masksToBounds = YES;
}
return _avatarView;
}
- (UILabel *)nameLabel {
if (!_nameLabel) {
_nameLabel = [UILabel new];
_nameLabel.textColor = [UIColor colorWithHex:KBBlackValue];
_nameLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold];
_nameLabel.text = @"Sdsd666";
}
return _nameLabel;
}
- (UIStackView *)heartsStack {
if (!_heartsStack) {
_heartsStack = [[UIStackView alloc] init];
_heartsStack.axis = UILayoutConstraintAxisHorizontal;
_heartsStack.spacing = 4;
_heartsStack.alignment = UIStackViewAlignmentCenter;
_heartsStack.distribution = UIStackViewDistributionFillProportionally;
for (int i = 0; i < 5; i++) {
UIImageView *iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"pay_5aixin_icon"]];
iv.contentMode = UIViewContentModeScaleAspectFit;
[iv mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.mas_equalTo(16);
}];
[_heartsStack addArrangedSubview:iv];
}
}
return _heartsStack;
}
- (UILabel *)contentLabel {
if (!_contentLabel) {
_contentLabel = [UILabel new];
_contentLabel.textColor = [UIColor colorWithWhite:0.25 alpha:1.0];
_contentLabel.font = [UIFont systemFontOfSize:12];
_contentLabel.numberOfLines = 2;
_contentLabel.text = @"I Highly Recommend This App. It Taught Me How To Chat";
}
return _contentLabel;
}
@end

View File

@@ -0,0 +1,16 @@
//
// KBVipReviewListCell.h
// keyBoard
//
// 末尾的横向滚动列表 Cell内部自带一个 UICollectionView。
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface KBVipReviewListCell : UICollectionViewCell
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,82 @@
//
// KBVipReviewListCell.m
// keyBoard
//
// Cell UICollectionView
//
#import "KBVipReviewListCell.h"
#import "KBVipReviewItemCell.h"
static NSString * const kKBVipReviewItemCellId = @"kKBVipReviewItemCellId";
@interface KBVipReviewListCell () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView; //
@property (nonatomic, strong) NSArray<NSDictionary *> *data; //
@end
@implementation KBVipReviewListCell
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.contentView.backgroundColor = [UIColor clearColor];
[self.contentView addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView).insets(UIEdgeInsetsMake(0, 0, 0, 0));
}];
//
_data = @[
@{@"name":@"Sdsd666", @"text":@"I Highly Recommend This App. It Taught Me How To Chat"},
@{@"name":@"Joyce", @"text":@"Great keyboard and AI features!"},
@{@"name":@"Luna", @"text":@"Amazing app, love it."},
@{@"name":@"Mark", @"text":@"Helps with chat and emotion."},
@{@"name":@"Alan", @"text":@"Useful personalized keyboard."},
@{@"name":@"Coco", @"text":@"Recommend to friends."},
];
}
return self;
}
#pragma mark - DataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.data.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
KBVipReviewItemCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBVipReviewItemCellId forIndexPath:indexPath];
NSDictionary *d = self.data[indexPath.item];
[cell configWithName:d[@"name"] text:d[@"text"]];
return cell;
}
#pragma mark - FlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat w = floor((KB_SCREEN_WIDTH - 16 - 16 - 10) / 2.0); //
return CGSizeMake(MAX(120, w), 120);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 10; }
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 10; }
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 16, 0, 16);
}
#pragma mark - Lazy
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.backgroundColor = UIColor.clearColor;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.dataSource = self;
_collectionView.delegate = self;
[_collectionView registerClass:KBVipReviewItemCell.class forCellWithReuseIdentifier:kKBVipReviewItemCellId];
if (@available(iOS 11.0, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return _collectionView;
}
@end

View File

@@ -0,0 +1,20 @@
//
// KBVipSubscribeCell.h
// keyBoard
//
// 订阅选项 Cell右侧选择按钮pay_circle_normal / pay_circle_sel
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface KBVipSubscribeCell : UICollectionViewCell
/// 配置展示文案
- (void)configTitle:(NSString *)title price:(NSString *)price strike:(nullable NSString *)strike;
/// 同步选中态(更新右侧选择图标、边框颜色)
- (void)applySelected:(BOOL)selected animated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,162 @@
//
// KBVipSubscribeCell.m
// keyBoard
//
// 使 mas +
//
#import "KBVipSubscribeCell.h"
@interface KBVipSubscribeCell ()
@property (nonatomic, strong) UIView *cardView; //
@property (nonatomic, strong) UILabel *titleLabel; // Monthly Subscription
@property (nonatomic, strong) UILabel *priceLabel; // $4.49
@property (nonatomic, strong) UILabel *strikeLabel; // 线
@property (nonatomic, strong) UIButton *selectButton; //
@property (nonatomic, strong) CAShapeLayer *borderLayer; //
@end
@implementation KBVipSubscribeCell
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.contentView.backgroundColor = [UIColor clearColor];
[self.contentView addSubview:self.cardView];
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
[self.cardView addSubview:self.titleLabel];
[self.cardView addSubview:self.priceLabel];
[self.cardView addSubview:self.strikeLabel];
[self.cardView addSubview:self.selectButton];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.cardView).offset(16);
make.top.equalTo(self.cardView).offset(16);
}];
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.titleLabel);
make.top.equalTo(self.titleLabel.mas_bottom).offset(6);
}];
[self.strikeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.priceLabel.mas_right).offset(10);
make.centerY.equalTo(self.priceLabel);
}];
[self.selectButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.cardView);
make.right.equalTo(self.cardView).offset(-16);
make.width.height.mas_equalTo(28);
}];
// 绿
self.borderLayer = [CAShapeLayer layer];
self.borderLayer.strokeColor = [UIColor colorWithWhite:0.9 alpha:1.0].CGColor;
// 使
self.borderLayer.fillColor = UIColor.clearColor.CGColor;
self.borderLayer.lineWidth = 1.5;
// label/button
[self.cardView.layer insertSublayer:self.borderLayer atIndex:0];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat radius = 16;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.cardView.bounds cornerRadius:radius];
self.cardView.layer.cornerRadius = radius;
self.cardView.layer.masksToBounds = YES;
self.borderLayer.frame = self.cardView.bounds;
self.borderLayer.path = path.CGPath;
}
- (void)prepareForReuse {
[super prepareForReuse];
[self applySelected:NO animated:NO];
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
[self applySelected:selected animated:NO];
}
- (void)configTitle:(NSString *)title price:(NSString *)price strike:(nullable NSString *)strike {
self.titleLabel.text = title.length ? title : @"Monthly Subscription";
self.priceLabel.text = price.length ? price : @"$4.49";
self.strikeLabel.hidden = (strike.length == 0);
if (strike.length) {
// 线
NSDictionary *attr = @{
NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle),
NSForegroundColorAttributeName: [UIColor colorWithWhite:0.7 alpha:1.0]
};
self.strikeLabel.attributedText = [[NSAttributedString alloc] initWithString:strike attributes:attr];
}
}
- (void)applySelected:(BOOL)selected animated:(BOOL)animated {
UIImage *img = [UIImage imageNamed:(selected ? @"pay_circle_sel" : @"pay_circle_normal")];
[self.selectButton setImage:img forState:UIControlStateNormal];
CGColorRef color = (selected ? [UIColor colorWithHex:KBColorValue].CGColor : [UIColor colorWithWhite:0.9 alpha:1.0].CGColor);
void (^changes)(void) = ^{
self.borderLayer.strokeColor = color;
self.cardView.layer.shadowOpacity = selected ? 0.12 : 0.0;
self.cardView.layer.shadowColor = [UIColor colorWithHex:KBColorValue].CGColor;
self.cardView.layer.shadowRadius = selected ? 8 : 0;
self.cardView.layer.shadowOffset = CGSizeMake(0, selected ? 4 : 0);
};
if (animated) {
[UIView animateWithDuration:0.15 animations:changes];
} else {
changes();
}
}
#pragma mark - Lazy
- (UIView *)cardView {
if (!_cardView) {
_cardView = [UIView new];
_cardView.backgroundColor = [UIColor whiteColor];
}
return _cardView;
}
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.text = @"Monthly Subscription";
_titleLabel.textColor = [UIColor colorWithHex:KBBlackValue];
_titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
}
return _titleLabel;
}
- (UILabel *)priceLabel {
if (!_priceLabel) {
_priceLabel = [UILabel new];
_priceLabel.text = @"$4.49";
_priceLabel.textColor = [UIColor colorWithHex:KBBlackValue];
_priceLabel.font = [UIFont systemFontOfSize:28 weight:UIFontWeightBold];
}
return _priceLabel;
}
- (UILabel *)strikeLabel {
if (!_strikeLabel) {
_strikeLabel = [UILabel new];
_strikeLabel.text = @"$4.49";
_strikeLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
_strikeLabel.textColor = [UIColor colorWithWhite:0.7 alpha:1.0];
}
return _strikeLabel;
}
- (UIButton *)selectButton {
if (!_selectButton) {
_selectButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_selectButton setImage:[UIImage imageNamed:@"pay_circle_normal"] forState:UIControlStateNormal];
_selectButton.userInteractionEnabled = NO; //
_selectButton.contentMode = UIViewContentModeCenter;
}
return _selectButton;
}
@end

View File

@@ -9,8 +9,8 @@
NS_ASSUME_NONNULL_BEGIN
/// VIP 订阅页(整体使用 UICollectionView上下滚动
@interface KBVipPay : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -6,8 +6,19 @@
//
#import "KBVipPay.h"
#import "KBVipPayHeaderView.h"
#import "KBVipSubscribeCell.h"
#import "KBVipReviewListCell.h"
@interface KBVipPay ()
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; //
@end
@@ -15,17 +26,168 @@
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//
// self.kb_titleLabel.text = @"VIP";
// self.kb_navView.backgroundColor = [UIColor clearColor];
self.view.backgroundColor = [UIColor colorWithWhite:0.97 alpha:1.0];
//
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.closeButton];
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT);
make.left.equalTo(self.view).offset(15);
make.width.height.mas_equalTo(36);
}];
//
[self.view addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.view);
make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT);
}];
[self.collectionView reloadData];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
- (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 - UICollectionView DataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
// 012
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, 106);
} else {
return CGSizeMake(w, 140);
}
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
if (section == 0) {
// = 484 +
return CGSizeMake(KB_SCREEN_WIDTH, 484 + KB_NAV_TOTAL_HEIGHT);
}
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) {
return UIEdgeInsetsMake(0, 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;
_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:@"white_close_icon"] forState:UIControlStateNormal];
[_closeButton addTarget:self action:@selector(onTapClose) forControlEvents:UIControlEventTouchUpInside];
}
return _closeButton;
}
@end