swift-ios-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift iOS UI Skill
Swift iOS UI 开发技能
Generate production-quality iOS UIKit code from UI designs, screenshots, or descriptions.
根据UI设计图、截图或描述生成可用于生产环境的iOS UIKit代码。
Tech Stack (Always Use These)
技术栈(必须使用以下库)
| Purpose | Library |
|---|---|
| Layout | SnapKit — Auto Layout DSL |
| Popups / Toasts | SwiftEntryKit |
| Image Loading | Kingfisher or SDWebImage |
| JSON Parsing | SwiftyJSON |
| 用途 | 库 |
|---|---|
| 布局 | SnapKit — 自动布局DSL |
| 弹窗/提示框 | SwiftEntryKit |
| 图片加载 | Kingfisher 或 SDWebImage |
| JSON解析 | SwiftyJSON |
Step-by-Step Workflow
分步工作流程
0. Understand the Input
0. 理解输入内容
The user may provide one of the following — adapt accordingly:
| Input type | How to handle |
|---|---|
| Screenshot / image | Carefully read all visible text, colors, layout, spacing, component types |
| Figma / Sketch description | Extract component hierarchy, spacing tokens, color styles |
| Text description | Ask clarifying questions if key details (colors, layout direction, data shape) are missing |
| No input yet | Ask the user: "Please share a UI screenshot, design spec, or describe the screen you want to build." |
⚠️ Do NOT start generating code until you have enough UI information. If the user provides a screenshot, analyze it fully before writing a single line.
用户可能提供以下任意一种输入,请按需处理:
| 输入类型 | 处理方式 |
|---|---|
| 截图/图片 | 仔细识别所有可见文本、颜色、布局、间距、组件类型 |
| Figma/Sketch设计描述 | 提取组件层级、间距参数、颜色样式 |
| 文字描述 | 如果关键细节(颜色、布局方向、数据结构)缺失,询问用户补充 |
| 尚未提供输入 | 询问用户:"请提供UI截图、设计规范或描述您想要构建的界面。" |
⚠️ 在获取足够的UI信息前,请勿开始生成代码。如果用户提供了截图,请先完整分析后再编写任何代码。
1. Analyze the UI
1. 分析UI
Before writing any code, examine the design and identify:
- Screen type: full screen / modal / bottom sheet / popup
- Layout structure: navigation bar, scroll view, table/collection view, static views
- Components: buttons, labels, images, text fields, cards, cells
- Colors: extract hex values from the design (or best-match hex if approximate)
- Spacing: margins, padding, gaps between elements
- Interactive states: normal / highlighted / disabled / loading / empty / error
编写代码前,请先分析设计并确定:
- 界面类型:完整界面/模态框/底部弹窗/弹出层
- 布局结构:导航栏、滚动视图、表格/集合视图、静态视图
- 组件:按钮、标签、图片、文本框、卡片、单元格
- 颜色:从设计中提取十六进制值(如果是近似值则匹配最接近的十六进制)
- 间距:外边距、内边距、元素间的空隙
- 交互状态:正常/高亮/禁用/加载/空数据/错误
2. Plan the Architecture
2. 规划架构
Choose the right UIKit pattern:
- +
UIViewController→ scrollable content screensUIScrollView - +
UIViewController→ list screensUITableView - +
UIViewController→ grid / complex layoutsUICollectionView - subclass → reusable components / cells
UIView - → popups, toasts, bottom sheets, alerts
SwiftEntryKit
选择合适的UIKit模式:
- +
UIViewController→ 可滚动内容界面UIScrollView - +
UIViewController→ 列表界面UITableView - +
UIViewController→ 网格/复杂布局UICollectionView - 子类 → 可复用组件/单元格
UIView - → 弹窗、提示框、底部弹窗、警告框
SwiftEntryKit
3. Generate the Code
3. 生成代码
Follow all conventions in the Code Conventions section below.
遵循下方代码规范中的所有约定。
4. Output Structure
4. 输出结构
For each screen, produce:
- Main or
ViewControllerfileView - Any custom /
UITableViewCellsubclassesUICollectionViewCell - Any reusable subviews extracted as separate subclasses
UIView - A struct/class if JSON data is involved
Model
针对每个界面,生成以下内容:
- 主或
ViewController文件View - 任何自定义/
UITableViewCell子类UICollectionViewCell - 提取为独立子类的可复用子视图
UIView - 如果涉及JSON数据,提供结构体/类
Model
Code Conventions
代码规范
File Structure
文件结构
swift
// MARK: - Properties
// MARK: - Lifecycle
// MARK: - Setup
// MARK: - Layout (SnapKit)
// MARK: - Actions
// MARK: - Data / Network
// MARK: - Helpersswift
// MARK: - Properties
// MARK: - Lifecycle
// MARK: - Setup
// MARK: - Layout (SnapKit)
// MARK: - Actions
// MARK: - Data / Network
// MARK: - HelpersSnapKit Layout Rules
SnapKit布局规则
- Always call and
setupUI()fromsetupConstraints()(orviewDidLoadfor UIView)init - Add subviews in , define constraints in
setupUI()setupConstraints() - Never use or
frame— SnapKit onlyautoresizingMask - Use named constants for spacing:
enum Layout { static let margin: CGFloat = 16 }
swift
// ✅ Correct SnapKit usage
private func setupConstraints() {
titleLabel.snp.makeConstraints { make in
make.top.equalTo(headerView.snp.bottom).offset(Layout.margin)
make.leading.trailing.equalToSuperview().inset(Layout.margin)
}
confirmButton.snp.makeConstraints { make in
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(Layout.margin)
make.leading.trailing.equalToSuperview().inset(Layout.margin)
make.height.equalTo(50)
}
}- 务必从(或
viewDidLoad的UIView方法)中调用init和setupUI()setupConstraints() - 在中添加子视图,在
setupUI()中定义约束setupConstraints() - 绝不要使用或
frame— 仅使用SnapKitautoresizingMask - 使用命名常量定义间距:
enum Layout { static let margin: CGFloat = 16 }
swift
// ✅ 正确的SnapKit用法
private func setupConstraints() {
titleLabel.snp.makeConstraints { make in
make.top.equalTo(headerView.snp.bottom).offset(Layout.margin)
make.leading.trailing.equalToSuperview().inset(Layout.margin)
}
confirmButton.snp.makeConstraints { make in
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(Layout.margin)
make.leading.trailing.equalToSuperview().inset(Layout.margin)
make.height.equalTo(50)
}
}Typography — PingFangSC (Always Use This)
字体 — 苹方字体(必须使用本字体)
⚠️ Never useor raw.systemFontstrings — always useUIFont(name:)via the extension below..pingFangSC()
swift
// UIFont+PingFangSC.swift — include this extension in every project
extension UIFont {
enum PingFangSC: String {
case ultralight = "PingFangSC-Ultralight"
case thin = "PingFangSC-Thin"
case light = "PingFangSC-Light"
case regular = "PingFangSC-Regular"
case medium = "PingFangSC-Medium"
case semibold = "PingFangSC-Semibold"
}
static func pingFangSC(_ style: PingFangSC, size: CGFloat) -> UIFont {
return UIFont(name: style.rawValue, size: size) ?? .systemFont(ofSize: size)
}
}Usage reference:
| Weight | Call | Typical use |
|---|---|---|
| Ultralight | | Decorative, large display numbers |
| Thin | | Subtle secondary info |
| Light | | Body text, descriptions |
| Regular | | Default body / labels |
| Medium | | Emphasized labels, button text |
| Semibold | | Titles, nav bar, headers |
swift
// ✅ Correct — always call the extension method
titleLabel.font = .pingFangSC(.semibold, size: 18)
bodyLabel.font = .pingFangSC(.regular, size: 14)
priceLabel.font = .pingFangSC(.medium, size: 16)
subtitleLabel.font = .pingFangSC(.light, size: 13)
// ❌ Never do this
titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
titleLabel.font = UIFont(name: "PingFangSC-Semibold", size: 18) // raw string usage forbidden⚠️ 绝不要使用或原始.systemFont字符串 — 务必通过下方的扩展调用UIFont(name:)。.pingFangSC()
swift
// UIFont+PingFangSC.swift — 请在每个项目中包含此扩展
extension UIFont {
enum PingFangSC: String {
case ultralight = "PingFangSC-Ultralight"
case thin = "PingFangSC-Thin"
case light = "PingFangSC-Light"
case regular = "PingFangSC-Regular"
case medium = "PingFangSC-Medium"
case semibold = "PingFangSC-Semibold"
}
static func pingFangSC(_ style: PingFangSC, size: CGFloat) -> UIFont {
return UIFont(name: style.rawValue, size: size) ?? .systemFont(ofSize: size)
}
}使用参考:
| 字重 | 调用方式 | 典型用途 |
|---|---|---|
| Ultralight | | 装饰性大字号数字 |
| Thin | | 次要辅助信息 |
| Light | | 正文文本、描述 |
| Regular | | 默认正文/标签 |
| Medium | | 强调标签、按钮文本 |
| Semibold | | 标题、导航栏、页眉 |
swift
// ✅ 正确用法 — 务必调用扩展方法
titleLabel.font = .pingFangSC(.semibold, size: 18)
bodyLabel.font = .pingFangSC(.regular, size: 14)
priceLabel.font = .pingFangSC(.medium, size: 16)
subtitleLabel.font = .pingFangSC(.light, size: 13)
// ❌ 请勿这样做
titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
titleLabel.font = UIFont(name: "PingFangSC-Semibold", size: 18) // 禁止使用原始字符串Colors — UIColor.colorWithHexString (Always Use This)
颜色 — UIColor.colorWithHexString(必须使用本方法)
⚠️ Never use,UIColor(hexString:), Hue, or SwiftHEXColors — always useUIColor(hex:)via the extension below.UIColor.colorWithHexString(hex:)
swift
// UIColor+Hex.swift — include this extension in every project
extension UIColor {
static func colorWithHexString(hex: String, alpha: CGFloat = 1.0) -> UIColor {
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
hexSanitized = hexSanitized.hasPrefix("#") ? String(hexSanitized.dropFirst()) : hexSanitized
var rgb: UInt64 = 0
Scanner(string: hexSanitized).scanHexInt64(&rgb)
let r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
let g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
let b = CGFloat(rgb & 0x0000FF) / 255.0
return UIColor(red: r, green: g, blue: b, alpha: alpha)
}
}Usage:
swift
// ✅ Correct
let primary = UIColor.colorWithHexString(hex: "#007AFF")
let dimmed = UIColor.colorWithHexString(hex: "#212226", alpha: 0.5)
// ❌ Never do this
let c1 = UIColor(hexString: "#FF6B35") // SwiftHEXColors — forbidden
let c2 = UIColor(hex: "#2C3E50") // SwiftHEXColors — forbidden
let c3 = primary.lighten(byAmount: 0.2) // Hue — forbidden⚠️ 绝不要使用、UIColor(hexString:)、Hue或SwiftHEXColors — 务必通过下方的扩展调用UIColor(hex:)。UIColor.colorWithHexString(hex:)
swift
// UIColor+Hex.swift — 请在每个项目中包含此扩展
extension UIColor {
static func colorWithHexString(hex: String, alpha: CGFloat = 1.0) -> UIColor {
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
hexSanitized = hexSanitized.hasPrefix("#") ? String(hexSanitized.dropFirst()) : hexSanitized
var rgb: UInt64 = 0
Scanner(string: hexSanitized).scanHexInt64(&rgb)
let r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
let g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
let b = CGFloat(rgb & 0x0000FF) / 255.0
return UIColor(red: r, green: g, blue: b, alpha: alpha)
}
}用法:
swift
// ✅ 正确用法
let primary = UIColor.colorWithHexString(hex: "#007AFF")
let dimmed = UIColor.colorWithHexString(hex: "#212226", alpha: 0.5)
// ❌ 请勿这样做
let c1 = UIColor(hexString: "#FF6B35") // SwiftHEXColors — 禁止使用
let c2 = UIColor(hex: "#2C3E50") // SwiftHEXColors — 禁止使用
let c3 = primary.lighten(byAmount: 0.2) // Hue — 禁止使用Image Loading with Kingfisher
Kingfisher图片加载
swift
// Basic
imageView.kf.setImage(with: URL(string: urlString))
// With placeholder + options
imageView.kf.setImage(
with: URL(string: urlString),
placeholder: UIImage(named: "placeholder"),
options: [
.transition(.fade(0.25)),
.cacheOriginalImage
]
)
// Cancel on reuse (in UITableViewCell)
override func prepareForReuse() {
super.prepareForReuse()
imageView.kf.cancelDownloadTask()
imageView.image = nil
}swift
// 基础用法
imageView.kf.setImage(with: URL(string: urlString))
// 带占位图和选项的用法
imageView.kf.setImage(
with: URL(string: urlString),
placeholder: UIImage(named: "placeholder"),
options: [
.transition(.fade(0.25)),
.cacheOriginalImage
]
)
// 复用时取消下载(在UITableViewCell中)
override func prepareForReuse() {
super.prepareForReuse()
imageView.kf.cancelDownloadTask()
imageView.image = nil
}JSON Parsing with SwiftyJSON
SwiftyJSON解析
swift
import SwiftyJSON
struct UserModel {
let id: Int
let name: String
let avatar: String
let score: Double
init(json: JSON) {
self.id = json["id"].intValue
self.name = json["name"].stringValue
self.avatar = json["avatar"].stringValue
self.score = json["score"].doubleValue
}
static func list(from json: JSON) -> [UserModel] {
return json.arrayValue.map { UserModel(json: $0) }
}
}swift
import SwiftyJSON
struct UserModel {
let id: Int
let name: String
let avatar: String
let score: Double
init(json: JSON) {
self.id = json["id"].intValue
self.name = json["name"].stringValue
self.avatar = json["avatar"].stringValue
self.score = json["score"].doubleValue
}
static func list(from json: JSON) -> [UserModel] {
return json.arrayValue.map { UserModel(json: $0) }
}
}SwiftEntryKit — Popups & Toasts
SwiftEntryKit — 弹窗与提示框
Toast / Snackbar
提示框/消息条
swift
func showToast(message: String, isSuccess: Bool = true) {
var attributes = EKAttributes.topToast
attributes.entryBackground = .color(color: EKColor(isSuccess ? AppColor.primary : .systemRed))
attributes.displayDuration = 2.5
attributes.shadow = .active(with: .init(color: .black, opacity: 0.2, radius: 6))
let style = EKProperty.LabelStyle(
font: .pingFangSC(.medium, size: 14),
color: EKColor(.white)
)
let labelContent = EKProperty.LabelContent(text: message, style: style)
let contentView = EKNoteMessageView(with: labelContent)
SwiftEntryKit.display(entry: contentView, using: attributes)
}swift
func showToast(message: String, isSuccess: Bool = true) {
var attributes = EKAttributes.topToast
attributes.entryBackground = .color(color: EKColor(isSuccess ? AppColor.primary : .systemRed))
attributes.displayDuration = 2.5
attributes.shadow = .active(with: .init(color: .black, opacity: 0.2, radius: 6))
let style = EKProperty.LabelStyle(
font: .pingFangSC(.medium, size: 14),
color: EKColor(.white)
)
let labelContent = EKProperty.LabelContent(text: message, style: style)
let contentView = EKNoteMessageView(with: labelContent)
SwiftEntryKit.display(entry: contentView, using: attributes)
}Center Alert Popup
居中警告弹窗
swift
func showAlertPopup(title: String, message: String, confirmAction: @escaping () -> Void) {
var attributes = EKAttributes.centerFloat
attributes.entryBackground = .color(color: EKColor(.white))
attributes.roundCorners = .all(radius: 16)
attributes.shadow = .active(with: .init(color: .black, opacity: 0.15, radius: 10))
attributes.screenInteraction = .absorbTouches
attributes.entryInteraction = .absorbTouches
attributes.displayDuration = .infinity
// Build your custom UIView popup, then:
let popupView = CustomAlertView(title: title, message: message)
popupView.onConfirm = {
SwiftEntryKit.dismiss()
confirmAction()
}
SwiftEntryKit.display(entry: popupView, using: attributes)
}swift
func showAlertPopup(title: String, message: String, confirmAction: @escaping () -> Void) {
var attributes = EKAttributes.centerFloat
attributes.entryBackground = .color(color: EKColor(.white))
attributes.roundCorners = .all(radius: 16)
attributes.shadow = .active(with: .init(color: .black, opacity: 0.15, radius: 10))
attributes.screenInteraction = .absorbTouches
attributes.entryInteraction = .absorbTouches
attributes.displayDuration = .infinity
// 构建自定义UIView弹窗,然后:
let popupView = CustomAlertView(title: title, message: message)
popupView.onConfirm = {
SwiftEntryKit.dismiss()
confirmAction()
}
SwiftEntryKit.display(entry: popupView, using: attributes)
}Bottom Sheet ⚠️ Always use this exact configuration
底部弹窗 ⚠️ 务必使用此精确配置
swift
/// Standard bottom sheet — MUST use this attribute setup exactly.
/// popupView is a UIView subclass that self-sizes via its own constraints (height: .intrinsic).
func showBottomSheet(popupView: UIView) {
var attributes = EKAttributes()
attributes.position = .bottom
attributes.displayDuration = .infinity
attributes.screenBackground = .color(
color: .init(
light: UIColor(white: 0, alpha: 0.4),
dark: UIColor(white: 0, alpha: 0.4)
)
)
attributes.entryBackground = .clear // popup view draws its own background
attributes.screenInteraction = .dismiss // tap outside to dismiss
attributes.entryInteraction = .forward // touches pass through to content
attributes.scroll = .disabled
attributes.positionConstraints.size = .init(width: .fill, height: .intrinsic)
attributes.positionConstraints.safeArea = .overridden // extend under home indicator
attributes.positionConstraints.verticalOffset = 0
SwiftEntryKit.display(entry: popupView, using: attributes)
}
// Dismiss from inside the popup:
// SwiftEntryKit.dismiss()Rules for the popup UIView:
- Draw its own background (white + top rounded corners) — NOT via
entryBackground- Use SnapKit so the view's intrinsic height is driven by its own content constraints
- Add a bottom padding area to account for home indicator
swift
final class SampleBottomSheetView: UIView {
// MARK: - UI
private let containerView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = 20
v.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
v.clipsToBounds = true
return v
}()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
setupConstraints()
}
required init?(coder: NSCoder) { fatalError() }
// MARK: - Setup
private func setupUI() {
backgroundColor = .clear
addSubview(containerView)
// add content subviews to containerView...
}
private func setupConstraints() {
containerView.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
// ⚠️ Do NOT pin bottom to superview — let content drive height
}
// ...content constraints inside containerView...
// Safe-area bottom padding (home indicator)
let bottomPadding: CGFloat = 34
containerView.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(0)
}
// Add a spacer view at the bottom of containerView with height 34
}
}swift
/// 标准底部弹窗 — 必须完全使用此属性配置。
/// popupView是一个UIView子类,其内部约束会自动适配高度(height: .intrinsic)。
func showBottomSheet(popupView: UIView) {
var attributes = EKAttributes()
attributes.position = .bottom
attributes.displayDuration = .infinity
attributes.screenBackground = .color(
color: .init(
light: UIColor(white: 0, alpha: 0.4),
dark: UIColor(white: 0, alpha: 0.4)
)
)
attributes.entryBackground = .clear // 弹窗视图自行绘制背景
attributes.screenInteraction = .dismiss // 点击外部关闭
attributes.entryInteraction = .forward // 触摸事件传递给内容
attributes.scroll = .disabled
attributes.positionConstraints.size = .init(width: .fill, height: .intrinsic)
attributes.positionConstraints.safeArea = .overridden // 延伸至Home指示器下方
attributes.positionConstraints.verticalOffset = 0
SwiftEntryKit.display(entry: popupView, using: attributes)
}
// 从弹窗内部关闭:
// SwiftEntryKit.dismiss()弹窗UIView规则:
- 自行绘制背景(白色+顶部圆角)— 不要通过
设置entryBackground- 使用SnapKit,使视图的内在高度由自身内容约束决定
- 添加底部内边距区域以适配Home指示器
swift
final class SampleBottomSheetView: UIView {
// MARK: - UI
private let containerView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = 20
v.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
v.clipsToBounds = true
return v
}()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
setupConstraints()
}
required init?(coder: NSCoder) { fatalError() }
// MARK: - Setup
private func setupUI() {
backgroundColor = .clear
addSubview(containerView)
// 向containerView中添加内容子视图...
}
private func setupConstraints() {
containerView.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
// ⚠️ 不要将底部固定到父视图 — 让内容驱动高度
}
// ...containerView内部的内容约束...
// 安全区域底部内边距(适配Home指示器)
let bottomPadding: CGFloat = 34
containerView.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(0)
}
// 在containerView底部添加高度为34的间隔视图
}
}UITableView / UICollectionView Best Practices
UITableView / UICollectionView最佳实践
swift
// Cell registration
tableView.register(ProductCell.self, forCellReuseIdentifier: ProductCell.reuseId)
// Cell class template
final class ProductCell: UITableViewCell {
static let reuseId = "ProductCell"
// MARK: - UI
private let containerView = UIView()
private let thumbImageView = UIImageView()
private let titleLabel = UILabel()
private let priceLabel = UILabel()
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
setupConstraints()
}
required init?(coder: NSCoder) { fatalError() }
// MARK: - Setup
private func setupUI() {
selectionStyle = .none
contentView.addSubview(containerView)
containerView.addSubview(thumbImageView)
containerView.addSubview(titleLabel)
containerView.addSubview(priceLabel)
titleLabel.font = .pingFangSC(.medium, size: 15)
titleLabel.textColor = AppColor.text
priceLabel.font = .pingFangSC(.semibold, size: 16)
priceLabel.textColor = AppColor.primary
}
private func setupConstraints() {
containerView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16))
}
thumbImageView.snp.makeConstraints { make in
make.leading.top.bottom.equalToSuperview()
make.width.height.equalTo(80)
}
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(12)
make.leading.equalTo(thumbImageView.snp.trailing).offset(12)
make.trailing.equalToSuperview().inset(12)
}
priceLabel.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(12)
make.leading.equalTo(thumbImageView.snp.trailing).offset(12)
}
}
// MARK: - Configure
func configure(with model: ProductModel) {
titleLabel.text = model.name
priceLabel.text = "¥\(model.price)"
thumbImageView.kf.setImage(with: URL(string: model.imageUrl), placeholder: UIImage(named: "placeholder"))
}
override func prepareForReuse() {
super.prepareForReuse()
thumbImageView.kf.cancelDownloadTask()
thumbImageView.image = nil
}
}swift
// 单元格注册
tableView.register(ProductCell.self, forCellReuseIdentifier: ProductCell.reuseId)
// 单元格类模板
final class ProductCell: UITableViewCell {
static let reuseId = "ProductCell"
// MARK: - UI
private let containerView = UIView()
private let thumbImageView = UIImageView()
private let titleLabel = UILabel()
private let priceLabel = UILabel()
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
setupConstraints()
}
required init?(coder: NSCoder) { fatalError() }
// MARK: - Setup
private func setupUI() {
selectionStyle = .none
contentView.addSubview(containerView)
containerView.addSubview(thumbImageView)
containerView.addSubview(titleLabel)
containerView.addSubview(priceLabel)
titleLabel.font = .pingFangSC(.medium, size: 15)
titleLabel.textColor = AppColor.text
priceLabel.font = .pingFangSC(.semibold, size: 16)
priceLabel.textColor = AppColor.primary
}
private func setupConstraints() {
containerView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16))
}
thumbImageView.snp.makeConstraints { make in
make.leading.top.bottom.equalToSuperview()
make.width.height.equalTo(80)
}
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(12)
make.leading.equalTo(thumbImageView.snp.trailing).offset(12)
make.trailing.equalToSuperview().inset(12)
}
priceLabel.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(12)
make.leading.equalTo(thumbImageView.snp.trailing).offset(12)
}
}
// MARK: - 配置
func configure(with model: ProductModel) {
titleLabel.text = model.name
priceLabel.text = "¥\(model.price)"
thumbImageView.kf.setImage(with: URL(string: model.imageUrl), placeholder: UIImage(named: "placeholder"))
}
override func prepareForReuse() {
super.prepareForReuse()
thumbImageView.kf.cancelDownloadTask()
thumbImageView.image = nil
}
}Navigation Bar Customization
导航栏自定义
swift
private func setupNavigationBar() {
title = "Page Title"
navigationController?.navigationBar.tintColor = AppColor.primary
navigationController?.navigationBar.titleTextAttributes = [
.foregroundColor: AppColor.text,
.font: UIFont.pingFangSC(.semibold, size: 17)
]
// Right bar button
let rightBtn = UIBarButtonItem(image: UIImage(systemName: "bell"),
style: .plain,
target: self,
action: #selector(rightButtonTapped))
navigationItem.rightBarButtonItem = rightBtn
}swift
private func setupNavigationBar() {
title = "页面标题"
navigationController?.navigationBar.tintColor = AppColor.primary
navigationController?.navigationBar.titleTextAttributes = [
.foregroundColor: AppColor.text,
.font: UIFont.pingFangSC(.semibold, size: 17)
]
// 右侧导航按钮
let rightBtn = UIBarButtonItem(image: UIImage(systemName: "bell"),
style: .plain,
target: self,
action: #selector(rightButtonTapped))
navigationItem.rightBarButtonItem = rightBtn
}Empty State & Loading State
空数据状态与加载状态
swift
// Loading
func showLoading() {
let indicator = UIActivityIndicatorView(style: .medium)
indicator.tag = 999
indicator.startAnimating()
view.addSubview(indicator)
indicator.snp.makeConstraints { $0.center.equalToSuperview() }
}
func hideLoading() {
view.viewWithTag(999)?.removeFromSuperview()
}
// Empty state
func showEmptyState(message: String = "暂无数据") {
let label = UILabel()
label.text = message
label.textColor = AppColor.subtext
label.font = .pingFangSC(.regular, size: 15)
label.tag = 998
view.addSubview(label)
label.snp.makeConstraints { $0.center.equalToSuperview() }
}swift
// 加载状态
func showLoading() {
let indicator = UIActivityIndicatorView(style: .medium)
indicator.tag = 999
indicator.startAnimating()
view.addSubview(indicator)
indicator.snp.makeConstraints { $0.center.equalToSuperview() }
}
func hideLoading() {
view.viewWithTag(999)?.removeFromSuperview()
}
// 空数据状态
func showEmptyState(message: String = "暂无数据") {
let label = UILabel()
label.text = message
label.textColor = AppColor.subtext
label.font = .pingFangSC(.regular, size: 15)
label.tag = 998
view.addSubview(label)
label.snp.makeConstraints { $0.center.equalToSuperview() }
}Output Checklist
输出检查清单
Before finishing, verify:
- No /
frameusage — SnapKit onlyAutoresizingMask - All fonts use extension — never
.pingFangSC()or raw.systemFontstringsUIFont(name:) - Safe area insets handled ()
safeAreaLayoutGuide - All colors use via
UIColor.colorWithHexString(hex:)enum — never Hue or SwiftHEXColorsAppColor - Images use with placeholder
kf.setImage - cancels Kingfisher tasks in cells
prepareForReuse - JSON models use with
SwiftyJSONinit(json: JSON) - Popups use (no
SwiftEntryKitunless truly native alert)UIAlertController - All UI created programmatically (no Storyboard/XIB unless asked)
- sections used for code organization
// MARK: - Spacing values extracted to
enum Layout - Bottom sheets use the exact config from the Bottom Sheet section (never
EKAttributes)EKAttributes.bottomFloat - Bottom sheet popup view sets and draws its own background
entryBackground = .clear - Bottom sheet config includes and
safeArea = .overriddenverticalOffset = 0
完成前,请验证:
- 未使用/
frame— 仅使用SnapKitAutoresizingMask - 所有字体均使用扩展 — 绝不要使用
.pingFangSC()或原始.systemFont字符串UIFont(name:) - 已处理安全区域内边距()
safeAreaLayoutGuide - 所有颜色均通过枚举使用
AppColor— 绝不要使用Hue或SwiftHEXColorsUIColor.colorWithHexString(hex:) - 图片加载使用并带有占位图
kf.setImage - 单元格的方法会取消Kingfisher下载任务
prepareForReuse - JSON模型使用并实现
SwiftyJSONinit(json: JSON) - 弹窗使用(除非是原生警告框,否则不要使用
SwiftEntryKit)UIAlertController - 所有UI均通过代码创建(除非用户要求,否则不要使用Storyboard/XIB)
- 使用注释组织代码
// MARK: - 间距值已提取到中
enum Layout - 底部弹窗使用底部弹窗章节中的精确配置(绝不要使用
EKAttributes)EKAttributes.bottomFloat - 底部弹窗视图设置并自行绘制背景
entryBackground = .clear - 底部弹窗配置包含和
safeArea = .overriddenverticalOffset = 0