iOS swift 自定义View

在 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 中直接使用
    }
}

最佳实践建议:

  1. 职责分离:自定义 View 应该专注于显示和布局

  2. 数据驱动:使用 ViewModel 模式传递数据

  3. 重用性:设计通用的、可配置的组件

  4. 性能优化 :避免在 draw(_:) 中做复杂操作

  5. 内存管理 :注意循环引用,使用 weakunowned

  6. 测试友好:将配置方法公开,便于单元测试

选择哪种方式取决于具体需求:

  • 简单的 UI 组件:纯代码或 @IBDesignable

  • 复杂布局:XIB + 代码

  • 高度定制:继承 UIView 或 UIControl

  • 需要 Interface Builder 预览:@IBDesignable

相关推荐
2501_915106322 小时前
HBuilderX 项目上架 iOS app上架 App Store 的关键流程
android·ios·小程序·https·uni-app·iphone·webview
2501_915106322 小时前
iOS 文件管理,在不越狱的前提下管理 iPhone / iPad 文件
android·ios·小程序·uni-app·iphone·webview·ipad
牛马1112 小时前
ios swift处理json数据
ios·json·swift
黑码哥2 小时前
iOS开屏广告多机型屏幕适配最佳实践
macos·ios·cocoa·广告·商业·开屏广告
Swift社区3 小时前
LeetCode 473 火柴拼正方形 - Swift 题解
算法·leetcode·swift
前端不太难4 小时前
Flutter / RN / iOS,在长期维护下谁更容易“止损”?
flutter·ios·状态模式
CareyWYR13 小时前
我开发了一款工具箱类型APP:CreativeUtil
ios·app·mac
2501_9159184115 小时前
只有 Flutter IPA 文件,通过多工具组合完成有效混淆与保护
android·flutter·ios·小程序·uni-app·iphone·webview
川石课堂软件测试21 小时前
Android和iOS APP平台测试的区别
android·数据库·ios·oracle·单元测试·测试用例·cocoa