Compare commits
2 Commits
8b177e5fad
...
ceaeb5c951
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ceaeb5c951 | ||
![]() |
e8d59495a4 |
@@ -425,6 +425,9 @@
|
|||||||
4C06428B2E98DC5F00BAF413 /* EPTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06428A2E98DC5F00BAF413 /* EPTabBarController.swift */; };
|
4C06428B2E98DC5F00BAF413 /* EPTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06428A2E98DC5F00BAF413 /* EPTabBarController.swift */; };
|
||||||
4C06428E2E98DC7E00BAF413 /* EPMineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C06428D2E98DC7E00BAF413 /* EPMineViewController.m */; };
|
4C06428E2E98DC7E00BAF413 /* EPMineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C06428D2E98DC7E00BAF413 /* EPMineViewController.m */; };
|
||||||
4C0642912E98DC8700BAF413 /* EPMomentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0642902E98DC8700BAF413 /* EPMomentViewController.m */; };
|
4C0642912E98DC8700BAF413 /* EPMomentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0642902E98DC8700BAF413 /* EPMomentViewController.m */; };
|
||||||
|
4C0642962E98F76F00BAF413 /* EPMomentAPIHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0642942E98F76F00BAF413 /* EPMomentAPIHelper.m */; };
|
||||||
|
4C0642992E98F77900BAF413 /* EPMomentListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0642982E98F77900BAF413 /* EPMomentListView.m */; };
|
||||||
|
4C06429C2E99120600BAF413 /* EPMomentPublishViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C06429B2E99120600BAF413 /* EPMomentPublishViewController.m */; };
|
||||||
4C0A5B842E02675300955219 /* MedalsCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B832E02675300955219 /* MedalsCollectionViewCell.m */; };
|
4C0A5B842E02675300955219 /* MedalsCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B832E02675300955219 /* MedalsCollectionViewCell.m */; };
|
||||||
4C0A5B872E02BB1100955219 /* MedalsLevelIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B862E02BB1100955219 /* MedalsLevelIndicatorView.m */; };
|
4C0A5B872E02BB1100955219 /* MedalsLevelIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B862E02BB1100955219 /* MedalsLevelIndicatorView.m */; };
|
||||||
4C0A5B8A2E02BC3900955219 /* MedalsDetailView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B892E02BC3900955219 /* MedalsDetailView.m */; };
|
4C0A5B8A2E02BC3900955219 /* MedalsDetailView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A5B892E02BC3900955219 /* MedalsDetailView.m */; };
|
||||||
@@ -2475,6 +2478,12 @@
|
|||||||
4C06428D2E98DC7E00BAF413 /* EPMineViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMineViewController.m; sourceTree = "<group>"; };
|
4C06428D2E98DC7E00BAF413 /* EPMineViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMineViewController.m; sourceTree = "<group>"; };
|
||||||
4C06428F2E98DC8700BAF413 /* EPMomentViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentViewController.h; sourceTree = "<group>"; };
|
4C06428F2E98DC8700BAF413 /* EPMomentViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentViewController.h; sourceTree = "<group>"; };
|
||||||
4C0642902E98DC8700BAF413 /* EPMomentViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentViewController.m; sourceTree = "<group>"; };
|
4C0642902E98DC8700BAF413 /* EPMomentViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentViewController.m; sourceTree = "<group>"; };
|
||||||
|
4C0642932E98F76F00BAF413 /* EPMomentAPIHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentAPIHelper.h; sourceTree = "<group>"; };
|
||||||
|
4C0642942E98F76F00BAF413 /* EPMomentAPIHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentAPIHelper.m; sourceTree = "<group>"; };
|
||||||
|
4C0642972E98F77900BAF413 /* EPMomentListView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentListView.h; sourceTree = "<group>"; };
|
||||||
|
4C0642982E98F77900BAF413 /* EPMomentListView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentListView.m; sourceTree = "<group>"; };
|
||||||
|
4C06429A2E99120600BAF413 /* EPMomentPublishViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EPMomentPublishViewController.h; sourceTree = "<group>"; };
|
||||||
|
4C06429B2E99120600BAF413 /* EPMomentPublishViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EPMomentPublishViewController.m; sourceTree = "<group>"; };
|
||||||
4C0A5B822E02675300955219 /* MedalsCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MedalsCollectionViewCell.h; sourceTree = "<group>"; };
|
4C0A5B822E02675300955219 /* MedalsCollectionViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MedalsCollectionViewCell.h; sourceTree = "<group>"; };
|
||||||
4C0A5B832E02675300955219 /* MedalsCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MedalsCollectionViewCell.m; sourceTree = "<group>"; };
|
4C0A5B832E02675300955219 /* MedalsCollectionViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MedalsCollectionViewCell.m; sourceTree = "<group>"; };
|
||||||
4C0A5B852E02BB1100955219 /* MedalsLevelIndicatorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MedalsLevelIndicatorView.h; sourceTree = "<group>"; };
|
4C0A5B852E02BB1100955219 /* MedalsLevelIndicatorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MedalsLevelIndicatorView.h; sourceTree = "<group>"; };
|
||||||
@@ -6523,6 +6532,8 @@
|
|||||||
4C0642782E97BD6D00BAF413 /* Controllers */ = {
|
4C0642782E97BD6D00BAF413 /* Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C06429A2E99120600BAF413 /* EPMomentPublishViewController.h */,
|
||||||
|
4C06429B2E99120600BAF413 /* EPMomentPublishViewController.m */,
|
||||||
4C06428F2E98DC8700BAF413 /* EPMomentViewController.h */,
|
4C06428F2E98DC8700BAF413 /* EPMomentViewController.h */,
|
||||||
4C0642902E98DC8700BAF413 /* EPMomentViewController.m */,
|
4C0642902E98DC8700BAF413 /* EPMomentViewController.m */,
|
||||||
);
|
);
|
||||||
@@ -6532,6 +6543,8 @@
|
|||||||
4C06427B2E97BD6D00BAF413 /* Views */ = {
|
4C06427B2E97BD6D00BAF413 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C0642972E98F77900BAF413 /* EPMomentListView.h */,
|
||||||
|
4C0642982E98F77900BAF413 /* EPMomentListView.m */,
|
||||||
4C0642792E97BD6D00BAF413 /* EPMomentCell.h */,
|
4C0642792E97BD6D00BAF413 /* EPMomentCell.h */,
|
||||||
4C06427A2E97BD6D00BAF413 /* EPMomentCell.m */,
|
4C06427A2E97BD6D00BAF413 /* EPMomentCell.m */,
|
||||||
);
|
);
|
||||||
@@ -6541,6 +6554,7 @@
|
|||||||
4C06427C2E97BD6D00BAF413 /* NewMoments */ = {
|
4C06427C2E97BD6D00BAF413 /* NewMoments */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C0642952E98F76F00BAF413 /* Services */,
|
||||||
4C0642782E97BD6D00BAF413 /* Controllers */,
|
4C0642782E97BD6D00BAF413 /* Controllers */,
|
||||||
4C06427B2E97BD6D00BAF413 /* Views */,
|
4C06427B2E97BD6D00BAF413 /* Views */,
|
||||||
);
|
);
|
||||||
@@ -6565,6 +6579,15 @@
|
|||||||
path = "E-P";
|
path = "E-P";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4C0642952E98F76F00BAF413 /* Services */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4C0642932E98F76F00BAF413 /* EPMomentAPIHelper.h */,
|
||||||
|
4C0642942E98F76F00BAF413 /* EPMomentAPIHelper.m */,
|
||||||
|
);
|
||||||
|
path = Services;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4C45C1A82E6837BF00E73A44 /* Manager */ = {
|
4C45C1A82E6837BF00E73A44 /* Manager */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -12232,6 +12255,7 @@
|
|||||||
E83ABF03280EC90C00322EE4 /* ContentApplicationShareModel.m in Sources */,
|
E83ABF03280EC90C00322EE4 /* ContentApplicationShareModel.m in Sources */,
|
||||||
4C7153952E0942F700C9F940 /* MedalsCyclePagerCell.m in Sources */,
|
4C7153952E0942F700C9F940 /* MedalsCyclePagerCell.m in Sources */,
|
||||||
9BA812E028BF6ABB00783EA7 /* Api+RedPacket.m in Sources */,
|
9BA812E028BF6ABB00783EA7 /* Api+RedPacket.m in Sources */,
|
||||||
|
4C0642962E98F76F00BAF413 /* EPMomentAPIHelper.m in Sources */,
|
||||||
9BA3B40F293DD2F90071DF1C /* XPUpgradeView.m in Sources */,
|
9BA3B40F293DD2F90071DF1C /* XPUpgradeView.m in Sources */,
|
||||||
E81A654928351B9500F55894 /* XPMomentsRecommendHeaderView.m in Sources */,
|
E81A654928351B9500F55894 /* XPMomentsRecommendHeaderView.m in Sources */,
|
||||||
E84B0E3F2727EDF6008818C6 /* XPRoomMessageTableViewCell.m in Sources */,
|
E84B0E3F2727EDF6008818C6 /* XPRoomMessageTableViewCell.m in Sources */,
|
||||||
@@ -12989,6 +13013,7 @@
|
|||||||
2305F3472AD94E9D00AD403C /* XPMaskManagerCell.m in Sources */,
|
2305F3472AD94E9D00AD403C /* XPMaskManagerCell.m in Sources */,
|
||||||
E852D74428633A08001465ED /* MonentsCommentModel.m in Sources */,
|
E852D74428633A08001465ED /* MonentsCommentModel.m in Sources */,
|
||||||
E8C1CD6D27D8938C00376F83 /* XPRoomFaceTitleCollectionViewCell.m in Sources */,
|
E8C1CD6D27D8938C00376F83 /* XPRoomFaceTitleCollectionViewCell.m in Sources */,
|
||||||
|
4C06429C2E99120600BAF413 /* EPMomentPublishViewController.m in Sources */,
|
||||||
548E01C62C3F78360071C83D /* FeedBackViewController.m in Sources */,
|
548E01C62C3F78360071C83D /* FeedBackViewController.m in Sources */,
|
||||||
E8C1CD7627D8AE3D00376F83 /* XPRoomFacePresenter.m in Sources */,
|
E8C1CD7627D8AE3D00376F83 /* XPRoomFacePresenter.m in Sources */,
|
||||||
E85E7B362A4EB0D300B6D00A /* XPClanRoomCollectionViewCell.m in Sources */,
|
E85E7B362A4EB0D300B6D00A /* XPClanRoomCollectionViewCell.m in Sources */,
|
||||||
@@ -13189,6 +13214,7 @@
|
|||||||
189DD58F26DF97E700AB55B1 /* LoginPresenter.m in Sources */,
|
189DD58F26DF97E700AB55B1 /* LoginPresenter.m in Sources */,
|
||||||
E88863C9278EBA43004BCFAB /* XPAcrossRoomPKForceEndResultView.m in Sources */,
|
E88863C9278EBA43004BCFAB /* XPAcrossRoomPKForceEndResultView.m in Sources */,
|
||||||
E8F63CB7298B566D00B338BA /* XPSessionSayHelloPresenter.m in Sources */,
|
E8F63CB7298B566D00B338BA /* XPSessionSayHelloPresenter.m in Sources */,
|
||||||
|
4C0642992E98F77900BAF413 /* EPMomentListView.m in Sources */,
|
||||||
E88C72952828F1AD0047FB2B /* XPRoomMusicLibraryViewController.m in Sources */,
|
E88C72952828F1AD0047FB2B /* XPRoomMusicLibraryViewController.m in Sources */,
|
||||||
233423D32AAF0F4F00B1253F /* XPIAPRechargeHeadCell.m in Sources */,
|
233423D32AAF0F4F00B1253F /* XPIAPRechargeHeadCell.m in Sources */,
|
||||||
E85E7BA62A4EC99300B6D00A /* XPMineGiveDiamondSearchView.m in Sources */,
|
E85E7BA62A4EC99300B6D00A /* XPMineGiveDiamondSearchView.m in Sources */,
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// EPMomentPublishViewController.h
|
||||||
|
// YuMi
|
||||||
|
//
|
||||||
|
// Created by AI on 2025-10-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/// EP 版:图文发布页面
|
||||||
|
@interface EPMomentPublishViewController : UIViewController
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
|
206
YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.m
Normal file
206
YuMi/E-P/NewMoments/Controllers/EPMomentPublishViewController.m
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
//
|
||||||
|
// EPMomentPublishViewController.m
|
||||||
|
// YuMi
|
||||||
|
//
|
||||||
|
// Created by AI on 2025-10-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "EPMomentPublishViewController.h"
|
||||||
|
#import <Masonry/Masonry.h>
|
||||||
|
#import <TZImagePickerController/TZImagePickerController.h>
|
||||||
|
#import "DJDKMIMOMColor.h"
|
||||||
|
#import "SZTextView.h"
|
||||||
|
|
||||||
|
@interface EPMomentPublishViewController () <UICollectionViewDataSource, UICollectionViewDelegate, TZImagePickerControllerDelegate, UITextViewDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, strong) UIView *navView;
|
||||||
|
@property (nonatomic, strong) UIButton *backButton;
|
||||||
|
@property (nonatomic, strong) UILabel *titleLabel;
|
||||||
|
@property (nonatomic, strong) UIButton *publishButton;
|
||||||
|
|
||||||
|
@property (nonatomic, strong) UIView *contentView;
|
||||||
|
@property (nonatomic, strong) SZTextView *textView;
|
||||||
|
@property (nonatomic, strong) UILabel *limitLabel;
|
||||||
|
@property (nonatomic, strong) UIView *lineView;
|
||||||
|
@property (nonatomic, strong) UICollectionView *collectionView;
|
||||||
|
@property (nonatomic, strong) NSMutableArray<UIImage *> *images;
|
||||||
|
@property (nonatomic, strong) NSMutableArray *selectedAssets; // TZImagePicker 已选资源
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation EPMomentPublishViewController
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
self.view.backgroundColor = [UIColor colorWithRed:0x0C/255.0 green:0x05/255.0 blue:0x27/255.0 alpha:1.0];
|
||||||
|
[self setupUI];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setupUI {
|
||||||
|
[self.view addSubview:self.navView];
|
||||||
|
[self.view addSubview:self.contentView];
|
||||||
|
[self.navView addSubview:self.backButton];
|
||||||
|
[self.navView addSubview:self.titleLabel];
|
||||||
|
// 发布按钮移到底部
|
||||||
|
[self.contentView addSubview:self.textView];
|
||||||
|
[self.contentView addSubview:self.limitLabel];
|
||||||
|
[self.contentView addSubview:self.lineView];
|
||||||
|
[self.contentView addSubview:self.collectionView];
|
||||||
|
[self.contentView addSubview:self.publishButton];
|
||||||
|
|
||||||
|
[self.navView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.leading.trailing.top.equalTo(self.view);
|
||||||
|
make.height.mas_equalTo(kNavigationHeight);
|
||||||
|
}];
|
||||||
|
[self.backButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.leading.equalTo(self.view).offset(10);
|
||||||
|
make.top.mas_equalTo(statusbarHeight);
|
||||||
|
make.size.mas_equalTo(CGSizeMake(44, 44));
|
||||||
|
}];
|
||||||
|
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.centerX.equalTo(self.navView);
|
||||||
|
make.centerY.equalTo(self.backButton);
|
||||||
|
}];
|
||||||
|
// 发布按钮约束移到底部
|
||||||
|
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.leading.trailing.equalTo(self.view);
|
||||||
|
make.top.equalTo(self.navView.mas_bottom);
|
||||||
|
make.bottom.equalTo(self.view);
|
||||||
|
}];
|
||||||
|
[self.textView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.leading.trailing.equalTo(self.contentView).inset(15);
|
||||||
|
make.top.equalTo(self.contentView).offset(10);
|
||||||
|
make.height.mas_equalTo(150);
|
||||||
|
}];
|
||||||
|
[self.limitLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.top.equalTo(self.textView.mas_bottom).offset(5);
|
||||||
|
make.trailing.equalTo(self.textView);
|
||||||
|
}];
|
||||||
|
[self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.top.equalTo(self.limitLabel.mas_bottom).offset(10);
|
||||||
|
make.leading.trailing.equalTo(self.textView);
|
||||||
|
make.height.mas_equalTo(1);
|
||||||
|
}];
|
||||||
|
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.leading.trailing.equalTo(self.contentView).inset(15);
|
||||||
|
make.top.equalTo(self.lineView.mas_bottom).offset(10);
|
||||||
|
make.height.mas_equalTo(110);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 底部发布按钮
|
||||||
|
[self.publishButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.leading.trailing.equalTo(self.view).inset(20);
|
||||||
|
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-20);
|
||||||
|
make.height.mas_equalTo(50);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
- (void)onBack {
|
||||||
|
[self dismissViewControllerAnimated:YES completion:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)onPublish {
|
||||||
|
// TODO: 挂接实际发布逻辑
|
||||||
|
[self dismissViewControllerAnimated:YES completion:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UICollectionView
|
||||||
|
|
||||||
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||||
|
return self.images.count + 1; // 最后一个是添加按钮
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ep.publish.cell" forIndexPath:indexPath];
|
||||||
|
cell.contentView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.06];
|
||||||
|
cell.contentView.layer.cornerRadius = 12;
|
||||||
|
// 清空复用子视图,避免加号被覆盖
|
||||||
|
for (UIView *sub in cell.contentView.subviews) { [sub removeFromSuperview]; }
|
||||||
|
BOOL showAdd = (self.images.count < 9) && (indexPath.item == self.images.count);
|
||||||
|
if (showAdd) {
|
||||||
|
UIImageView *iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mine_user_info_album_add"]];
|
||||||
|
[cell.contentView addSubview:iv];
|
||||||
|
[iv mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(cell.contentView); make.size.mas_equalTo(CGSizeMake(24, 24)); }];
|
||||||
|
} else {
|
||||||
|
UIImageView *iv = [[UIImageView alloc] init];
|
||||||
|
iv.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
iv.layer.masksToBounds = YES;
|
||||||
|
[cell.contentView addSubview:iv];
|
||||||
|
[iv mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(cell.contentView); }];
|
||||||
|
NSInteger idx = MIN(indexPath.item, (NSInteger)self.images.count - 1);
|
||||||
|
if (idx >= 0 && idx < self.images.count) iv.image = self.images[idx];
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
if (indexPath.item == self.images.count) {
|
||||||
|
TZImagePickerController *picker = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self];
|
||||||
|
picker.allowPickingVideo = NO;
|
||||||
|
picker.allowTakeVideo = NO;
|
||||||
|
picker.selectedAssets = self.selectedAssets; // 预选
|
||||||
|
picker.maxImagesCount = 9; // 总上限
|
||||||
|
[self presentViewController:picker animated:YES completion:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - TZImagePickerControllerDelegate
|
||||||
|
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto infos:(NSArray<NSDictionary *> *)infos {
|
||||||
|
// 合并选择:在已有基础上追加,最多 9 张
|
||||||
|
for (NSInteger i = 0; i < assets.count; i++) {
|
||||||
|
id asset = assets[i];
|
||||||
|
UIImage *img = [photos xpSafeObjectAtIndex:i] ?: photos[i];
|
||||||
|
if (![self.selectedAssets containsObject:asset] && self.images.count < 9) {
|
||||||
|
[self.selectedAssets addObject:asset];
|
||||||
|
[self.images addObject:img];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[self.collectionView reloadData];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITextViewDelegate
|
||||||
|
- (void)textViewDidChange:(UITextView *)textView {
|
||||||
|
if (textView.text.length > 500) {
|
||||||
|
textView.text = [textView.text substringToIndex:500];
|
||||||
|
}
|
||||||
|
self.limitLabel.text = [NSString stringWithFormat:@"%lu/500", (unsigned long)textView.text.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Lazy
|
||||||
|
|
||||||
|
- (UIView *)navView { if (!_navView) { _navView = [UIView new]; _navView.backgroundColor = [UIColor clearColor]; } return _navView; }
|
||||||
|
- (UIButton *)backButton { if (!_backButton) { _backButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_backButton setImage:[UIImage imageNamed:@"common_nav_back"] forState:UIControlStateNormal]; [_backButton addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside]; } return _backButton; }
|
||||||
|
- (UILabel *)titleLabel { if (!_titleLabel) { _titleLabel = [UILabel new]; _titleLabel.text = @"图文发布"; _titleLabel.textColor = [DJDKMIMOMColor mainTextColor]; _titleLabel.font = [UIFont systemFontOfSize:17]; } return _titleLabel; }
|
||||||
|
- (UIButton *)publishButton {
|
||||||
|
if (!_publishButton) {
|
||||||
|
_publishButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
[_publishButton setTitle:@"发布" forState:UIControlStateNormal];
|
||||||
|
[_publishButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||||
|
_publishButton.titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium];
|
||||||
|
_publishButton.layer.cornerRadius = 25;
|
||||||
|
_publishButton.layer.masksToBounds = YES;
|
||||||
|
// 渐变背景:从浅紫到深紫
|
||||||
|
CAGradientLayer *gradient = [CAGradientLayer layer];
|
||||||
|
gradient.colors = @[(__bridge id)[UIColor colorWithRed:0.6 green:0.3 blue:0.8 alpha:1.0].CGColor,
|
||||||
|
(__bridge id)[UIColor colorWithRed:0.3 green:0.1 blue:0.5 alpha:1.0].CGColor];
|
||||||
|
gradient.startPoint = CGPointMake(0, 0);
|
||||||
|
gradient.endPoint = CGPointMake(1, 0);
|
||||||
|
gradient.frame = CGRectMake(0, 0, 1, 1);
|
||||||
|
[_publishButton.layer insertSublayer:gradient atIndex:0];
|
||||||
|
[_publishButton addTarget:self action:@selector(onPublish) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
}
|
||||||
|
return _publishButton;
|
||||||
|
}
|
||||||
|
- (UIView *)contentView { if (!_contentView) { _contentView = [UIView new]; _contentView.backgroundColor = [UIColor clearColor]; } return _contentView; }
|
||||||
|
- (SZTextView *)textView { if (!_textView) { _textView = [SZTextView new]; _textView.placeholder = @"Enter Content"; _textView.textColor = [DJDKMIMOMColor mainTextColor]; _textView.placeholderTextColor = [DJDKMIMOMColor secondTextColor]; _textView.font = [UIFont systemFontOfSize:15]; _textView.delegate = self; } return _textView; }
|
||||||
|
- (UILabel *)limitLabel { if (!_limitLabel) { _limitLabel = [UILabel new]; _limitLabel.text = @"0/500"; _limitLabel.textColor = [DJDKMIMOMColor mainTextColor]; _limitLabel.font = [UIFont systemFontOfSize:12]; } return _limitLabel; }
|
||||||
|
- (UIView *)lineView { if (!_lineView) { _lineView = [UIView new]; _lineView.backgroundColor = [DJDKMIMOMColor dividerColor]; } return _lineView; }
|
||||||
|
- (UICollectionView *)collectionView { if (!_collectionView) { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.minimumLineSpacing = 10; layout.minimumInteritemSpacing = 10; CGFloat itemW = (KScreenWidth - 15*2 - 10*2)/3.0; layout.itemSize = CGSizeMake(itemW, itemW); _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.delegate = self; _collectionView.dataSource = self; _collectionView.backgroundColor = [UIColor clearColor]; [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"ep.publish.cell"]; } return _collectionView; }
|
||||||
|
- (NSMutableArray<UIImage *> *)images { if (!_images) { _images = [NSMutableArray array]; } return _images; }
|
||||||
|
- (NSMutableArray *)selectedAssets { if (!_selectedAssets) { _selectedAssets = [NSMutableArray array]; } return _selectedAssets; }
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
@@ -7,34 +7,21 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "EPMomentViewController.h"
|
#import "EPMomentViewController.h"
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <Masonry/Masonry.h>
|
||||||
#import "EPMomentCell.h"
|
#import "EPMomentCell.h"
|
||||||
#import "Api+Moments.h"
|
#import "EPMomentListView.h"
|
||||||
#import "AccountInfoStorage.h"
|
#import "EPMomentPublishViewController.h"
|
||||||
#import "MomentsInfoModel.h"
|
|
||||||
|
|
||||||
@interface EPMomentViewController () <UITableViewDelegate, UITableViewDataSource>
|
@interface EPMomentViewController ()
|
||||||
|
|
||||||
// MARK: - UI Components
|
// MARK: - UI Components
|
||||||
|
|
||||||
/// 主列表(卡片式布局)
|
/// 列表视图(MVVM:View)
|
||||||
@property (nonatomic, strong) UITableView *tableView;
|
@property (nonatomic, strong) EPMomentListView *listView;
|
||||||
|
|
||||||
/// 刷新控件
|
/// 顶部固定文案
|
||||||
@property (nonatomic, strong) UIRefreshControl *refreshControl;
|
@property (nonatomic, strong) UILabel *topTipLabel;
|
||||||
|
|
||||||
/// 发布按钮
|
|
||||||
@property (nonatomic, strong) UIButton *publishButton;
|
|
||||||
|
|
||||||
// MARK: - Data
|
|
||||||
|
|
||||||
/// 动态数据源(MomentsInfoModel 数组)
|
|
||||||
@property (nonatomic, strong) NSMutableArray<MomentsInfoModel *> *dataSource;
|
|
||||||
|
|
||||||
/// 当前页码
|
|
||||||
@property (nonatomic, assign) NSInteger currentPage;
|
|
||||||
|
|
||||||
/// 是否正在加载
|
|
||||||
@property (nonatomic, assign) BOOL isLoading;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -48,7 +35,7 @@
|
|||||||
self.title = @"动态";
|
self.title = @"动态";
|
||||||
|
|
||||||
[self setupUI];
|
[self setupUI];
|
||||||
[self loadData];
|
[self.listView reloadFirstPage];
|
||||||
|
|
||||||
NSLog(@"[EPMomentViewController] 页面加载完成");
|
NSLog(@"[EPMomentViewController] 页面加载完成");
|
||||||
}
|
}
|
||||||
@@ -74,77 +61,37 @@
|
|||||||
make.edges.mas_equalTo(self.view);
|
make.edges.mas_equalTo(self.view);
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// TableView
|
// 顶部固定文案
|
||||||
[self.view addSubview:self.tableView];
|
[self.view addSubview:self.topTipLabel];
|
||||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
[self.topTipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
make.edges.equalTo(self.view);
|
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(8);
|
||||||
|
make.left.equalTo(self.view).offset(20);
|
||||||
|
make.right.equalTo(self.view).offset(-20);
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// TODO: 调整为右上角
|
// 列表视图
|
||||||
[self.view addSubview:self.publishButton];
|
[self.view addSubview:self.listView];
|
||||||
[self.publishButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
[self.listView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
make.right.equalTo(self.view).offset(-20);
|
make.left.right.bottom.equalTo(self.view);
|
||||||
make.bottom.equalTo(self.view).offset(-100); // 避开 TabBar
|
make.top.equalTo(self.topTipLabel.mas_bottom).offset(8);
|
||||||
make.size.mas_equalTo(CGSizeMake(56, 56));
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
// 右上角发布按钮
|
||||||
|
UIBarButtonItem *publishItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(onPublishButtonTapped)];
|
||||||
|
self.navigationItem.rightBarButtonItem = publishItem;
|
||||||
|
|
||||||
NSLog(@"[EPMomentViewController] UI 设置完成");
|
NSLog(@"[EPMomentViewController] UI 设置完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Data Loading
|
// 不再在 VC 内部直接发请求/维护分页
|
||||||
|
|
||||||
- (void)loadData {
|
|
||||||
if (self.isLoading) return;
|
|
||||||
|
|
||||||
self.isLoading = YES;
|
|
||||||
NSLog(@"[EPMomentViewController] 开始加载数据,页码: %ld", (long)self.currentPage);
|
|
||||||
|
|
||||||
// 调用真实 API 加载动态列表
|
|
||||||
NSString *page = [NSString stringWithFormat:@"%ld", (long)self.currentPage];
|
|
||||||
NSString *pageSize = @"10";
|
|
||||||
NSString *types = @"0,2"; // 类型:0=图片,2=文字
|
|
||||||
|
|
||||||
@kWeakify(self);
|
|
||||||
[Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
|
||||||
@kStrongify(self);
|
|
||||||
self.isLoading = NO;
|
|
||||||
[self.refreshControl endRefreshing];
|
|
||||||
|
|
||||||
if (code == 200 && data.data) {
|
|
||||||
// 解析数据
|
|
||||||
NSArray *list = [MomentsInfoModel mj_objectArrayWithKeyValuesArray:data.data];
|
|
||||||
|
|
||||||
if (list.count > 0) {
|
|
||||||
[self.dataSource addObjectsFromArray:list];
|
|
||||||
self.currentPage++;
|
|
||||||
[self.tableView reloadData];
|
|
||||||
NSLog(@"[EPMomentViewController] 加载成功,新增 %lu 条动态", (unsigned long)list.count);
|
|
||||||
} else {
|
|
||||||
NSLog(@"[EPMomentViewController] 没有更多数据");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NSLog(@"[EPMomentViewController] 加载失败: code=%ld, msg=%@", (long)code, msg);
|
|
||||||
// 如果 API 失败,显示提示
|
|
||||||
if (self.dataSource.count == 0) {
|
|
||||||
// 首次加载失败,显示空状态
|
|
||||||
[self showAlertWithMessage:msg ?: @"加载失败"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} page:page pageSize:pageSize types:types];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)onRefresh {
|
|
||||||
self.currentPage = 0;
|
|
||||||
[self.dataSource removeAllObjects];
|
|
||||||
[self loadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
- (void)onPublishButtonTapped {
|
- (void)onPublishButtonTapped {
|
||||||
NSLog(@"[EPMomentViewController] 发布按钮点击");
|
NSLog(@"[EPMomentViewController] 发布按钮点击");
|
||||||
// TODO: 跳转到发布页面
|
EPMomentPublishViewController *vc = [[EPMomentPublishViewController alloc] init];
|
||||||
[self showAlertWithMessage:@"发布功能开发中"];
|
vc.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||||
|
[self.navigationController presentViewController:vc animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)showAlertWithMessage:(NSString *)message {
|
- (void)showAlertWithMessage:(NSString *)message {
|
||||||
@@ -155,108 +102,35 @@
|
|||||||
[self presentViewController:alert animated:YES completion:nil];
|
[self presentViewController:alert animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDataSource
|
// 列表点击回调由 listView 暴露
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
|
||||||
return self.dataSource.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
||||||
EPMomentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewMomentCell" forIndexPath:indexPath];
|
|
||||||
|
|
||||||
if (indexPath.row < self.dataSource.count) {
|
|
||||||
MomentsInfoModel *model = self.dataSource[indexPath.row];
|
|
||||||
[cell configureWithModel:model];
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
|
||||||
|
|
||||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
||||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
|
||||||
|
|
||||||
NSLog(@"[EPMomentViewController] 点击动态: %ld", (long)indexPath.row);
|
|
||||||
// TODO: 跳转到详情页
|
|
||||||
[self showAlertWithMessage:[NSString stringWithFormat:@"点击了第 %ld 条动态", (long)indexPath.row]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
||||||
return UITableViewAutomaticDimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
||||||
return 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 滚动到底部时加载更多
|
|
||||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
|
||||||
CGFloat offsetY = scrollView.contentOffset.y;
|
|
||||||
CGFloat contentHeight = scrollView.contentSize.height;
|
|
||||||
CGFloat screenHeight = scrollView.frame.size.height;
|
|
||||||
|
|
||||||
if (offsetY > contentHeight - screenHeight - 100 && !self.isLoading) {
|
|
||||||
[self loadData];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Lazy Loading
|
// MARK: - Lazy Loading
|
||||||
|
|
||||||
- (UITableView *)tableView {
|
// Lazy
|
||||||
if (!_tableView) {
|
|
||||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
- (EPMomentListView *)listView {
|
||||||
_tableView.delegate = self;
|
if (!_listView) {
|
||||||
_tableView.dataSource = self;
|
_listView = [[EPMomentListView alloc] initWithFrame:CGRectZero];
|
||||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
__weak typeof(self) weakSelf = self;
|
||||||
_tableView.backgroundColor = [UIColor clearColor]; // 透明背景,显示下层背景图
|
_listView.onSelectMoment = ^(NSInteger index) {
|
||||||
_tableView.estimatedRowHeight = 200;
|
__strong typeof(weakSelf) self = weakSelf;
|
||||||
_tableView.rowHeight = UITableViewAutomaticDimension;
|
[self showAlertWithMessage:[NSString stringWithFormat:@"点击了第 %ld 条动态", (long)index]];
|
||||||
_tableView.showsVerticalScrollIndicator = NO;
|
};
|
||||||
_tableView.contentInset = UIEdgeInsetsMake(10, 0, 10, 0);
|
|
||||||
|
|
||||||
// 注册 Cell
|
|
||||||
[_tableView registerClass:[EPMomentCell class] forCellReuseIdentifier:@"NewMomentCell"];
|
|
||||||
|
|
||||||
// 添加下拉刷新
|
|
||||||
_tableView.refreshControl = self.refreshControl;
|
|
||||||
}
|
}
|
||||||
return _tableView;
|
return _listView;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIRefreshControl *)refreshControl {
|
- (UILabel *)topTipLabel {
|
||||||
if (!_refreshControl) {
|
if (!_topTipLabel) {
|
||||||
_refreshControl = [[UIRefreshControl alloc] init];
|
_topTipLabel = [UILabel new];
|
||||||
[_refreshControl addTarget:self action:@selector(onRefresh) forControlEvents:UIControlEventValueChanged];
|
_topTipLabel.numberOfLines = 0;
|
||||||
|
_topTipLabel.textColor = [UIColor whiteColor];
|
||||||
|
_topTipLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
|
||||||
|
_topTipLabel.text = @"The disease is like a cruel ruler, measuring the true length of my life, but it is also like a lamp, illuminating the present that I have always ignored. Now I feel a strange freedom: since the end is clear, I can take every step with my whole heart.";
|
||||||
}
|
}
|
||||||
return _refreshControl;
|
return _topTipLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIButton *)publishButton {
|
// 无数据源属性
|
||||||
if (!_publishButton) {
|
|
||||||
_publishButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
||||||
_publishButton.backgroundColor = [UIColor colorWithRed:0.2 green:0.6 blue:0.86 alpha:1.0]; // 主色调
|
|
||||||
_publishButton.layer.cornerRadius = 28;
|
|
||||||
_publishButton.layer.shadowColor = [UIColor blackColor].CGColor;
|
|
||||||
_publishButton.layer.shadowOffset = CGSizeMake(0, 2);
|
|
||||||
_publishButton.layer.shadowOpacity = 0.3;
|
|
||||||
_publishButton.layer.shadowRadius = 4;
|
|
||||||
|
|
||||||
// 设置图标(暂时使用文字)
|
|
||||||
[_publishButton setTitle:@"+" forState:UIControlStateNormal];
|
|
||||||
_publishButton.titleLabel.font = [UIFont systemFontOfSize:32 weight:UIFontWeightLight];
|
|
||||||
[_publishButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
|
||||||
|
|
||||||
[_publishButton addTarget:self action:@selector(onPublishButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
|
||||||
}
|
|
||||||
return _publishButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSMutableArray *)dataSource {
|
|
||||||
if (!_dataSource) {
|
|
||||||
_dataSource = [NSMutableArray array];
|
|
||||||
}
|
|
||||||
return _dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
33
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h
Normal file
33
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// EPMomentAPIHelper.h
|
||||||
|
// YuMi
|
||||||
|
//
|
||||||
|
// Created by AI on 2025-10-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "BaseMvpPresenter.h"
|
||||||
|
#import "MomentsInfoModel.h"
|
||||||
|
#import "MomentsListInfoModel.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/// 推荐/我的动态列表数据源类型
|
||||||
|
typedef NS_ENUM(NSInteger, EPMomentListSourceType) {
|
||||||
|
EPMomentListSourceTypeRecommend = 0,
|
||||||
|
EPMomentListSourceTypeMine = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 统一封装 Moments 列表 API
|
||||||
|
@interface EPMomentAPIHelper : BaseMvpPresenter
|
||||||
|
|
||||||
|
/// 拉取最新动态列表(默认 types:"0,2" 图片+文字)
|
||||||
|
- (void)fetchLatestMomentsWithNextID:(NSString *)nextID
|
||||||
|
completion:(void (^)(NSArray <MomentsInfoModel *>* _Nullable list, NSString *nextMomentID))completion
|
||||||
|
failure:(void(^)(NSInteger code, NSString * _Nullable msg))failure;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
|
42
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m
Normal file
42
YuMi/E-P/NewMoments/Services/EPMomentAPIHelper.m
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// EPMomentAPIHelper.m
|
||||||
|
// YuMi
|
||||||
|
//
|
||||||
|
// Created by AI on 2025-10-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import "EPMomentAPIHelper.h"
|
||||||
|
#import "Api+Moments.h"
|
||||||
|
#import "AccountInfoStorage.h"
|
||||||
|
#import "BaseModel.h"
|
||||||
|
|
||||||
|
|
||||||
|
@implementation EPMomentAPIHelper
|
||||||
|
// [Api momentsRecommendList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||||
|
// if (code == 200 && data.data) {
|
||||||
|
// NSArray *array = [MomentsInfoModel modelsWithArray:data.data];
|
||||||
|
// if (completion) completion(array ?: @[], 200, @"success");
|
||||||
|
// } else {
|
||||||
|
// if (completion) completion(@[], code, msg);
|
||||||
|
// }
|
||||||
|
// } page:pageStr pageSize:pageSizeStr types:types];
|
||||||
|
|
||||||
|
- (void)fetchLatestMomentsWithNextID:(NSString *)nextID
|
||||||
|
completion:(void (^)(NSArray <MomentsInfoModel *>* _Nullable list, NSString *nextMomentID))completion
|
||||||
|
failure:(void(^)(NSInteger code, NSString * _Nullable msg))failure {
|
||||||
|
NSString *pageSizeStr = @"20";
|
||||||
|
NSString *types = @"0,2"; // 图片+文字
|
||||||
|
|
||||||
|
[Api momentsLatestList:[self createHttpCompletion:^(BaseModel * _Nonnull data) {
|
||||||
|
MomentsListInfoModel *listInfo = [MomentsListInfoModel modelWithDictionary:data.data];
|
||||||
|
if (completion) completion(listInfo.dynamicList ?: @[],
|
||||||
|
listInfo.nextDynamicId);
|
||||||
|
} fail:^(NSInteger code, NSString * _Nullable msg) {
|
||||||
|
if (failure) failure(code, msg);
|
||||||
|
}] dynamicId:nextID pageSize:pageSizeStr types:types];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
@@ -10,7 +10,7 @@
|
|||||||
#import "MomentsInfoModel.h"
|
#import "MomentsInfoModel.h"
|
||||||
#import "AccountInfoStorage.h"
|
#import "AccountInfoStorage.h"
|
||||||
#import "Api+Moments.h"
|
#import "Api+Moments.h"
|
||||||
#import <Masonry/Masonry.h>
|
#import "NetImageView.h"
|
||||||
|
|
||||||
@interface EPMomentCell ()
|
@interface EPMomentCell ()
|
||||||
|
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
/// 卡片容器
|
/// 卡片容器
|
||||||
@property (nonatomic, strong) UIView *cardView;
|
@property (nonatomic, strong) UIView *cardView;
|
||||||
|
|
||||||
/// 头像
|
/// 头像(网络)
|
||||||
@property (nonatomic, strong) UIImageView *avatarImageView;
|
@property (nonatomic, strong) NetImageView *avatarImageView;
|
||||||
|
|
||||||
/// 用户名
|
/// 用户名
|
||||||
@property (nonatomic, strong) UILabel *nameLabel;
|
@property (nonatomic, strong) UILabel *nameLabel;
|
||||||
@@ -31,8 +31,9 @@
|
|||||||
/// 内容标签
|
/// 内容标签
|
||||||
@property (nonatomic, strong) UILabel *contentLabel;
|
@property (nonatomic, strong) UILabel *contentLabel;
|
||||||
|
|
||||||
/// 图片容器(可选)
|
/// 图片容器(九宫格)
|
||||||
@property (nonatomic, strong) UIView *imagesContainer;
|
@property (nonatomic, strong) UIView *imagesContainer;
|
||||||
|
@property (nonatomic, strong) NSMutableArray<NetImageView *> *imageViews;
|
||||||
|
|
||||||
/// 底部操作栏
|
/// 底部操作栏
|
||||||
@property (nonatomic, strong) UIView *actionBar;
|
@property (nonatomic, strong) UIView *actionBar;
|
||||||
@@ -43,8 +44,7 @@
|
|||||||
/// 评论按钮
|
/// 评论按钮
|
||||||
@property (nonatomic, strong) UIButton *commentButton;
|
@property (nonatomic, strong) UIButton *commentButton;
|
||||||
|
|
||||||
/// 分享按钮
|
// 分享按钮已移除
|
||||||
@property (nonatomic, strong) UIButton *shareButton;
|
|
||||||
|
|
||||||
/// 当前数据模型
|
/// 当前数据模型
|
||||||
@property (nonatomic, strong) MomentsInfoModel *currentModel;
|
@property (nonatomic, strong) MomentsInfoModel *currentModel;
|
||||||
@@ -108,11 +108,19 @@
|
|||||||
make.top.equalTo(self.avatarImageView.mas_bottom).offset(12);
|
make.top.equalTo(self.avatarImageView.mas_bottom).offset(12);
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
// 图片九宫格
|
||||||
|
[self.cardView addSubview:self.imagesContainer];
|
||||||
|
[self.imagesContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.cardView).offset(15);
|
||||||
|
make.right.equalTo(self.cardView).offset(-15);
|
||||||
|
make.top.equalTo(self.contentLabel.mas_bottom).offset(12);
|
||||||
|
}];
|
||||||
|
|
||||||
// 底部操作栏
|
// 底部操作栏
|
||||||
[self.cardView addSubview:self.actionBar];
|
[self.cardView addSubview:self.actionBar];
|
||||||
[self.actionBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
[self.actionBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
make.left.right.equalTo(self.cardView);
|
make.left.right.equalTo(self.cardView);
|
||||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(15);
|
make.top.equalTo(self.imagesContainer.mas_bottom).offset(12);
|
||||||
make.height.mas_equalTo(50);
|
make.height.mas_equalTo(50);
|
||||||
make.bottom.equalTo(self.cardView).offset(-8);
|
make.bottom.equalTo(self.cardView).offset(-8);
|
||||||
}];
|
}];
|
||||||
@@ -133,13 +141,7 @@
|
|||||||
make.width.mas_greaterThanOrEqualTo(60);
|
make.width.mas_greaterThanOrEqualTo(60);
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// 分享按钮
|
// 右侧占位(去掉分享按钮后,右边保持留白)
|
||||||
[self.actionBar addSubview:self.shareButton];
|
|
||||||
[self.shareButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
||||||
make.right.equalTo(self.actionBar).offset(-15);
|
|
||||||
make.centerY.equalTo(self.actionBar);
|
|
||||||
make.width.mas_greaterThanOrEqualTo(60);
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public Methods
|
// MARK: - Public Methods
|
||||||
@@ -156,17 +158,74 @@
|
|||||||
// 配置内容
|
// 配置内容
|
||||||
self.contentLabel.text = model.content ?: @"";
|
self.contentLabel.text = model.content ?: @"";
|
||||||
|
|
||||||
// 配置点赞数
|
// 配置图片九宫格
|
||||||
[self.likeButton setTitle:[NSString stringWithFormat:@"👍 %ld", (long)model.likeCount] forState:UIControlStateNormal];
|
[self renderImages:model.dynamicResList];
|
||||||
|
|
||||||
|
// 配置点赞/评论数(安全整型,避免负数和溢出)
|
||||||
|
NSInteger likeCnt = MAX(0, model.likeCount.integerValue);
|
||||||
|
NSInteger cmtCnt = MAX(0, model.commentCount.integerValue);
|
||||||
|
[self.likeButton setTitle:[NSString stringWithFormat:@"👍 %ld", (long)likeCnt] forState:UIControlStateNormal];
|
||||||
|
[self.commentButton setTitle:[NSString stringWithFormat:@"💬 %ld", (long)cmtCnt] forState:UIControlStateNormal];
|
||||||
|
|
||||||
// 配置评论数
|
self.avatarImageView.imageUrl = model.avatar;
|
||||||
[self.commentButton setTitle:[NSString stringWithFormat:@"💬 %ld", (long)model.commentCount] forState:UIControlStateNormal];
|
}
|
||||||
|
|
||||||
// 配置分享
|
// MARK: - Images Grid
|
||||||
[self.shareButton setTitle:@"🔗 分享" forState:UIControlStateNormal];
|
|
||||||
|
- (void)renderImages:(NSArray *)resList {
|
||||||
// TODO: 加载头像图片
|
// 清理旧视图
|
||||||
// [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:model.avatar]];
|
for (UIView *iv in self.imageViews) { [iv removeFromSuperview]; }
|
||||||
|
[self.imageViews removeAllObjects];
|
||||||
|
if (resList.count == 0) {
|
||||||
|
[self.imagesContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.cardView).offset(15);
|
||||||
|
make.right.equalTo(self.cardView).offset(-15);
|
||||||
|
make.top.equalTo(self.contentLabel.mas_bottom).offset(0);
|
||||||
|
make.height.mas_equalTo(0);
|
||||||
|
}];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSInteger columns = 3;
|
||||||
|
CGFloat spacing = 6.0;
|
||||||
|
CGFloat totalWidth = [UIScreen mainScreen].bounds.size.width - 30 - 30; // 左右各 15 内边距,再减卡片左右 15
|
||||||
|
CGFloat itemW = floor((totalWidth - spacing * (columns - 1)) / columns);
|
||||||
|
|
||||||
|
for (NSInteger i = 0; i < resList.count && i < 9; i++) {
|
||||||
|
NetImageConfig *config = [[NetImageConfig alloc] init];
|
||||||
|
config.placeHolder = [UIImageConstant defaultBannerPlaceholder];
|
||||||
|
NetImageView *iv = [[NetImageView alloc] initWithConfig:config];
|
||||||
|
iv.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
|
||||||
|
iv.layer.cornerRadius = 6;
|
||||||
|
iv.layer.masksToBounds = YES;
|
||||||
|
iv.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
[self.imagesContainer addSubview:iv];
|
||||||
|
[self.imageViews addObject:iv];
|
||||||
|
NSInteger row = i / columns;
|
||||||
|
NSInteger col = i % columns;
|
||||||
|
[iv mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.imagesContainer).offset((itemW + spacing) * col);
|
||||||
|
make.top.equalTo(self.imagesContainer).offset((itemW + spacing) * row);
|
||||||
|
make.size.mas_equalTo(CGSizeMake(itemW, itemW));
|
||||||
|
}];
|
||||||
|
// 绑定网络图片
|
||||||
|
NSString *url = nil;
|
||||||
|
id item = resList[i];
|
||||||
|
if ([item isKindOfClass:[NSDictionary class]]) {
|
||||||
|
url = [item valueForKey:@"resUrl"] ?: [item valueForKey:@"url"];
|
||||||
|
} else if ([item respondsToSelector:@selector(resUrl)]) {
|
||||||
|
url = [item valueForKey:@"resUrl"];
|
||||||
|
}
|
||||||
|
iv.imageUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSInteger rows = ((MIN(resList.count, 9) - 1) / columns) + 1;
|
||||||
|
CGFloat height = rows * itemW + (rows - 1) * spacing;
|
||||||
|
[self.imagesContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.cardView).offset(15);
|
||||||
|
make.right.equalTo(self.cardView).offset(-15);
|
||||||
|
make.top.equalTo(self.contentLabel.mas_bottom).offset(12);
|
||||||
|
make.height.mas_equalTo(height);
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 格式化时间戳为相对时间
|
/// 格式化时间戳为相对时间
|
||||||
@@ -249,7 +308,10 @@
|
|||||||
|
|
||||||
- (UIImageView *)avatarImageView {
|
- (UIImageView *)avatarImageView {
|
||||||
if (!_avatarImageView) {
|
if (!_avatarImageView) {
|
||||||
_avatarImageView = [[UIImageView alloc] init];
|
NetImageConfig *config = [[NetImageConfig alloc] init];
|
||||||
|
config.imageType = ImageTypeUserIcon;
|
||||||
|
config.placeHolder = [UIImageConstant defaultAvatarPlaceholder];
|
||||||
|
_avatarImageView = [[NetImageView alloc] initWithConfig:config];
|
||||||
_avatarImageView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
|
_avatarImageView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
|
||||||
_avatarImageView.layer.cornerRadius = 8; // 圆角矩形,不是圆形!
|
_avatarImageView.layer.cornerRadius = 8; // 圆角矩形,不是圆形!
|
||||||
_avatarImageView.layer.masksToBounds = YES;
|
_avatarImageView.layer.masksToBounds = YES;
|
||||||
@@ -312,11 +374,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (UIButton *)shareButton {
|
- (UIButton *)shareButton {
|
||||||
if (!_shareButton) {
|
return nil;
|
||||||
_shareButton = [self createActionButtonWithTitle:@"🔗 分享"];
|
|
||||||
[_shareButton addTarget:self action:@selector(onShareButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
|
||||||
}
|
|
||||||
return _shareButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIButton *)createActionButtonWithTitle:(NSString *)title {
|
- (UIButton *)createActionButtonWithTitle:(NSString *)title {
|
||||||
@@ -327,4 +385,19 @@
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (UIView *)imagesContainer {
|
||||||
|
if (!_imagesContainer) {
|
||||||
|
_imagesContainer = [[UIView alloc] init];
|
||||||
|
_imagesContainer.backgroundColor = [UIColor clearColor];
|
||||||
|
}
|
||||||
|
return _imagesContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMutableArray<NetImageView *> *)imageViews {
|
||||||
|
if (!_imageViews) {
|
||||||
|
_imageViews = [NSMutableArray array];
|
||||||
|
}
|
||||||
|
return _imageViews;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
34
YuMi/E-P/NewMoments/Views/EPMomentListView.h
Normal file
34
YuMi/E-P/NewMoments/Views/EPMomentListView.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// EPMomentListView.h
|
||||||
|
// YuMi
|
||||||
|
//
|
||||||
|
// Created by AI on 2025-10-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import "EPMomentAPIHelper.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@class MomentsInfoModel;
|
||||||
|
|
||||||
|
/// 承载 Moments 列表与分页刷新的视图
|
||||||
|
@interface EPMomentListView : UIView
|
||||||
|
|
||||||
|
/// 当前数据源(外部可读)
|
||||||
|
@property (nonatomic, strong, readonly) NSArray *rawList;
|
||||||
|
|
||||||
|
/// 列表类型:推荐 / 我的
|
||||||
|
@property (nonatomic, assign) EPMomentListSourceType sourceType;
|
||||||
|
|
||||||
|
/// 外部可设置:当某一项被点击
|
||||||
|
@property (nonatomic, copy) void (^onSelectMoment)(NSInteger index);
|
||||||
|
|
||||||
|
/// 重新加载(刷新到第一页)
|
||||||
|
- (void)reloadFirstPage;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
|
174
YuMi/E-P/NewMoments/Views/EPMomentListView.m
Normal file
174
YuMi/E-P/NewMoments/Views/EPMomentListView.m
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
//
|
||||||
|
// EPMomentListView.m
|
||||||
|
// YuMi
|
||||||
|
//
|
||||||
|
// Created by AI on 2025-10-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import "EPMomentListView.h"
|
||||||
|
#import "EPMomentCell.h"
|
||||||
|
#import <MJRefresh/MJRefresh.h>
|
||||||
|
|
||||||
|
|
||||||
|
@interface EPMomentListView () <UITableViewDelegate, UITableViewDataSource>
|
||||||
|
|
||||||
|
@property (nonatomic, strong) UITableView *tableView;
|
||||||
|
@property (nonatomic, strong) UIRefreshControl *refreshControl;
|
||||||
|
@property (nonatomic, strong) NSMutableArray *mutableRawList;
|
||||||
|
@property (nonatomic, strong) EPMomentAPIHelper *api;
|
||||||
|
@property (nonatomic, assign) BOOL isLoading;
|
||||||
|
@property (nonatomic, copy) NSString *nextID;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation EPMomentListView
|
||||||
|
|
||||||
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
|
self = [super initWithFrame:frame];
|
||||||
|
if (self) {
|
||||||
|
self.backgroundColor = [UIColor clearColor];
|
||||||
|
_api = [[EPMomentAPIHelper alloc] init];
|
||||||
|
_mutableRawList = [NSMutableArray array];
|
||||||
|
_sourceType = EPMomentListSourceTypeRecommend;
|
||||||
|
|
||||||
|
[self addSubview:self.tableView];
|
||||||
|
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.edges.equalTo(self);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray<NSMutableDictionary *> *)rawList {
|
||||||
|
return [self.mutableRawList copy];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reloadFirstPage {
|
||||||
|
self.nextID = @"";
|
||||||
|
[self.mutableRawList removeAllObjects];
|
||||||
|
[self.tableView reloadData];
|
||||||
|
[self.tableView.mj_footer resetNoMoreData];
|
||||||
|
[self requestNextPage];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)requestNextPage {
|
||||||
|
if (self.isLoading) return;
|
||||||
|
self.isLoading = YES;
|
||||||
|
|
||||||
|
@kWeakify(self);
|
||||||
|
[self.api fetchLatestMomentsWithNextID:self.nextID
|
||||||
|
completion:^(NSArray<MomentsInfoModel *> * _Nullable list, NSString * _Nonnull nextMomentID) {
|
||||||
|
@kStrongify(self);
|
||||||
|
[self endLoading];
|
||||||
|
if (list.count > 0) {
|
||||||
|
self.nextID = nextMomentID;
|
||||||
|
[self.mutableRawList addObjectsFromArray:list];
|
||||||
|
[self.tableView reloadData];
|
||||||
|
if (nextMomentID.length > 0) {
|
||||||
|
[self.tableView.mj_footer endRefreshing];
|
||||||
|
} else {
|
||||||
|
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: 后续补充空数据页面
|
||||||
|
if (self.nextID.length == 0) {
|
||||||
|
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
||||||
|
} else {
|
||||||
|
[self.tableView.mj_footer endRefreshing];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} failure:^(NSInteger code, NSString * _Nullable msg) {
|
||||||
|
@kStrongify(self);
|
||||||
|
[self endLoading];
|
||||||
|
// TODO: 完全没有数据情况下,后续补充数据异常页面
|
||||||
|
[self.tableView.mj_footer endRefreshing];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)endLoading {
|
||||||
|
self.isLoading = NO;
|
||||||
|
[self.refreshControl endRefreshing];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableView
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||||
|
return self.mutableRawList.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
EPMomentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewMomentCell" forIndexPath:indexPath];
|
||||||
|
if (indexPath.row < self.mutableRawList.count) {
|
||||||
|
MomentsInfoModel *model = [self.mutableRawList xpSafeObjectAtIndex:indexPath.row];
|
||||||
|
[cell configureWithModel:model];
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
return UITableViewAutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||||
|
if (self.onSelectMoment) self.onSelectMoment(indexPath.row);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||||
|
CGFloat offsetY = scrollView.contentOffset.y;
|
||||||
|
CGFloat contentHeight = scrollView.contentSize.height;
|
||||||
|
CGFloat screenHeight = scrollView.frame.size.height;
|
||||||
|
if (offsetY > contentHeight - screenHeight - 100 && !self.isLoading) {
|
||||||
|
[self requestNextPage];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Lazy
|
||||||
|
|
||||||
|
- (UITableView *)tableView {
|
||||||
|
if (!_tableView) {
|
||||||
|
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||||
|
_tableView.delegate = self;
|
||||||
|
_tableView.dataSource = self;
|
||||||
|
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||||
|
_tableView.backgroundColor = [UIColor clearColor];
|
||||||
|
_tableView.estimatedRowHeight = 200;
|
||||||
|
_tableView.rowHeight = UITableViewAutomaticDimension;
|
||||||
|
_tableView.showsVerticalScrollIndicator = NO;
|
||||||
|
// 底部留出更高空间,避免被悬浮 TabBar 遮挡
|
||||||
|
_tableView.contentInset = UIEdgeInsetsMake(10, 0, 120, 0);
|
||||||
|
_tableView.scrollIndicatorInsets = UIEdgeInsetsMake(10, 0, 120, 0);
|
||||||
|
[_tableView registerClass:[EPMomentCell class] forCellReuseIdentifier:@"NewMomentCell"];
|
||||||
|
_tableView.refreshControl = self.refreshControl;
|
||||||
|
|
||||||
|
// MJRefresh Footer - 加载更多
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
_tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
|
||||||
|
__strong typeof(weakSelf) self = weakSelf;
|
||||||
|
if (!self.isLoading && self.nextID.length > 0) {
|
||||||
|
[self requestNextPage];
|
||||||
|
} else if (self.nextID.length == 0) {
|
||||||
|
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
||||||
|
} else {
|
||||||
|
[self.tableView.mj_footer endRefreshing];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return _tableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIRefreshControl *)refreshControl {
|
||||||
|
if (!_refreshControl) {
|
||||||
|
_refreshControl = [[UIRefreshControl alloc] init];
|
||||||
|
[_refreshControl addTarget:self action:@selector(reloadFirstPage) forControlEvents:UIControlEventValueChanged];
|
||||||
|
}
|
||||||
|
return _refreshControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
@@ -43,6 +43,9 @@ import SnapKit
|
|||||||
// 隐藏原生 TabBar
|
// 隐藏原生 TabBar
|
||||||
self.tabBar.isHidden = true
|
self.tabBar.isHidden = true
|
||||||
|
|
||||||
|
// 设置 delegate 以完全控制切换行为
|
||||||
|
self.delegate = self
|
||||||
|
|
||||||
setupCustomFloatingTabBar()
|
setupCustomFloatingTabBar()
|
||||||
setupGlobalManagers()
|
setupGlobalManagers()
|
||||||
setupInitialViewControllers()
|
setupInitialViewControllers()
|
||||||
@@ -111,14 +114,12 @@ import SnapKit
|
|||||||
selectedImage: "tab_moment_on",
|
selectedImage: "tab_moment_on",
|
||||||
tag: 0
|
tag: 0
|
||||||
)
|
)
|
||||||
momentButton.isSelected = true
|
|
||||||
|
|
||||||
let mineButton = createTabButton(
|
let mineButton = createTabButton(
|
||||||
normalImage: "tab_mine_off",
|
normalImage: "tab_mine_off",
|
||||||
selectedImage: "tab_mine_on",
|
selectedImage: "tab_mine_on",
|
||||||
tag: 1
|
tag: 1
|
||||||
)
|
)
|
||||||
mineButton.isSelected = true
|
|
||||||
|
|
||||||
tabButtons = [momentButton, mineButton]
|
tabButtons = [momentButton, mineButton]
|
||||||
|
|
||||||
@@ -144,35 +145,35 @@ import SnapKit
|
|||||||
private func createTabButton(normalImage: String, selectedImage: String, tag: Int) -> UIButton {
|
private func createTabButton(normalImage: String, selectedImage: String, tag: Int) -> UIButton {
|
||||||
let button = UIButton(type: .custom)
|
let button = UIButton(type: .custom)
|
||||||
button.tag = tag
|
button.tag = tag
|
||||||
|
button.adjustsImageWhenHighlighted = false // 禁用高亮效果,避免闪烁
|
||||||
// 存储图片名称到 button,方便后续切换
|
|
||||||
button.accessibilityLabel = normalImage
|
|
||||||
button.accessibilityHint = selectedImage
|
|
||||||
|
|
||||||
// 尝试设置自定义图片,如果不存在则使用 SF Symbols
|
// 尝试设置自定义图片,如果不存在则使用 SF Symbols
|
||||||
if let normalImg = UIImage(named: normalImage), let _ = UIImage(named: selectedImage) {
|
if let normalImg = UIImage(named: normalImage), let selectedImg = UIImage(named: selectedImage) {
|
||||||
// 使用自定义图片(初始显示 off 图片)
|
// 正确设置:分别为 normal 和 selected 状态设置图片
|
||||||
button.setImage(normalImg, for: .normal)
|
button.setImage(normalImg, for: .normal)
|
||||||
|
button.setImage(selectedImg, for: .selected)
|
||||||
} else {
|
} else {
|
||||||
// 使用 SF Symbols 作为备用
|
// 使用 SF Symbols 作为备用
|
||||||
let fallbackIcons = ["sparkles", "person.circle"]
|
let fallbackIcons = ["sparkles", "person.circle"]
|
||||||
let iconName = fallbackIcons[tag]
|
let iconName = fallbackIcons[tag]
|
||||||
let imageConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
|
let imageConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
|
||||||
|
let normalIcon = UIImage(systemName: iconName, withConfiguration: imageConfig)
|
||||||
|
|
||||||
button.setImage(UIImage(systemName: iconName, withConfiguration: imageConfig), for: .normal)
|
button.setImage(normalIcon, for: .normal)
|
||||||
|
button.setImage(normalIcon, for: .selected)
|
||||||
button.tintColor = .white.withAlphaComponent(0.6)
|
button.tintColor = .white.withAlphaComponent(0.6)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片渲染模式
|
// 图片渲染模式
|
||||||
button.imageView?.contentMode = .scaleAspectFit
|
button.imageView?.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
// 移除标题,只使用图片
|
// 移除标题
|
||||||
button.setTitle(nil, for: .normal)
|
button.setTitle(nil, for: .normal)
|
||||||
button.setTitle(nil, for: .selected)
|
button.setTitle(nil, for: .selected)
|
||||||
|
|
||||||
// 设置图片大小约束
|
// 设置图片大小约束
|
||||||
button.imageView?.snp.makeConstraints { make in
|
button.imageView?.snp.makeConstraints { make in
|
||||||
make.size.equalTo(28) // 图标大小
|
make.size.equalTo(28)
|
||||||
}
|
}
|
||||||
|
|
||||||
button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside)
|
button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside)
|
||||||
@@ -191,8 +192,10 @@ import SnapKit
|
|||||||
// 先更新按钮状态
|
// 先更新按钮状态
|
||||||
updateTabButtonStates(selectedIndex: newIndex)
|
updateTabButtonStates(selectedIndex: newIndex)
|
||||||
|
|
||||||
// 再切换 ViewController(使用动画)
|
// 禁用 UITabBarController 的默认切换动画,避免闪烁
|
||||||
selectedIndex = newIndex
|
UIView.performWithoutAnimation {
|
||||||
|
selectedIndex = newIndex
|
||||||
|
}
|
||||||
|
|
||||||
let tabNames = ["动态", "我的"]
|
let tabNames = ["动态", "我的"]
|
||||||
NSLog("[EPTabBarController] 选中 Tab: \(tabNames[newIndex])")
|
NSLog("[EPTabBarController] 选中 Tab: \(tabNames[newIndex])")
|
||||||
@@ -205,32 +208,23 @@ import SnapKit
|
|||||||
|
|
||||||
for (index, button) in tabButtons.enumerated() {
|
for (index, button) in tabButtons.enumerated() {
|
||||||
let isSelected = (index == selectedIndex)
|
let isSelected = (index == selectedIndex)
|
||||||
|
|
||||||
|
// 直接设置 isSelected 属性即可,图片会自动切换
|
||||||
button.isSelected = isSelected
|
button.isSelected = isSelected
|
||||||
|
|
||||||
// 更新图片状态
|
// SF Symbols 的情况需要手动更新 tintColor
|
||||||
if let normalImageName = button.accessibilityLabel,
|
if button.currentImage?.isSymbolImage == true {
|
||||||
let selectedImageName = button.accessibilityHint {
|
|
||||||
// 使用自定义图片
|
|
||||||
let imageName = isSelected ? selectedImageName : normalImageName
|
|
||||||
if let image = UIImage(named: imageName) {
|
|
||||||
button.setImage(image, for: .normal)
|
|
||||||
} else {
|
|
||||||
// 如果自定义图片不存在,使用 SF Symbols
|
|
||||||
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 使用 SF Symbols(备用方案)
|
|
||||||
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
|
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中状态动画
|
// 选中状态缩放动画
|
||||||
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseInOut], animations: {
|
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut], animations: {
|
||||||
button.transform = isSelected ? CGAffineTransform(scaleX: 1.1, y: 1.1) : .identity
|
button.transform = isSelected ? CGAffineTransform(scaleX: 1.1, y: 1.1) : .identity
|
||||||
}, completion: nil)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 延迟恢复按钮交互,避免动画期间的重复点击
|
// 延迟恢复按钮交互
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||||||
self.tabButtons.forEach { $0.isUserInteractionEnabled = true }
|
self.tabButtons.forEach { $0.isUserInteractionEnabled = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,22 +311,34 @@ import SnapKit
|
|||||||
!(viewControllers?[0] is EPMomentViewController) ||
|
!(viewControllers?[0] is EPMomentViewController) ||
|
||||||
!(viewControllers?[1] is EPMineViewController) {
|
!(viewControllers?[1] is EPMineViewController) {
|
||||||
|
|
||||||
// 创建真实的 ViewController(OC 类)
|
// 创建真实的 ViewController(OC 类),并使用导航控制器包裹以显示标题/右上按钮
|
||||||
let momentVC = EPMomentViewController()
|
let momentVC = EPMomentViewController()
|
||||||
momentVC.tabBarItem = createTabBarItem(
|
momentVC.title = "动态"
|
||||||
|
let momentNav = UINavigationController(rootViewController: momentVC)
|
||||||
|
momentNav.navigationBar.isTranslucent = true
|
||||||
|
momentNav.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||||
|
momentNav.navigationBar.shadowImage = UIImage()
|
||||||
|
momentNav.view.backgroundColor = .clear
|
||||||
|
momentNav.tabBarItem = createTabBarItem(
|
||||||
title: "动态",
|
title: "动态",
|
||||||
normalImage: "tab_moment_normal",
|
normalImage: "tab_moment_normal",
|
||||||
selectedImage: "tab_moment_selected"
|
selectedImage: "tab_moment_selected"
|
||||||
)
|
)
|
||||||
|
|
||||||
let mineVC = EPMineViewController()
|
let mineVC = EPMineViewController()
|
||||||
mineVC.tabBarItem = createTabBarItem(
|
mineVC.title = "我的"
|
||||||
|
let mineNav = UINavigationController(rootViewController: mineVC)
|
||||||
|
mineNav.navigationBar.isTranslucent = true
|
||||||
|
mineNav.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||||
|
mineNav.navigationBar.shadowImage = UIImage()
|
||||||
|
mineNav.view.backgroundColor = .clear
|
||||||
|
mineNav.tabBarItem = createTabBarItem(
|
||||||
title: "我的",
|
title: "我的",
|
||||||
normalImage: "tab_mine_normal",
|
normalImage: "tab_mine_normal",
|
||||||
selectedImage: "tab_mine_selected"
|
selectedImage: "tab_mine_selected"
|
||||||
)
|
)
|
||||||
|
|
||||||
viewControllers = [momentVC, mineVC]
|
viewControllers = [momentNav, mineNav]
|
||||||
NSLog("[EPTabBarController] 登录后 ViewControllers 创建完成 - Moment & Mine")
|
NSLog("[EPTabBarController] 登录后 ViewControllers 创建完成 - Moment & Mine")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +353,21 @@ extension EPTabBarController: UITabBarControllerDelegate {
|
|||||||
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||||
NSLog("[EPTabBarController] 选中 Tab: \(item.title ?? "Unknown")")
|
NSLog("[EPTabBarController] 选中 Tab: \(item.title ?? "Unknown")")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 禁用系统默认的切换动画
|
||||||
|
func tabBarController(_ tabBarController: UITabBarController,
|
||||||
|
animationControllerForTransitionFrom fromVC: UIViewController,
|
||||||
|
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
|
// 返回 nil 表示不使用动画
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 完全控制是否允许切换
|
||||||
|
func tabBarController(_ tabBarController: UITabBarController,
|
||||||
|
shouldSelect viewController: UIViewController) -> Bool {
|
||||||
|
// 允许切换,但通过返回 nil 的 animationController 来禁用动画
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - OC Compatibility
|
// MARK: - OC Compatibility
|
||||||
|
@@ -1,374 +0,0 @@
|
|||||||
//
|
|
||||||
// EPTabBarController.swift
|
|
||||||
// YuMi
|
|
||||||
//
|
|
||||||
// Created by AI on 2025-10-09.
|
|
||||||
// Copyright © 2025 YuMi. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import SnapKit
|
|
||||||
|
|
||||||
/// EP 系列 TabBar 控制器
|
|
||||||
/// 悬浮设计 + 液态玻璃效果,只包含 Moment 和 Mine 两个 Tab
|
|
||||||
@objc class EPTabBarController: UITabBarController {
|
|
||||||
|
|
||||||
// MARK: - Properties
|
|
||||||
|
|
||||||
/// 全局事件管理器
|
|
||||||
private var globalEventManager: GlobalEventManager?
|
|
||||||
|
|
||||||
/// 是否已登录
|
|
||||||
private var isLoggedIn: Bool = false
|
|
||||||
|
|
||||||
/// 自定义悬浮 TabBar 容器
|
|
||||||
private var customTabBarView: UIView!
|
|
||||||
|
|
||||||
/// 毛玻璃背景视图
|
|
||||||
private var tabBarBackgroundView: UIVisualEffectView!
|
|
||||||
|
|
||||||
/// Tab 按钮数组
|
|
||||||
private var tabButtons: [UIButton] = []
|
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
// 测试域名配置
|
|
||||||
#if DEBUG
|
|
||||||
APIConfig.testEncryption()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 隐藏原生 TabBar
|
|
||||||
self.tabBar.isHidden = true
|
|
||||||
|
|
||||||
// 设置 delegate 以完全控制切换行为
|
|
||||||
self.delegate = self
|
|
||||||
|
|
||||||
setupCustomFloatingTabBar()
|
|
||||||
setupGlobalManagers()
|
|
||||||
setupInitialViewControllers()
|
|
||||||
|
|
||||||
NSLog("[EPTabBarController] 悬浮 TabBar 初始化完成")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
globalEventManager?.removeAllDelegates()
|
|
||||||
NSLog("[EPTabBarController] 已释放")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Setup
|
|
||||||
|
|
||||||
/// 设置自定义悬浮 TabBar
|
|
||||||
private func setupCustomFloatingTabBar() {
|
|
||||||
// 创建悬浮容器
|
|
||||||
customTabBarView = UIView()
|
|
||||||
customTabBarView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
customTabBarView.backgroundColor = .clear
|
|
||||||
view.addSubview(customTabBarView)
|
|
||||||
|
|
||||||
// 液态玻璃/毛玻璃效果
|
|
||||||
let effect: UIVisualEffect
|
|
||||||
if #available(iOS 26.0, *) {
|
|
||||||
// iOS 26+ 使用液态玻璃(Material)
|
|
||||||
effect = UIGlassEffect()
|
|
||||||
} else {
|
|
||||||
// iOS 13-17 使用毛玻璃
|
|
||||||
effect = UIBlurEffect(style: .systemMaterial)
|
|
||||||
}
|
|
||||||
|
|
||||||
tabBarBackgroundView = UIVisualEffectView(effect: effect)
|
|
||||||
tabBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
tabBarBackgroundView.layer.cornerRadius = 28
|
|
||||||
tabBarBackgroundView.layer.masksToBounds = true
|
|
||||||
|
|
||||||
// 添加边框
|
|
||||||
tabBarBackgroundView.layer.borderWidth = 0.5
|
|
||||||
tabBarBackgroundView.layer.borderColor = UIColor.white.withAlphaComponent(0.2).cgColor
|
|
||||||
|
|
||||||
customTabBarView.addSubview(tabBarBackgroundView)
|
|
||||||
|
|
||||||
// 简化的布局约束(类似 Masonry 风格)
|
|
||||||
customTabBarView.snp.makeConstraints { make in
|
|
||||||
make.leading.equalTo(view).offset(16)
|
|
||||||
make.trailing.equalTo(view).offset(-16)
|
|
||||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
|
|
||||||
make.height.equalTo(64)
|
|
||||||
}
|
|
||||||
|
|
||||||
tabBarBackgroundView.snp.makeConstraints { make in
|
|
||||||
make.edges.equalTo(customTabBarView)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加 Tab 按钮
|
|
||||||
setupTabButtons()
|
|
||||||
|
|
||||||
NSLog("[EPTabBarController] 悬浮 TabBar 设置完成")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 设置 Tab 按钮
|
|
||||||
private func setupTabButtons() {
|
|
||||||
let momentButton = createTabButton(
|
|
||||||
normalImage: "tab_moment_off",
|
|
||||||
selectedImage: "tab_moment_on",
|
|
||||||
tag: 0
|
|
||||||
)
|
|
||||||
|
|
||||||
let mineButton = createTabButton(
|
|
||||||
normalImage: "tab_mine_off",
|
|
||||||
selectedImage: "tab_mine_on",
|
|
||||||
tag: 1
|
|
||||||
)
|
|
||||||
|
|
||||||
tabButtons = [momentButton, mineButton]
|
|
||||||
|
|
||||||
let stackView = UIStackView(arrangedSubviews: tabButtons)
|
|
||||||
stackView.axis = .horizontal
|
|
||||||
stackView.distribution = .fillEqually
|
|
||||||
stackView.spacing = 20
|
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
tabBarBackgroundView.contentView.addSubview(stackView)
|
|
||||||
|
|
||||||
stackView.snp.makeConstraints { make in
|
|
||||||
make.top.equalTo(tabBarBackgroundView).offset(8)
|
|
||||||
make.leading.equalTo(tabBarBackgroundView).offset(20)
|
|
||||||
make.trailing.equalTo(tabBarBackgroundView).offset(-20)
|
|
||||||
make.bottom.equalTo(tabBarBackgroundView).offset(-8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认选中第一个
|
|
||||||
updateTabButtonStates(selectedIndex: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 创建 Tab 按钮
|
|
||||||
private func createTabButton(normalImage: String, selectedImage: String, tag: Int) -> UIButton {
|
|
||||||
let button = UIButton(type: .custom)
|
|
||||||
button.tag = tag
|
|
||||||
button.adjustsImageWhenHighlighted = false // 禁用高亮效果,避免闪烁
|
|
||||||
|
|
||||||
// 尝试设置自定义图片,如果不存在则使用 SF Symbols
|
|
||||||
if let normalImg = UIImage(named: normalImage), let selectedImg = UIImage(named: selectedImage) {
|
|
||||||
// 正确设置:分别为 normal 和 selected 状态设置图片
|
|
||||||
button.setImage(normalImg, for: .normal)
|
|
||||||
button.setImage(selectedImg, for: .selected)
|
|
||||||
} else {
|
|
||||||
// 使用 SF Symbols 作为备用
|
|
||||||
let fallbackIcons = ["sparkles", "person.circle"]
|
|
||||||
let iconName = fallbackIcons[tag]
|
|
||||||
let imageConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
|
|
||||||
let normalIcon = UIImage(systemName: iconName, withConfiguration: imageConfig)
|
|
||||||
|
|
||||||
button.setImage(normalIcon, for: .normal)
|
|
||||||
button.setImage(normalIcon, for: .selected)
|
|
||||||
button.tintColor = .white.withAlphaComponent(0.6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片渲染模式
|
|
||||||
button.imageView?.contentMode = .scaleAspectFit
|
|
||||||
|
|
||||||
// 移除标题
|
|
||||||
button.setTitle(nil, for: .normal)
|
|
||||||
button.setTitle(nil, for: .selected)
|
|
||||||
|
|
||||||
// 设置图片大小约束
|
|
||||||
button.imageView?.snp.makeConstraints { make in
|
|
||||||
make.size.equalTo(28)
|
|
||||||
}
|
|
||||||
|
|
||||||
button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside)
|
|
||||||
return button
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tab 按钮点击事件
|
|
||||||
@objc private func tabButtonTapped(_ sender: UIButton) {
|
|
||||||
let newIndex = sender.tag
|
|
||||||
|
|
||||||
// 如果点击的是当前已选中的 tab,不做任何操作
|
|
||||||
if newIndex == selectedIndex {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先更新按钮状态
|
|
||||||
updateTabButtonStates(selectedIndex: newIndex)
|
|
||||||
|
|
||||||
// 禁用 UITabBarController 的默认切换动画,避免闪烁
|
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
selectedIndex = newIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
let tabNames = ["动态", "我的"]
|
|
||||||
NSLog("[EPTabBarController] 选中 Tab: \(tabNames[newIndex])")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 更新 Tab 按钮状态
|
|
||||||
private func updateTabButtonStates(selectedIndex: Int) {
|
|
||||||
// 禁用按钮交互,避免快速点击
|
|
||||||
tabButtons.forEach { $0.isUserInteractionEnabled = false }
|
|
||||||
|
|
||||||
for (index, button) in tabButtons.enumerated() {
|
|
||||||
let isSelected = (index == selectedIndex)
|
|
||||||
|
|
||||||
// 直接设置 isSelected 属性即可,图片会自动切换
|
|
||||||
button.isSelected = isSelected
|
|
||||||
|
|
||||||
// SF Symbols 的情况需要手动更新 tintColor
|
|
||||||
if button.currentImage?.isSymbolImage == true {
|
|
||||||
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选中状态缩放动画
|
|
||||||
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut], animations: {
|
|
||||||
button.transform = isSelected ? CGAffineTransform(scaleX: 1.1, y: 1.1) : .identity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 延迟恢复按钮交互
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
|
||||||
self.tabButtons.forEach { $0.isUserInteractionEnabled = true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 设置全局管理器
|
|
||||||
private func setupGlobalManagers() {
|
|
||||||
globalEventManager = GlobalEventManager.shared()
|
|
||||||
globalEventManager?.setupSDKDelegates()
|
|
||||||
|
|
||||||
// TODO: v0.2 版本暂时禁用房间最小化视图(无房间功能)
|
|
||||||
// 后续版本可通过 Build Configuration 或版本号判断是否启用
|
|
||||||
/*
|
|
||||||
if let containerView = view {
|
|
||||||
globalEventManager?.setupRoomMiniView(on: containerView)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 注册社交分享回调
|
|
||||||
globalEventManager?.registerSocialShareCallback()
|
|
||||||
|
|
||||||
NSLog("[EPTabBarController] 全局管理器设置完成(v0.2 - 无 MiniRoom)")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 设置初始 ViewController(未登录状态)
|
|
||||||
private func setupInitialViewControllers() {
|
|
||||||
// TODO: 暂时使用空白页面占位
|
|
||||||
let blankVC1 = UIViewController()
|
|
||||||
blankVC1.view.backgroundColor = .white
|
|
||||||
blankVC1.tabBarItem = createTabBarItem(
|
|
||||||
title: "动态",
|
|
||||||
normalImage: "tab_moment_normal",
|
|
||||||
selectedImage: "tab_moment_selected"
|
|
||||||
)
|
|
||||||
|
|
||||||
let blankVC2 = UIViewController()
|
|
||||||
blankVC2.view.backgroundColor = .white
|
|
||||||
blankVC2.tabBarItem = createTabBarItem(
|
|
||||||
title: "我的",
|
|
||||||
normalImage: "tab_mine_normal",
|
|
||||||
selectedImage: "tab_mine_selected"
|
|
||||||
)
|
|
||||||
|
|
||||||
viewControllers = [blankVC1, blankVC2]
|
|
||||||
selectedIndex = 0
|
|
||||||
|
|
||||||
NSLog("[EPTabBarController] 初始 ViewControllers 设置完成")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 创建 TabBarItem
|
|
||||||
/// - Parameters:
|
|
||||||
/// - title: 标题
|
|
||||||
/// - normalImage: 未选中图标名称
|
|
||||||
/// - selectedImage: 选中图标名称
|
|
||||||
/// - Returns: UITabBarItem
|
|
||||||
private func createTabBarItem(title: String, normalImage: String, selectedImage: String) -> UITabBarItem {
|
|
||||||
let item = UITabBarItem(
|
|
||||||
title: title,
|
|
||||||
image: UIImage(named: normalImage)?.withRenderingMode(.alwaysOriginal),
|
|
||||||
selectedImage: UIImage(named: selectedImage)?.withRenderingMode(.alwaysOriginal)
|
|
||||||
)
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Public Methods
|
|
||||||
|
|
||||||
/// 登录成功后刷新 TabBar
|
|
||||||
/// - Parameter isLogin: 是否已登录
|
|
||||||
func refreshTabBar(isLogin: Bool) {
|
|
||||||
isLoggedIn = isLogin
|
|
||||||
|
|
||||||
if isLogin {
|
|
||||||
setupLoggedInViewControllers()
|
|
||||||
} else {
|
|
||||||
setupInitialViewControllers()
|
|
||||||
}
|
|
||||||
|
|
||||||
NSLog("[EPTabBarController] TabBar 已刷新,登录状态: \(isLogin)")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 设置登录后的 ViewControllers
|
|
||||||
private func setupLoggedInViewControllers() {
|
|
||||||
// 只在 viewControllers 为空或不是正确类型时才创建
|
|
||||||
if viewControllers?.count != 2 ||
|
|
||||||
!(viewControllers?[0] is EPMomentViewController) ||
|
|
||||||
!(viewControllers?[1] is EPMineViewController) {
|
|
||||||
|
|
||||||
// 创建真实的 ViewController(OC 类)
|
|
||||||
let momentVC = EPMomentViewController()
|
|
||||||
momentVC.tabBarItem = createTabBarItem(
|
|
||||||
title: "动态",
|
|
||||||
normalImage: "tab_moment_normal",
|
|
||||||
selectedImage: "tab_moment_selected"
|
|
||||||
)
|
|
||||||
|
|
||||||
let mineVC = EPMineViewController()
|
|
||||||
mineVC.tabBarItem = createTabBarItem(
|
|
||||||
title: "我的",
|
|
||||||
normalImage: "tab_mine_normal",
|
|
||||||
selectedImage: "tab_mine_selected"
|
|
||||||
)
|
|
||||||
|
|
||||||
viewControllers = [momentVC, mineVC]
|
|
||||||
NSLog("[EPTabBarController] 登录后 ViewControllers 创建完成 - Moment & Mine")
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedIndex = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - UITabBarControllerDelegate
|
|
||||||
|
|
||||||
extension EPTabBarController: UITabBarControllerDelegate {
|
|
||||||
|
|
||||||
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
|
||||||
NSLog("[EPTabBarController] 选中 Tab: \(item.title ?? "Unknown")")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 禁用系统默认的切换动画
|
|
||||||
func tabBarController(_ tabBarController: UITabBarController,
|
|
||||||
animationControllerForTransitionFrom fromVC: UIViewController,
|
|
||||||
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
|
||||||
// 返回 nil 表示不使用动画
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 完全控制是否允许切换
|
|
||||||
func tabBarController(_ tabBarController: UITabBarController,
|
|
||||||
shouldSelect viewController: UIViewController) -> Bool {
|
|
||||||
// 允许切换,但通过返回 nil 的 animationController 来禁用动画
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - OC Compatibility
|
|
||||||
|
|
||||||
extension EPTabBarController {
|
|
||||||
|
|
||||||
/// OC 兼容:创建实例的工厂方法
|
|
||||||
@objc static func create() -> EPTabBarController {
|
|
||||||
return EPTabBarController()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// OC 兼容:刷新 TabBar 方法
|
|
||||||
@objc func refreshTabBarWithIsLogin(_ isLogin: Bool) {
|
|
||||||
refreshTabBar(isLogin: isLogin)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -17,8 +17,7 @@
|
|||||||
/// @param pageSize 一页的个数
|
/// @param pageSize 一页的个数
|
||||||
/// @param types 类型 0,2
|
/// @param types 类型 0,2
|
||||||
+ (void)momentsRecommendList:(HttpRequestHelperCompletion)completion page:(NSString *)page pageSize:(NSString *)pageSize types:(NSString *)types {
|
+ (void)momentsRecommendList:(HttpRequestHelperCompletion)completion page:(NSString *)page pageSize:(NSString *)pageSize types:(NSString *)types {
|
||||||
NSString * fang = [NSString stringFromBase64String:@"ZHluYW1pYy9zcXVhcmUvcmVjb21tZW5kRHluYW1pY3M="];///dynamic/square/recommendDynamics
|
[self makeRequest:@"dynamic/square/recommendDynamics" method:HttpRequestHelperMethodGET completion:completion, __FUNCTION__, page, pageSize, types, nil];
|
||||||
[self makeRequest:fang method:HttpRequestHelperMethodGET completion:completion, __FUNCTION__, page, pageSize, types, nil];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 朋友圈动态最新列表
|
/// 朋友圈动态最新列表
|
||||||
@@ -27,8 +26,7 @@
|
|||||||
/// @param pageSize 一页的个数
|
/// @param pageSize 一页的个数
|
||||||
/// @param types 类型 0,2
|
/// @param types 类型 0,2
|
||||||
+ (void)momentsLatestList:(HttpRequestHelperCompletion)completion dynamicId:(NSString *)dynamicId pageSize:(NSString *)pageSize types:(NSString *)types {
|
+ (void)momentsLatestList:(HttpRequestHelperCompletion)completion dynamicId:(NSString *)dynamicId pageSize:(NSString *)pageSize types:(NSString *)types {
|
||||||
NSString * fang = [NSString stringFromBase64String:@"ZHluYW1pYy9zcXVhcmUvbGF0ZXN0RHluYW1pY3M="];///dynamic/square/latestDynamics
|
[self makeRequest:@"dynamic/square/latestDynamics" method:HttpRequestHelperMethodGET completion:completion, __FUNCTION__, dynamicId, pageSize, types, nil];
|
||||||
[self makeRequest:fang method:HttpRequestHelperMethodGET completion:completion, __FUNCTION__, dynamicId, pageSize, types, nil];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 朋友圈动态关注列表
|
/// 朋友圈动态关注列表
|
||||||
|
Reference in New Issue
Block a user