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

相关推荐
大熊猫侯佩15 小时前
Swift 6.4 的 Ref / MutableRef 大揭秘:给值类型开一扇“安全的小窗”
ios·swift·编程语言
黑科技iOS上架16 小时前
没有mac电脑如何借助windows系统上传ipa到App Store
经验分享·ios
Layer17 小时前
从 WWDC 26 空间重构(Spatial Reframing)再看端侧 2D 转 3D 的技术演进
ios·aigc
Cutecat_1 天前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
大熊猫侯佩1 天前
WWDC26 SwiftUI 进化之路:砸碎黑盒,彻底迎来开发自由!
ios·swiftui·swift
游戏开发爱好者81 天前
iPhone真机调试有哪些方法?一次定位推送权限问题时整理出来的几种方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
大熊猫侯佩2 天前
WWDC26 最被忽视的王炸:告别“伪并发”陷阱,Swift 6.4 的 async defer
ios·swift·编程语言
h-189-53-6712072 天前
苹果开发者账号防关联3.2f隔离环境传包提审iOS开发上架的高效隔离方案:iOSUploader工具实用解析
ios·ios上架·ios审核·苹果审核·苹果开发者账号·苹果开发者封号
Legendary_0082 天前
LDR6020P:iPad 一体式皮套键盘 OTG 应用的核心引擎
ios·计算机外设·ipad
Digitally2 天前
如何高效地将文件从电脑传输到 iPad:6 种简单方法
ios·电脑·ipad