Fix TabBar icon assets and simplify Podfile post_install.

Rename tab icons to snake_case with @2x/@3x scales, register CYL plus button at app launch, and remove redundant Podfile hooks already covered by the Xcode project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 18:49:37 +08:00
parent a1c031c9b7
commit 43179abf2c
52 changed files with 93 additions and 82 deletions

56
Podfile
View File

@ -7,7 +7,7 @@ target 'suixinkan_ios' do
pod 'Kingfisher', '~> 8.0' pod 'Kingfisher', '~> 8.0'
pod 'CYLTabBarController', '~> 1.99.38' pod 'CYLTabBarController', '~> 1.99.38'
# 高德地图 SDK:仅真机构建时链接;模拟器通过 post_install 剥离 # 高德地图 SDK 仅支持真机;模拟器通过下方 post_install 剥离链接
pod 'AMap3DMap-NO-IDFA', '~> 11.1' pod 'AMap3DMap-NO-IDFA', '~> 11.1'
pod 'AMapSearch-NO-IDFA', '~> 9.7' pod 'AMapSearch-NO-IDFA', '~> 9.7'
pod 'AMapLocation-NO-IDFA', '~> 2.11' pod 'AMapLocation-NO-IDFA', '~> 2.11'
@ -17,6 +17,7 @@ target 'suixinkan_ios' do
end end
end end
# 模拟器构建时不链接高德(真机专用 framework仅保留通用 Pod 依赖
SIMULATOR_OTHER_LDFLAGS = <<~FLAGS.squish SIMULATOR_OTHER_LDFLAGS = <<~FLAGS.squish
-ObjC -l"c++" -l"swiftCoreGraphics" -l"z" -ObjC -l"c++" -l"swiftCoreGraphics" -l"z"
-framework "Accelerate" -framework "CFNetwork" -framework "CYLTabBarController" -framework "Accelerate" -framework "CFNetwork" -framework "CYLTabBarController"
@ -32,6 +33,12 @@ post_install do |installer|
target.build_configurations.each do |config| target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
end end
next unless target.name.start_with?('AMap')
target.build_configurations.each do |config|
config.build_settings['SUPPORTED_PLATFORMS'] = 'iphoneos'
end
end end
%w[Pods-suixinkan_ios Pods-suixinkan_iosTests].each do |target_name| %w[Pods-suixinkan_ios Pods-suixinkan_iosTests].each do |target_name|
@ -48,51 +55,4 @@ post_install do |installer|
File.write(path, content) File.write(path, content)
end end
end end
installer.pods_project.targets.each do |target|
next unless target.name.start_with?('AMap')
target.build_configurations.each do |config|
config.build_settings['SUPPORTED_PLATFORMS'] = 'iphoneos'
end
end
resources_script = File.join(
installer.sandbox.root,
'Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-resources.sh'
)
if File.exist?(resources_script)
content = File.read(resources_script)
unless content.include?('SKIP_AMAP_RESOURCES_FOR_SIMULATOR')
content.sub!(
"RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt",
<<~SHELL
# SKIP_AMAP_RESOURCES_FOR_SIMULATOR
if [ "${EFFECTIVE_PLATFORM_NAME}" = "-iphonesimulator" ]; then
exit 0
fi
RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
SHELL
)
File.write(resources_script, content)
end
end
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.user_project.native_targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = ''
config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
next unless target.name == 'suixinkan_ios'
existing = config.build_settings['SWIFT_ACTIVE_COMPILATION_CONDITIONS[sdk=iphoneos*]'] || '$(inherited)'
unless existing.to_s.include?('AMAP_ENABLED')
config.build_settings['SWIFT_ACTIVE_COMPILATION_CONDITIONS[sdk=iphoneos*]'] = "#{existing} AMAP_ENABLED"
end
end
end
aggregate_target.user_project.save
end
end end

View File

