子类化UIView创建筛选框:子类内设置Frame与背景色不生效问题
自定义UIView子类设置Frame和背景色不生效的问题解决
嗨,我来帮你捋清楚这个问题~你遇到的情况绝对不是子类不能设置frame或背景色,核心问题出在初始化时机+UIView的布局职责划分上!
先分析你代码里的问题
你现在的写法有两个关键问题:
- 创建时机不对:你大概率是把
let f = FilterView(frame: Layout.filterFrame)作为全局变量写在ViewController里了吧?如果是这样,当这个实例被创建时,ViewController的view还没完成加载,Layout.filterFrame如果是基于当前页面的view尺寸计算的,那得到的frame大概率是CGRect.zero(宽高都是0),自然看不到任何内容。而当你在按钮点击事件里重新设置frame时,view已经加载完成,Layout.filterFrame能算出正确的尺寸,所以就能显示了。 - 职责越界:UIView子类的核心职责是封装自己内部的子视图布局和交互逻辑,自身的frame(位置、大小)应该交给使用它的父视图来决定,你在子类里硬写
self.frame = Layout.filterFrame反而会限制复用性——万一别的页面需要不同大小的筛选框,你还得改子类代码,这就违背了子类复用的初衷。
给你修改后的代码方案
第一步:优化FilterView子类
把内部布局和自身frame解耦,只负责子视图的管理:
import UIKit class FilterView: UIView { // 内部控件私有化,只暴露必要的交互接口(比如按钮点击事件) private let filterLabel = UILabel() private let applyButton = UIButton(type: .system) // 可以给外部暴露按钮点击的回调,方便ViewController处理逻辑 var applyAction: (() -> Void)? override init(frame: CGRect) { super.init(frame: frame) setupBasicStyle() addSubviews() setupActions() } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 初始化视图基础样式 private func setupBasicStyle() { backgroundColor = .blue // 自身frame交给外部设置,这里只处理背景、圆角这类自身样式 } // 添加所有子视图 private func addSubviews() { addSubview(filterLabel) addSubview(applyButton) filterLabel.text = "筛选范围" filterLabel.textColor = .white filterLabel.font = .systemFont(ofSize: 16) applyButton.setTitle("Apply", for: .normal) applyButton.setTitleColor(.white, for: .normal) } // 绑定控件事件 private func setupActions() { applyButton.addTarget(self, action: #selector(applyButtonTapped), for: .touchUpInside) } // 子视图布局:重写layoutSubviews,基于自身bounds布局,适配任意尺寸 override func layoutSubviews() { super.layoutSubviews() filterLabel.frame = CGRect(x: 20, y: 20, width: bounds.width - 40, height: 30) applyButton.frame = CGRect(x: 40, y: 60, width: 60, height: 30) } @objc private func applyButtonTapped() { applyAction?() } }
第二步:修改ViewController的使用方式
把FilterView的创建放到按钮点击事件里,确保frame计算时机正确:
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 这里不要提前创建FilterView,避免布局时机问题 } @IBAction func butFilter(_ sender: Any) { // 点击按钮时才创建,此时view已经加载完成,Layout.filterFrame能算出正确尺寸 let filterView = FilterView(frame: Layout.filterFrame) // 绑定按钮点击回调 filterView.applyAction = { [weak self] in // 这里写你的筛选逻辑 self?.view.sendSubviewToBack(filterView) // 或者移除筛选框:filterView.removeFromSuperview() } self.view.addSubview(filterView) } }
为什么这样改就没问题?
- 时机正确:在按钮点击时创建FilterView,此时ViewController的view已经完成布局,
Layout.filterFrame能基于当前页面的实际尺寸计算出正确的frame,不会出现宽高为0的情况。 - 复用性强:FilterView不再硬写自身的frame,不管你把它放到哪个页面,设置多大的尺寸,内部的子视图都会自动适配(因为
layoutSubviews是基于自身的bounds布局的)。 - 职责清晰:子类管自己内部的控件和样式,父视图管它的位置和大小,完全符合UIKit的设计逻辑。
内容的提问来源于stack exchange,提问作者Jonno
相关产品推荐
相关产品推荐

