iOS Swift:将自定义视图Bounds转换为Frame坐标方法咨询
如何让自定义视图完全基于Frame坐标进行布局与绘制?
我来帮你理清这个问题——其实核心是搞懂iOS里Frame和Bounds的区别,然后针对性地转换坐标就行,分两种场景给你讲清楚:
先搞懂Frame和Bounds的核心差异
frame:是当前视图相对于父视图的坐标系(原点在父视图左上角),包含了视图在父视图里的位置和大小;bounds:是视图自身内部的坐标系(默认原点在自身左上角),所有内部绘制、子视图布局默认都是基于这个坐标系的。
你想要的“全部基于Frame坐标”,本质上就是把父视图坐标系下的位置,转换成自定义视图内部能识别的坐标。
场景1:自定义视图内按钮基于父视图Frame坐标布局
如果想让按钮的位置直接对应父视图里的Frame坐标,最简单的方式就是把父视图的目标坐标,减去自定义视图自身的frame.origin,得到按钮在自定义视图内的Frame:
举个例子,假设你的自定义视图在控制器里的Frame是CGRect(x: 50, y: 50, width: 300, height: 200),你想把按钮放在父视图的(100, 100)位置,大小是(80, 40),那按钮的Frame应该这么算:
let targetParentFrame = CGRect(x: 100, y: 100, width: 80, height: 40) // 转换为自定义视图内部的Frame let buttonFrame = CGRect( x: targetParentFrame.origin.x - customView.frame.origin.x, y: targetParentFrame.origin.y - customView.frame.origin.y, width: targetParentFrame.size.width, height: targetParentFrame.size.height ) let button = UIButton(frame: buttonFrame) customView.addSubview(button)
嫌每次计算麻烦?可以给自定义视图加个扩展方法,封装转换逻辑:
extension UIView { /// 添加子视图,并直接传入父视图坐标系下的Frame func addSubview(withParentFrame parentFrame: CGRect, subview: UIView) { let convertedFrame = CGRect( x: parentFrame.origin.x - self.frame.origin.x, y: parentFrame.origin.y - self.frame.origin.y, width: parentFrame.size.width, height: parentFrame.size.height ) subview.frame = convertedFrame self.addSubview(subview) } }
调用的时候就很简洁:
customView.addSubview(withParentFrame: CGRect(x: 100, y: 100, width: 80, height: 40), subview: button)
场景2:让draw方法基于Frame坐标绘制
draw(_:)方法默认使用视图自身的bounds坐标系,要想在这里用父视图的Frame坐标,同样需要做坐标转换:
比如你想在父视图的(120, 120)位置画一个红色的圆,在自定义视图的draw方法里这么写:
override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } // 父视图里的目标坐标点 let parentPoint = CGPoint(x: 120, y: 120) // 转换为自定义视图内部的坐标 let localPoint = CGPoint( x: parentPoint.x - self.frame.origin.x, y: parentPoint.y - self.frame.origin.y ) // 绘制圆 context.addArc(center: localPoint, radius: 20, startAngle: 0, endAngle: .pi * 2, clockwise: true) context.setStrokeColor(UIColor.red.cgColor) context.setLineWidth(2) context.strokePath() }
如果你想彻底省去转换步骤,也可以修改自定义视图的bounds.origin,让内部坐标系的原点直接对齐父视图的原点:
override func layoutSubviews() { super.layoutSubviews() // 把bounds的原点设置为frame.origin的相反数,这样内部的(0,0)就对应父视图的frame.origin self.bounds = CGRect( origin: CGPoint(x: -self.frame.origin.x, y: -self.frame.origin.y), size: self.frame.size ) }
这样之后,不管是draw方法还是子视图布局,都可以直接用父视图的Frame坐标:
// draw方法里直接用父视图坐标 override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } context.addArc(center: CGPoint(x: 120, y: 120), radius: 20, startAngle: 0, endAngle: .pi * 2, clockwise: true) context.setStrokeColor(UIColor.red.cgColor) context.strokePath() } // 按钮直接用父视图Frame button.frame = CGRect(x: 100, y: 100, width: 80, height: 40) customView.addSubview(button)
⚠️ 注意:这种修改bounds.origin的方式,可能会和Auto Layout冲突,如果你的自定义视图用了自动布局,建议还是用第一种坐标转换的方式更安全。
总结一下
- 不管是子视图布局还是绘制操作,核心都是把父视图坐标系下的坐标,转换成自定义视图内部的坐标(减去自定义视图的
frame.origin); - 封装转换方法可以减少重复代码;
- 修改
bounds.origin能彻底省去转换步骤,但要注意Auto Layout的兼容性。
内容的提问来源于stack exchange,提问作者Ashh
相关产品推荐
相关产品推荐

