fix: 固定首页常用应用图标尺寸

This commit is contained in:
2026-07-16 19:02:00 +08:00
parent 5f1c22c6a8
commit 3d5ad8a614
4 changed files with 104 additions and 26 deletions

View File

@ -8,14 +8,49 @@ import UIKit
/// / SF Symbol /// / SF Symbol
enum HomeMenuIconFactory { enum HomeMenuIconFactory {
/// 使
static let canvasSize = CGSize(width: 24, height: 24)
/// 2426pt /// 2426pt
static let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 18, weight: .medium) static let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 18, weight: .medium)
/// Assets SF Symbol private static let imageCache = NSCache<NSString, UIImage>()
/// Assets SF Symbol
static func image(named iconName: String) -> UIImage? { static func image(named iconName: String) -> UIImage? {
if let asset = UIImage(named: iconName) { let cacheKey = iconName as NSString
return asset if let cachedImage = imageCache.object(forKey: cacheKey) {
return cachedImage
} }
return UIImage(systemName: iconName, withConfiguration: symbolConfiguration)
guard let sourceImage = UIImage(named: iconName)
?? UIImage(systemName: iconName, withConfiguration: symbolConfiguration) else {
return nil
}
let normalizedImage = renderOnFixedCanvas(sourceImage)
imageCache.setObject(normalizedImage, forKey: cacheKey)
return normalizedImage
}
private static func renderOnFixedCanvas(_ image: UIImage) -> UIImage {
let sourceSize = image.size
guard sourceSize.width > 0, sourceSize.height > 0 else {
return image
}
let scale = min(canvasSize.width / sourceSize.width, canvasSize.height / sourceSize.height)
let drawSize = CGSize(width: sourceSize.width * scale, height: sourceSize.height * scale)
let drawRect = CGRect(
x: (canvasSize.width - drawSize.width) / 2,
y: (canvasSize.height - drawSize.height) / 2,
width: drawSize.width,
height: drawSize.height
)
let renderer = UIGraphicsImageRenderer(size: canvasSize)
let renderedImage = renderer.image { _ in
image.withRenderingMode(.alwaysOriginal).draw(in: drawRect)
}
return renderedImage.withRenderingMode(.alwaysTemplate)
} }
} }

View File

