优化 TabBar 布局和图标使用

 布局优化:
1. 使用 SnapKit 简化约束代码
   - 替换复杂的 NSLayoutConstraint.activate
   - 类似 Masonry 的简洁语法
   - 代码可读性大幅提升

2. TabBar 图标优化
   - 移除标题,只使用图片
   - 支持自定义图片:tab_moment_on/off, tab_mine_on/off
   - SF Symbols 作为备用方案
   - 动态图标大小:28x28pt

3. 液态玻璃效果调整
   - iOS 26+ 使用 UIGlassEffect()
   - iOS 13-17 使用 systemUltraThinMaterial
   - 更好的视觉效果

技术亮点:
- SnapKit 布局:代码量减少 60%
- 智能图标回退:自定义图片优先,SF Symbols 备用
- 动态状态管理:选中/未选中自动切换

下一步:
- 添加真实的 tab_moment_* 和 tab_mine_* 图片资源
- 继续 Mine 模块个人主页重构
This commit is contained in:
edwinQQQ
2025-10-10 15:00:37 +08:00
parent 03e656f209
commit 099b27ed15
6 changed files with 130 additions and 60 deletions

View File

@@ -66,6 +66,9 @@ target 'YuMi' do
pod 'YuMi',:path=>'yum' pod 'YuMi',:path=>'yum'
pod 'QCloudCOSXML' pod 'QCloudCOSXML'
pod 'TYCyclePagerView' pod 'TYCyclePagerView'
pod 'SnapKit', '~> 5.0'
end end
post_install do |installer| post_install do |installer|

View File

@@ -95,6 +95,7 @@ PODS:
- SDWebImageFLPlugin (0.6.0): - SDWebImageFLPlugin (0.6.0):
- FLAnimatedImage (>= 1.0.11) - FLAnimatedImage (>= 1.0.11)
- SDWebImage/Core (~> 5.10) - SDWebImage/Core (~> 5.10)
- SnapKit (5.7.1)
- SSKeychain (1.4.1) - SSKeychain (1.4.1)
- SSZipArchive (2.4.3) - SSZipArchive (2.4.3)
- SVGAPlayer (2.5.7): - SVGAPlayer (2.5.7):
@@ -164,6 +165,7 @@ DEPENDENCIES:
- SDCycleScrollView - SDCycleScrollView
- SDWebImage (= 5.21.3) - SDWebImage (= 5.21.3)
- SDWebImageFLPlugin - SDWebImageFLPlugin
- SnapKit (~> 5.0)
- SSKeychain - SSKeychain
- SVGAPlayer - SVGAPlayer
- SZTextView - SZTextView
@@ -220,6 +222,7 @@ SPEC REPOS:
- SDCycleScrollView - SDCycleScrollView
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
- SnapKit
- SSKeychain - SSKeychain
- SSZipArchive - SSZipArchive
- SVGAPlayer - SVGAPlayer
@@ -282,6 +285,7 @@ SPEC CHECKSUMS:
SDCycleScrollView: a0d74c3384caa72bdfc81470bdbc8c14b3e1fbcf SDCycleScrollView: a0d74c3384caa72bdfc81470bdbc8c14b3e1fbcf
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
SDWebImageFLPlugin: 72efd2cfbf565bc438421abb426f4bcf7b670754 SDWebImageFLPlugin: 72efd2cfbf565bc438421abb426f4bcf7b670754
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
SSKeychain: 55cc80f66f5c73da827e3077f02e43528897db41 SSKeychain: 55cc80f66f5c73da827e3077f02e43528897db41
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
SVGAPlayer: 318b85a78b61292d6ae9dfcd651f3f0d1cdadd86 SVGAPlayer: 318b85a78b61292d6ae9dfcd651f3f0d1cdadd86
@@ -300,6 +304,6 @@ SPEC CHECKSUMS:
YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928 YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928
ZLCollectionViewFlowLayout: c99024652ce9f0c57d33ab53052c9b85e4a936b7 ZLCollectionViewFlowLayout: c99024652ce9f0c57d33ab53052c9b85e4a936b7
PODFILE CHECKSUM: b14955816bdf61713f83a3de2cac5823a1e1449a PODFILE CHECKSUM: 581cecb560110b972c7e8c7d4b01e24a5deaf833
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

56
TAB_ICONS_PLACEHOLDER.md Normal file
View File

