在 iOS Swift 中创建自定义 View 有以下几种常见方式:
1. 纯代码创建自定义 View
基本结构:
swift
import UIKit
class CustomView: UIView {
// MARK: - 子视图
private let titleLabel = UILabel()
private let iconImageView = UIImageView()
// MARK: - 初始化方法
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
// MARK: - 配置方法
private func setupView() {
// 设置自身属性
backgroundColor = .white
layer.cornerRadius = 8
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.1
layer.shadowOffset = CGSize(width: 0, height: 2)
// 配置子视图
configureSubviews()
setupConstraints()
}
private func configureSubviews() {
titleLabel.textColor = .darkGray
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
titleLabel.textAlignment = .center
iconImageView.contentMode = .scaleAspectFit
iconImageView.tintColor = .systemBlue
// 添加到视图层级
addSubview(titleLabel)
addSubview(iconImageView)
}
private func setupConstraints() {
// 关闭自动转换
titleLabel.translatesAutoresizingMaskIntoConstraints = false
iconImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// 图片约束
iconImageView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
iconImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
iconImageView.widthAnchor.constraint(equalToConstant: 40),
iconImageView.heightAnchor.constraint(equalToConstant: 40),
// 标签约束
titleLabel.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 8),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
titleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16)
])
}
// MARK: - 公开方法
func configure(title: String, icon: UIImage?) {
titleLabel.text = title
iconImageView.image = icon
}
}
2. 使用 @IBDesignable 和 @IBInspectable
swift
@IBDesignable
class CustomCardView: UIView {
@IBInspectable var cornerRadius: CGFloat = 8 {
didSet {
layer.cornerRadius = cornerRadius
}
}
@IBInspectable var shadowRadius: CGFloat = 4 {
didSet {
layer.shadowRadius = shadowRadius
}
}
@IBInspectable var shadowOpacity: Float = 0.1 {
didSet {
layer.shadowOpacity = shadowOpacity
}
}
@IBInspectable var borderWidth: CGFloat = 1 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable var borderColor: UIColor = .lightGray {
didSet {
layer.borderColor = borderColor.cgColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
private func setupView() {
// 配置视图
}
}
3. 使用 XIB 文件创建自定义 View
CustomView.xib
xml
<!-- 在 Interface Builder 中设计布局 -->
CustomView.swift
swift
class CustomView: UIView {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
@IBOutlet weak var actionButton: UIButton!
var onButtonTapped: (() -> Void)?
// 从 XIB 加载
class func loadFromNib() -> CustomView {
let bundle = Bundle(for: CustomView.self)
let nib = UINib(nibName: "CustomView", bundle: bundle)
return nib.instantiate(withOwner: nil, options: nil).first as! CustomView
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
private func setupView() {
layer.cornerRadius = 12
actionButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
}
@objc private func buttonTapped() {
onButtonTapped?()
}
func configure(with data: YourDataModel) {
titleLabel.text = data.title
subtitleLabel.text = data.subtitle
}
}
4. 带数据绑应的自定义 View
swift
class UserProfileView: UIView {
struct ViewModel {
let userName: String
let userAvatar: UIImage?
let isOnline: Bool
}
private let avatarImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 25
imageView.clipsToBounds = true
return imageView
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 18, weight: .semibold)
return label
}()
private let statusView: UIView = {
let view = UIView()
view.layer.cornerRadius = 5
return view
}()
// 使用 ViewModel 模式
var viewModel: ViewModel? {
didSet {
updateUI()
}
}
private func updateUI() {
guard let viewModel = viewModel else { return }
avatarImageView.image = viewModel.userAvatar ?? UIImage(systemName: "person.circle")
nameLabel.text = viewModel.userName
statusView.backgroundColor = viewModel.isOnline ? .systemGreen : .lightGray
}
}
5. 支持 Auto Layout 的自定义 View
swift
class AutoLayoutCustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
// 固有内容尺寸
override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: 100)
}
override func layoutSubviews() {
super.layoutSubviews()
// 需要时在此处进行手动布局
}
override func updateConstraints() {
// 在此处更新约束
super.updateConstraints()
}
}
6. 可复用的 UIControl 自定义
swift
class RatingControl: UIControl {
private let stackView = UIStackView()
private var ratingButtons: [UIButton] = []
var rating = 0 {
didSet {
updateButtonSelectionStates()
sendActions(for: .valueChanged)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupButtons()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupButtons()
}
private func setupButtons() {
// 创建按钮
for _ in 0..<5 {
let button = UIButton()
button.setImage(UIImage(systemName: "star"), for: .normal)
button.setImage(UIImage(systemName: "star.fill"), for: .selected)
button.addTarget(self, action: #selector(ratingButtonTapped(_:)), for: .touchUpInside)
ratingButtons.append(button)
stackView.addArrangedSubview(button)
}
stackView.axis = .horizontal
stackView.distribution = .fillEqually
addSubview(stackView)
// 设置约束
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
@objc private func ratingButtonTapped(_ button: UIButton) {
guard let index = ratingButtons.firstIndex(of: button) else { return }
rating = index + 1
}
private func updateButtonSelectionStates() {
for (index, button) in ratingButtons.enumerated() {
button.isSelected = index < rating
}
}
}
使用示例:
swift
// 在 ViewController 中使用
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 方式1:代码创建
let customView = CustomView()
customView.configure(title: "Hello", icon: UIImage(systemName: "star"))
view.addSubview(customView)
// 方式2:XIB 创建
let xibView = CustomView.loadFromNib()
xibView.onButtonTapped = {
print("Button tapped!")
}
view.addSubview(xibView)
// 方式3:IBDesignable 在 Storyboard 中直接使用
}
}
最佳实践建议:
-
职责分离:自定义 View 应该专注于显示和布局
-
数据驱动:使用 ViewModel 模式传递数据
-
重用性:设计通用的、可配置的组件
-
性能优化 :避免在
draw(_:)中做复杂操作 -
内存管理 :注意循环引用,使用
weak或unowned -
测试友好:将配置方法公开,便于单元测试
选择哪种方式取决于具体需求:
-
简单的 UI 组件:纯代码或 @IBDesignable
-
复杂布局:XIB + 代码
-
高度定制:继承 UIView 或 UIControl
-
需要 Interface Builder 预览:@IBDesignable