@ -75,9 +75,8 @@ final class AllFunctionMenuCell: UICollectionViewCell {
cardView.clipsToBounds = true cardView.clipsToBounds = true
iconView.tintColor = AppColor.primary iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit iconView.contentMode = .center
iconView.preferredSymbolConfiguration = HomeMenuIconFactory.symbolConfiguration iconView.adjustsImageSizeForAccessibilityContentSizeCategory = false
contentStackView.axis = .vertical contentStackView.axis = .vertical
contentStackView.alignment = .center contentStackView.alignment = .center

View File

@ -127,6 +127,13 @@ final class HomeMenuCell: UICollectionViewCell {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func prepareForReuse() {
super.prepareForReuse()
iconView.image = nil
iconView.accessibilityIdentifier = nil
titleLabel.text = nil
}
func apply(menu: HomeMenuItem) { func apply(menu: HomeMenuItem) {
iconView.image = HomeMenuIconFactory.image(named: menu.iconName) iconView.image = HomeMenuIconFactory.image(named: menu.iconName)
iconView.accessibilityIdentifier = menu.iconName iconView.accessibilityIdentifier = menu.iconName
@ -146,9 +153,8 @@ final class HomeMenuCell: UICollectionViewCell {
iconContainerView.layer.cornerRadius = 20 iconContainerView.layer.cornerRadius = 20
iconView.tintColor = AppColor.primary iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit iconView.contentMode = .center
iconView.preferredSymbolConfiguration = HomeMenuIconFactory.symbolConfiguration iconView.adjustsImageSizeForAccessibilityContentSizeCategory = false
contentStackView.axis = .vertical contentStackView.axis = .vertical
contentStackView.alignment = .center contentStackView.alignment = .center
@ -176,6 +182,8 @@ final class HomeMenuCell: UICollectionViewCell {
iconContainerView.snp.makeConstraints { make in iconContainerView.snp.makeConstraints { make in
make.width.height.equalTo(40) make.width.height.equalTo(40)
} }
iconContainerView.setContentHuggingPriority(.required, for: .vertical)
iconContainerView.setContentCompressionResistancePriority(.required, for: .vertical)
iconView.snp.makeConstraints { make in iconView.snp.makeConstraints { make in
make.center.equalToSuperview() make.center.equalToSuperview()
make.width.height.equalTo(24) make.width.height.equalTo(24)

View File

@ -14,27 +14,25 @@ final class HomeMenuIconFactoryTests: XCTestCase {
let image = HomeMenuIconFactory.image(named: "home_menu_space") let image = HomeMenuIconFactory.image(named: "home_menu_space")
XCTAssertNotNil(image) XCTAssertNotNil(image)
XCTAssertEqual(image?.size, HomeMenuIconFactory.canvasSize)
XCTAssertNil(UIImage(systemName: "home_menu_space")) XCTAssertNil(UIImage(systemName: "home_menu_space"))
} }
func testSystemSymbolMatchesSharedConfiguration() { func testSystemSymbolsUseFixedCanvas() {
let expected = UIImage( let iconNames = [
systemName: "ellipsis", "ellipsis",
withConfiguration: HomeMenuIconFactory.symbolConfiguration "dot.radiowaves.left.and.right",
) "rectangle.stack.badge.plus",
let actual = HomeMenuIconFactory.image(named: "ellipsis") ]
XCTAssertNotNil(actual) for iconName in iconNames {
XCTAssertEqual(actual?.size, expected?.size) let image = HomeMenuIconFactory.image(named: iconName)
XCTAssertNotNil(image)
XCTAssertEqual(image?.size, HomeMenuIconFactory.canvasSize)
XCTAssertEqual(image?.renderingMode, .alwaysTemplate)
XCTAssertFalse(image?.isSymbolImage ?? true)
} }
func testSystemSymbolDiffersFromUnconfiguredDefault() {
let configured = HomeMenuIconFactory.image(named: "ellipsis")
let plain = UIImage(systemName: "ellipsis")
XCTAssertNotNil(configured)
XCTAssertNotNil(plain)
XCTAssertNotEqual(configured?.size, plain?.size)
} }
func testComplexSystemSymbolResolves() { func testComplexSystemSymbolResolves() {
@ -42,3 +40,41 @@ final class HomeMenuIconFactoryTests: XCTestCase {
XCTAssertNotNil(HomeMenuIconFactory.image(named: "rectangle.stack.badge.plus")) XCTAssertNotNil(HomeMenuIconFactory.image(named: "rectangle.stack.badge.plus"))
} }
} }
/// cell
@MainActor
final class HomeMenuCellTests: XCTestCase {
func testIconKeepsFixedSizeWhenReusedAtIPhoneXWidth() throws {
let cell = HomeMenuCell(frame: CGRect(x: 0, y: 0, width: 109, height: 109))
let menus = [
HomeMenuItem(uri: "space", title: "空间设置", iconName: "home_menu_space"),
HomeMenuItem(uri: "live", title: "直播管理", iconName: "dot.radiowaves.left.and.right"),
HomeMenuItem(uri: "more", title: "更多功能", iconName: "ellipsis"),
]
for menu in menus {
cell.prepareForReuse()
cell.apply(menu: menu)
cell.setNeedsLayout()
cell.layoutIfNeeded()
let iconView = try XCTUnwrap(findImageView(in: cell, identifier: menu.iconName))
XCTAssertEqual(iconView.bounds.size, CGSize(width: 24, height: 24))
XCTAssertEqual(iconView.superview?.bounds.size, CGSize(width: 40, height: 40))
XCTAssertEqual(iconView.image?.size, HomeMenuIconFactory.canvasSize)
XCTAssertEqual(iconView.contentMode, .center)
XCTAssertNil(iconView.preferredSymbolConfiguration)
}
}
private func findImageView(in view: UIView, identifier: String) -> UIImageView? {
if let imageView = view as? UIImageView,
imageView.accessibilityIdentifier == identifier {
return imageView
}
return view.subviews.lazy.compactMap {
self.findImageView(in: $0, identifier: identifier)
}.first
}
}