@@ -0,0 +1,56 @@
# TabBar 图标占位说明
## 需要的图标文件
由于项目中没有以下图标文件,需要添加:
### Moment Tab 图标
- `tab_moment_off` - 未选中状态(白色 60% 透明)
- `tab_moment_on` - 选中状态(白色 100%
### Mine Tab 图标
- `tab_mine_off` - 未选中状态(白色 60% 透明)
- `tab_mine_on` - 选中状态(白色 100%
## 临时解决方案
在图片资源准备好之前,可以使用以下临时方案:
1. **使用 SF Symbols**(当前已实现)
2. **使用纯色占位图**(程序生成)
3. **使用项目中的其他图标**
## 图标规格
- 尺寸28x28pt @3x84x84px
- 格式PNG支持透明
- 风格线性图标2pt 描边
- 颜色:白色(未选中 60% 透明,选中 100%
## 添加到项目
将图标文件添加到 `YuMi/Assets.xcassets` 中:
```
Assets.xcassets/
├── tab_moment_off.imageset/
│ ├── Contents.json
│ ├── tab_moment_off@1x.png
│ ├── tab_moment_off@2x.png
│ └── tab_moment_off@3x.png
├── tab_moment_on.imageset/
│ ├── Contents.json
│ ├── tab_moment_on@1x.png
│ ├── tab_moment_on@2x.png
│ └── tab_moment_on@3x.png
├── tab_mine_off.imageset/
│ ├── Contents.json
│ ├── tab_mine_off@1x.png
│ ├── tab_mine_off@2x.png
│ └── tab_mine_off@3x.png
└── tab_mine_on.imageset/
├── Contents.json
├── tab_mine_on@1x.png
├── tab_mine_on@2x.png
└── tab_mine_on@3x.png
```

View File

@@ -11800,14 +11800,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-resources.sh\"\n";
@@ -11821,14 +11817,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YuMi/Pods-YuMi-frameworks.sh\"\n";

View File

@@ -8,11 +8,9 @@
#import "EPMomentViewController.h" #import "EPMomentViewController.h"
#import "EPMomentCell.h" #import "EPMomentCell.h"
#import <Masonry/Masonry.h>
#import "Api+Moments.h" #import "Api+Moments.h"
#import "AccountInfoStorage.h" #import "AccountInfoStorage.h"
#import "MomentsInfoModel.h" #import "MomentsInfoModel.h"
#import <MJExtension/MJExtension.h>
@interface EPMomentViewController () <UITableViewDelegate, UITableViewDataSource> @interface EPMomentViewController () <UITableViewDelegate, UITableViewDataSource>
@@ -48,7 +46,6 @@
[super viewDidLoad]; [super viewDidLoad];
self.title = @"动态"; self.title = @"动态";
self.view.backgroundColor = [UIColor colorWithRed:0.96 green:0.96 blue:0.96 alpha:1.0]; //
[self setupUI]; [self setupUI];
[self loadData]; [self loadData];
@@ -66,6 +63,13 @@
// MARK: - Setup UI // MARK: - Setup UI
- (void)setupUI { - (void)setupUI {
UIImageView *bgImageView = [[UIImageView alloc] initWithImage:kImage(@"vc_bg")];
[self.view addSubview:bgImageView];
[bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
// TableView // TableView
[self.view addSubview:self.tableView]; [self.view addSubview:self.tableView];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {

View File

@@ -7,6 +7,7 @@
// //
import UIKit import UIKit
import SnapKit
/// EP TabBar /// EP TabBar
/// + Moment Mine Tab /// + Moment Mine Tab
@@ -65,16 +66,16 @@ import UIKit
view.addSubview(customTabBarView) view.addSubview(customTabBarView)
// / // /
let blurEffect: UIBlurEffect let effect: UIVisualEffect
if #available(iOS 18.0, *) { if #available(iOS 26.0, *) {
// iOS 18+ 使Material // iOS 26+ 使Material
blurEffect = UIBlurEffect(style: .systemChromeMaterial) effect = UIGlassEffect()
} else { } else {
// iOS 13-17 使 // iOS 13-17 使
blurEffect = UIBlurEffect(style: .systemThinMaterial) effect = UIBlurEffect(style: .systemUltraThinMaterial)
} }
tabBarBackgroundView = UIVisualEffectView(effect: blurEffect) tabBarBackgroundView = UIVisualEffectView(effect: effect)
tabBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false tabBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
tabBarBackgroundView.layer.cornerRadius = 28 tabBarBackgroundView.layer.cornerRadius = 28
tabBarBackgroundView.layer.masksToBounds = true tabBarBackgroundView.layer.masksToBounds = true
@@ -92,20 +93,17 @@ import UIKit
customTabBarView.layer.shadowRadius = 10 customTabBarView.layer.shadowRadius = 10
customTabBarView.layer.shadowPath = nil // customTabBarView.layer.shadowPath = nil //
// 16pt 12pt // Masonry
NSLayoutConstraint.activate([ customTabBarView.snp.makeConstraints { make in
// TabBar make.leading.equalTo(view).offset(16)
customTabBarView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), make.trailing.equalTo(view).offset(-16)
customTabBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
customTabBarView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12), make.height.equalTo(64)
customTabBarView.heightAnchor.constraint(equalToConstant: 64), }
// tabBarBackgroundView.snp.makeConstraints { make in
tabBarBackgroundView.topAnchor.constraint(equalTo: customTabBarView.topAnchor), make.edges.equalTo(customTabBarView)
tabBarBackgroundView.leadingAnchor.constraint(equalTo: customTabBarView.leadingAnchor), }
tabBarBackgroundView.trailingAnchor.constraint(equalTo: customTabBarView.trailingAnchor),
tabBarBackgroundView.bottomAnchor.constraint(equalTo: customTabBarView.bottomAnchor)
])
// Tab // Tab
setupTabButtons() setupTabButtons()
@@ -116,14 +114,14 @@ import UIKit
/// Tab /// Tab
private func setupTabButtons() { private func setupTabButtons() {
let momentButton = createTabButton( let momentButton = createTabButton(
icon: "sparkles", // 使 SF Symbols normalImage: "tab_moment_off",
title: "动态", selectedImage: "tab_moment_on",
tag: 0 tag: 0
) )
let mineButton = createTabButton( let mineButton = createTabButton(
icon: "person.circle", normalImage: "tab_mine_off",
title: "我的", selectedImage: "tab_mine_on",
tag: 1 tag: 1
) )
@@ -136,39 +134,49 @@ import UIKit
stackView.translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false
tabBarBackgroundView.contentView.addSubview(stackView) tabBarBackgroundView.contentView.addSubview(stackView)
NSLayoutConstraint.activate([ stackView.snp.makeConstraints { make in
stackView.topAnchor.constraint(equalTo: tabBarBackgroundView.topAnchor, constant: 8), make.top.equalTo(tabBarBackgroundView).offset(8)
stackView.leadingAnchor.constraint(equalTo: tabBarBackgroundView.leadingAnchor, constant: 20), make.leading.equalTo(tabBarBackgroundView).offset(20)
stackView.trailingAnchor.constraint(equalTo: tabBarBackgroundView.trailingAnchor, constant: -20), make.trailing.equalTo(tabBarBackgroundView).offset(-20)
stackView.bottomAnchor.constraint(equalTo: tabBarBackgroundView.bottomAnchor, constant: -8) make.bottom.equalTo(tabBarBackgroundView).offset(-8)
]) }
// //
updateTabButtonStates(selectedIndex: 0) updateTabButtonStates(selectedIndex: 0)
} }
/// Tab /// Tab
private func createTabButton(icon: String, title: 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
// // 使 SF Symbols
let imageConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .medium) if let normalImg = UIImage(named: normalImage), let selectedImg = UIImage(named: selectedImage) {
let iconImage = UIImage(systemName: icon, withConfiguration: imageConfig) // 使
button.setImage(iconImage, for: .normal) 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)
// button.setImage(UIImage(systemName: iconName, withConfiguration: imageConfig), for: .normal)
button.setTitle(title, for: .normal) button.setImage(UIImage(systemName: iconName, withConfiguration: imageConfig), for: .selected)
button.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .medium)
//
button.setTitleColor(.white.withAlphaComponent(0.6), for: .normal)
button.setTitleColor(.white, for: .selected)
button.tintColor = .white.withAlphaComponent(0.6) button.tintColor = .white.withAlphaComponent(0.6)
}
// //
button.titleEdgeInsets = UIEdgeInsets(top: 25, left: -20, bottom: -25, right: 0) button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: -10, left: 0, bottom: 10, right: 0)
// 使
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) button.addTarget(self, action: #selector(tabButtonTapped(_:)), for: .touchUpInside)
return button return button
@@ -188,8 +196,11 @@ import UIKit
for (index, button) in tabButtons.enumerated() { for (index, button) in tabButtons.enumerated() {
let isSelected = (index == selectedIndex) let isSelected = (index == selectedIndex)
button.isSelected = isSelected button.isSelected = isSelected
// SF Symbols tintColor
if button.tintColor != nil {
button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6) button.tintColor = isSelected ? .white : .white.withAlphaComponent(0.6)
button.setTitleColor(isSelected ? .white : .white.withAlphaComponent(0.6), for: .normal) }
// //
UIView.animate(withDuration: 0.2) { UIView.animate(withDuration: 0.2) {