@ -39,6 +39,6 @@ SPEC CHECKSUMS:
Kingfisher: db468f911dd666c9134dcbeec4db8bed52e4132d Kingfisher: db468f911dd666c9134dcbeec4db8bed52e4132d
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
PODFILE CHECKSUM: 4068d11e2fdbc0a6514a9ec709302300b8e54da8 PODFILE CHECKSUM: 9659fc63d0173f0286a7da18725e548d86c24ca6
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@ -55,6 +55,8 @@
}; };
A1TEST012FEE49BE00240726 /* suixinkan_iosTests */ = { A1TEST012FEE49BE00240726 /* suixinkan_iosTests */ = {
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = suixinkan_iosTests; path = suixinkan_iosTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -243,14 +245,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-resources.sh\"\n";
@ -308,14 +306,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-suixinkan_ios/Pods-suixinkan_ios-frameworks.sh\"\n";

View File

@ -33,13 +33,13 @@ enum AppTab: String, CaseIterable, Identifiable, Hashable {
var selectedImageName: String { var selectedImageName: String {
switch self { switch self {
case .home: case .home:
"TabHomeSelected" "tab_home_selected"
case .orders: case .orders:
"TabOrderSelected" "tab_order_selected"
case .statistics: case .statistics:
"TabDataSelected" "tab_data_selected"
case .profile: case .profile:
"TabProfileSelected" "tab_profile_selected"
} }
} }
@ -47,13 +47,13 @@ enum AppTab: String, CaseIterable, Identifiable, Hashable {
var unselectedImageName: String { var unselectedImageName: String {
switch self { switch self {
case .home: case .home:
"TabHomeUnselected" "tab_home_unselected"
case .orders: case .orders:
"TabOrderUnselected" "tab_order_unselected"
case .statistics: case .statistics:
"TabDataUnselected" "tab_data_unselected"
case .profile: case .profile:
"TabProfileUnselected" "tab_profile_unselected"
} }
} }
@ -66,7 +66,6 @@ enum AppTab: String, CaseIterable, Identifiable, Hashable {
image: unselectedImage, image: unselectedImage,
selectedImage: selectedImage selectedImage: selectedImage
) )
item.accessibilityIdentifier = "main.tab.\(rawValue)"
return item return item
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "icon_scan.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon_scan@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon_scan@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_data_selected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_data_selected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_data_unselected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_data_unselected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_home_selected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_home_selected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_home_unselected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_home_unselected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_order_selected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_order_selected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_order_unselected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_order_unselected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_profile_selected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_profile_selected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -6,10 +6,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "tab_profile_unselected@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "tab_profile_unselected@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -18,12 +18,11 @@ final class MainTabBarController: CYLTabBarController {
/// ///
init(services: AppServices) { init(services: AppServices) {
self.services = services self.services = services
MainScanPlusButton.register()
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
MainScanPlusButton.onScanTap = { [weak self] in MainScanPlusButton.onScanTap = { [weak self] in
self?.presentGlobalScanner() self?.presentGlobalScanner()
} }
delegate = self // delegate = self
} }
@available(*, unavailable) @available(*, unavailable)
@ -86,11 +85,10 @@ final class MainTabBarController: CYLTabBarController {
tabNavigationControllers[tab] = navigationController tabNavigationControllers[tab] = navigationController
return navigationController return navigationController
} }
applyTabAccessibilityIdentifiers()
} }
/// CYLTabBarController TabBarItem /// CYLTabBarController TabBarItem
private func makeTabBarItemAttributes(for tab: AppTab) -> [String: Any] { private func makeTabBarItemAttributes(for tab: AppTab) -> [String: String] {
[ [
CYLTabBarItemTitle: tab.title, CYLTabBarItemTitle: tab.title,
CYLTabBarItemImage: tab.unselectedImageName, CYLTabBarItemImage: tab.unselectedImageName,
@ -98,13 +96,6 @@ final class MainTabBarController: CYLTabBarController {
] ]
} }
/// CYL TabBarItem
private func applyTabAccessibilityIdentifiers() {
for tab in AppTab.allCases {
tabNavigationControllers[tab]?.tabBarItem.accessibilityIdentifier = "main.tab.\(tab.rawValue)"
}
}
/// ViewModel /// ViewModel
private func bindBadgeViewModel() { private func bindBadgeViewModel() {
badgeViewModel.onChange = { [weak self] in badgeViewModel.onChange = { [weak self] in

View File

@ -16,10 +16,20 @@ final class MainScanPlusButton: CYLPlusButton, CYLPlusButtonSubclassing {
/// CYLTabBarController 使 /// CYLTabBarController 使
static func plusButton() -> Any { static func plusButton() -> Any {
let button = MainScanPlusButton(type: .custom) let button = MainScanPlusButton()
button.frame = CGRect(x: 0, y: 0, width: 70, height: 64) button.setImage(UIImage(named: "icon_scan"), for: .normal)
button.configureAppearance() button.titleLabel?.textAlignment = .center
button.addTarget(button, action: #selector(handleScanTap), for: .touchUpInside)
button.titleLabel?.font = UIFont.systemFont(ofSize: 10)
button.setTitle("发布", for: .normal)
button.setTitleColor(UIColor.gray, for: .normal)
button.setTitle("选中", for: .selected)
button.setTitleColor(UIColor(red:255.0/255.0,green:102.0/255.0,blue:0,alpha:1.0), for: .selected)
button.adjustsImageWhenHighlighted = false
button.sizeToFit()
return button return button
} }
@ -46,7 +56,7 @@ final class MainScanPlusButton: CYLPlusButton, CYLPlusButtonSubclassing {
let symbolConfig = UIImage.SymbolConfiguration(pointSize: 30, weight: .bold) let symbolConfig = UIImage.SymbolConfiguration(pointSize: 30, weight: .bold)
let iconView = UIImageView( let iconView = UIImageView(
image: UIImage(systemName: "qrcode.viewfinder", withConfiguration: symbolConfig) image: UIImage(named: "icon_scan")
) )
iconView.tintColor = .white iconView.tintColor = .white
iconView.contentMode = .scaleAspectFit iconView.contentMode = .scaleAspectFit

View File

@ -6,6 +6,7 @@
// //
import UIKit import UIKit
import CYLTabBarController
/// Scene /// Scene
class SceneDelegate: UIResponder, UIWindowSceneDelegate { class SceneDelegate: UIResponder, UIWindowSceneDelegate {
@ -23,6 +24,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
) { ) {
guard let windowScene = scene as? UIWindowScene else { return } guard let windowScene = scene as? UIWindowScene else { return }
MainScanPlusButton.register()
AppUITestLaunchState.resetIfNeeded() AppUITestLaunchState.resetIfNeeded()
let rootViewController = RootViewController() let rootViewController = RootViewController()

View File

@ -41,6 +41,21 @@ final class MainScanPlusButtonTests: XCTestCase {
) )
} }
} }
/// Tab tabBarItem
func testMainTabBarControllerTabBarItemsHaveImages() {
let services = AppServices(apiClient: APIClient(session: MainTabRecordingURLSession()))
let controller = MainTabBarController(services: services)
controller.loadViewIfNeeded()
for tab in AppTab.allCases {
let item = controller.navigationController(for: tab)?.tabBarItem
XCTAssertNotNil(item?.image, "tab \(tab.rawValue) image should not be nil")
XCTAssertNotNil(item?.selectedImage, "tab \(tab.rawValue) selectedImage should not be nil")
XCTAssertGreaterThan(item?.image?.size.width ?? 0, 0)
XCTAssertGreaterThan(item?.selectedImage?.size.width ?? 0, 0)
}
}
} }
/// Tab 使 /// Tab 使