更改子视图的边界使其在其父视图中居中
Changing bounds of a child view centers it in its parent
我一直在关注解释自定义 ios 控件的教程。本教程没有问题,但我对 frame/bounds 剪辑(不是本教程的一部分)感到非常困惑。
我在故事板的场景中有一个 UIView 实例。此 UIView 的大小为 120 x 120。我正在使用 addSubview
向此容器视图添加自定义控件(扩展 UIControl)。我开始尝试为自定义控件的框架和边界设置不同的宽度和高度,这是控件的初始化程序:
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
}
...并产生此结果(红色是父级,蓝色圆圈是子级):
现在我将 init 更改为:
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
self.frame.size.width = 40
self.frame.size.height = 40
println("frame: \(self.frame)")
println("bounds: \(self.bounds)")
self.clipsToBounds = true
}
这会产生这个结果:
并打印:
框架:(0.0,0.0,40.0,40.0)
边界:(0.0,0.0,40.0,40.0)
但是当我将 initliazer 更改为:
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
self.bounds.size.width = 40
self.bounds.size.height = 40
println("frame: \(self.frame)")
println("bounds: \(self.bounds)")
self.clipsToBounds = true
}
我明白了:
并打印:框架:(40.0,40.0,40.0,40.0)
边界:(0.0,0.0,40.0,40.0)
我似乎无法理解为什么当我更改其边界时此视图会在其父视图中居中。究竟是什么原因造成的? 'frame' 是否总是朝着它的中心剪裁?还是在修改边界后此视图以其父视图为中心并导致 'frame' 更新?是不是有些属性可以改?例如,我如何设法将它放到 top-left 角(与我修改 'frame' 时的方式完全相同)?非常感谢!
编辑:
class ViewController: UIViewController {
@IBOutlet var knobPlaceholder: UIView!
@IBOutlet var valueLabel: UILabel!
@IBOutlet var valueSlider: UISlider!
@IBOutlet var animateSwitch: UISwitch!
var knob: Knob!
override func viewDidLoad() {
super.viewDidLoad()
knob = Knob(frame: knobPlaceholder.bounds)
knobPlaceholder.addSubview(knob)
knobPlaceholder.backgroundColor = UIColor.redColor();
}
@IBAction func sliderValueChanged(slider: UISlider) {
}
@IBAction func randomButtonTouched(button: UIButton) {
}
}
旋钮:
public class Knob: UIControl {
private let knobRenderer = KnobRenderer()
private var backingValue: Float = 0.0
/** Contains the receiver’s current value. */
public var value: Float {
get {
return backingValue
}
set {
setValue(newValue, animated: false)
}
}
/** Sets the receiver’s current value, allowing you to animate the change visually. */
public func setValue(value: Float, animated: Bool) {
if value != backingValue {
backingValue = min(maximumValue, max(minimumValue, value))
}
}
/** Contains the minimum value of the receiver. */
public var minimumValue: Float = 0.0
/** Contains the maximum value of the receiver. */
public var maximumValue: Float = 1.0
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
self.bounds.size.width = 40
self.bounds.size.height = 40
println("frame: \(self.frame)")
println("bounds: \(self.bounds)")
self.clipsToBounds = true
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createSublayers() {
knobRenderer.update(bounds)
knobRenderer.strokeColor = tintColor
knobRenderer.startAngle = -CGFloat(M_PI * 11.0 / 8.0);
knobRenderer.endAngle = CGFloat(M_PI * 3.0 / 8.0);
knobRenderer.pointerAngle = knobRenderer.startAngle;
knobRenderer.lineWidth = 2.0
knobRenderer.pointerLength = 6.0
layer.addSublayer(knobRenderer.trackLayer)
layer.addSublayer(knobRenderer.pointerLayer)
}
}
private class KnobRenderer {
let trackLayer = CAShapeLayer()
let pointerLayer = CAShapeLayer()
var strokeColor: UIColor {
get {
return UIColor(CGColor: trackLayer.strokeColor)
}
set(strokeColor) {
trackLayer.strokeColor = strokeColor.CGColor
pointerLayer.strokeColor = strokeColor.CGColor
}
}
var lineWidth: CGFloat = 1.0 {
didSet {
update();
}
}
var startAngle: CGFloat = 0.0 {
didSet {
update();
}
}
var endAngle: CGFloat = 0.0 {
didSet {
update()
}
}
var backingPointerAngle: CGFloat = 0.0
var pointerAngle: CGFloat {
get { return backingPointerAngle }
set { setPointerAngle(newValue, animated: false) }
}
func setPointerAngle(pointerAngle: CGFloat, animated: Bool) {
backingPointerAngle = pointerAngle
}
var pointerLength: CGFloat = 0.0 {
didSet {
update()
}
}
init() {
trackLayer.fillColor = UIColor.clearColor().CGColor
pointerLayer.fillColor = UIColor.clearColor().CGColor
}
func updateTrackLayerPath() {
let arcCenter = CGPoint(x: trackLayer.bounds.width / 2.0, y: trackLayer.bounds.height / 2.0)
let offset = max(pointerLength, trackLayer.lineWidth / 2.0)
let radius = min(trackLayer.bounds.height, trackLayer.bounds.width) / 2.0 - offset;
trackLayer.path = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true).CGPath
}
func updatePointerLayerPath() {
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: pointerLayer.bounds.width - pointerLength - pointerLayer.lineWidth / 2.0, y: pointerLayer.bounds.height / 2.0))
path.addLineToPoint(CGPoint(x: pointerLayer.bounds.width, y: pointerLayer.bounds.height / 2.0))
pointerLayer.path = path.CGPath
}
func update(bounds: CGRect) {
let position = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
trackLayer.bounds = bounds
trackLayer.position = position
pointerLayer.bounds = bounds
pointerLayer.position = position
update()
}
func update() {
trackLayer.lineWidth = lineWidth
pointerLayer.lineWidth = lineWidth
updateTrackLayerPath()
updatePointerLayerPath()
}
}
在您的更新函数中,您将视图居中。
func update(bounds: CGRect) {
**let position = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)**
trackLayer.bounds = bounds
trackLayer.position = position
pointerLayer.bounds = bounds
pointerLayer.position = position
update()
}
如果你把它去掉,那么你的视图将不再居中。设置框架将其留在左上角而设置边界导致它位于中心的原因是因为设置边界 x/y 不会覆盖框架 x/y。当您设置框架时,稍后您的代码只设置边界,因此框架 x/y 永远不会被覆盖,因此视图停留在左上角。但是,在第二个中没有为框架设置 x/y,所以我猜它采用了你为边界设置的内容,所以它居中。
我建议不要为视图设置边界 x/y,因为它应该始终为 0、0。如果要重新定位它,请使用框架。请记住,框架是相对于父级的,而边界是相对于它自己的。
我一直在关注解释自定义 ios 控件的教程。本教程没有问题,但我对 frame/bounds 剪辑(不是本教程的一部分)感到非常困惑。
我在故事板的场景中有一个 UIView 实例。此 UIView 的大小为 120 x 120。我正在使用 addSubview
向此容器视图添加自定义控件(扩展 UIControl)。我开始尝试为自定义控件的框架和边界设置不同的宽度和高度,这是控件的初始化程序:
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
}
...并产生此结果(红色是父级,蓝色圆圈是子级):
现在我将 init 更改为:
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
self.frame.size.width = 40
self.frame.size.height = 40
println("frame: \(self.frame)")
println("bounds: \(self.bounds)")
self.clipsToBounds = true
}
这会产生这个结果:
但是当我将 initliazer 更改为:
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
self.bounds.size.width = 40
self.bounds.size.height = 40
println("frame: \(self.frame)")
println("bounds: \(self.bounds)")
self.clipsToBounds = true
}
我明白了:
我似乎无法理解为什么当我更改其边界时此视图会在其父视图中居中。究竟是什么原因造成的? 'frame' 是否总是朝着它的中心剪裁?还是在修改边界后此视图以其父视图为中心并导致 'frame' 更新?是不是有些属性可以改?例如,我如何设法将它放到 top-left 角(与我修改 'frame' 时的方式完全相同)?非常感谢!
编辑:
class ViewController: UIViewController {
@IBOutlet var knobPlaceholder: UIView!
@IBOutlet var valueLabel: UILabel!
@IBOutlet var valueSlider: UISlider!
@IBOutlet var animateSwitch: UISwitch!
var knob: Knob!
override func viewDidLoad() {
super.viewDidLoad()
knob = Knob(frame: knobPlaceholder.bounds)
knobPlaceholder.addSubview(knob)
knobPlaceholder.backgroundColor = UIColor.redColor();
}
@IBAction func sliderValueChanged(slider: UISlider) {
}
@IBAction func randomButtonTouched(button: UIButton) {
}
}
旋钮:
public class Knob: UIControl {
private let knobRenderer = KnobRenderer()
private var backingValue: Float = 0.0
/** Contains the receiver’s current value. */
public var value: Float {
get {
return backingValue
}
set {
setValue(newValue, animated: false)
}
}
/** Sets the receiver’s current value, allowing you to animate the change visually. */
public func setValue(value: Float, animated: Bool) {
if value != backingValue {
backingValue = min(maximumValue, max(minimumValue, value))
}
}
/** Contains the minimum value of the receiver. */
public var minimumValue: Float = 0.0
/** Contains the maximum value of the receiver. */
public var maximumValue: Float = 1.0
public override init(frame: CGRect) {
super.init(frame: frame)
createSublayers()
self.bounds.size.width = 40
self.bounds.size.height = 40
println("frame: \(self.frame)")
println("bounds: \(self.bounds)")
self.clipsToBounds = true
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createSublayers() {
knobRenderer.update(bounds)
knobRenderer.strokeColor = tintColor
knobRenderer.startAngle = -CGFloat(M_PI * 11.0 / 8.0);
knobRenderer.endAngle = CGFloat(M_PI * 3.0 / 8.0);
knobRenderer.pointerAngle = knobRenderer.startAngle;
knobRenderer.lineWidth = 2.0
knobRenderer.pointerLength = 6.0
layer.addSublayer(knobRenderer.trackLayer)
layer.addSublayer(knobRenderer.pointerLayer)
}
}
private class KnobRenderer {
let trackLayer = CAShapeLayer()
let pointerLayer = CAShapeLayer()
var strokeColor: UIColor {
get {
return UIColor(CGColor: trackLayer.strokeColor)
}
set(strokeColor) {
trackLayer.strokeColor = strokeColor.CGColor
pointerLayer.strokeColor = strokeColor.CGColor
}
}
var lineWidth: CGFloat = 1.0 {
didSet {
update();
}
}
var startAngle: CGFloat = 0.0 {
didSet {
update();
}
}
var endAngle: CGFloat = 0.0 {
didSet {
update()
}
}
var backingPointerAngle: CGFloat = 0.0
var pointerAngle: CGFloat {
get { return backingPointerAngle }
set { setPointerAngle(newValue, animated: false) }
}
func setPointerAngle(pointerAngle: CGFloat, animated: Bool) {
backingPointerAngle = pointerAngle
}
var pointerLength: CGFloat = 0.0 {
didSet {
update()
}
}
init() {
trackLayer.fillColor = UIColor.clearColor().CGColor
pointerLayer.fillColor = UIColor.clearColor().CGColor
}
func updateTrackLayerPath() {
let arcCenter = CGPoint(x: trackLayer.bounds.width / 2.0, y: trackLayer.bounds.height / 2.0)
let offset = max(pointerLength, trackLayer.lineWidth / 2.0)
let radius = min(trackLayer.bounds.height, trackLayer.bounds.width) / 2.0 - offset;
trackLayer.path = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true).CGPath
}
func updatePointerLayerPath() {
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: pointerLayer.bounds.width - pointerLength - pointerLayer.lineWidth / 2.0, y: pointerLayer.bounds.height / 2.0))
path.addLineToPoint(CGPoint(x: pointerLayer.bounds.width, y: pointerLayer.bounds.height / 2.0))
pointerLayer.path = path.CGPath
}
func update(bounds: CGRect) {
let position = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
trackLayer.bounds = bounds
trackLayer.position = position
pointerLayer.bounds = bounds
pointerLayer.position = position
update()
}
func update() {
trackLayer.lineWidth = lineWidth
pointerLayer.lineWidth = lineWidth
updateTrackLayerPath()
updatePointerLayerPath()
}
}
在您的更新函数中,您将视图居中。
func update(bounds: CGRect) {
**let position = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)**
trackLayer.bounds = bounds
trackLayer.position = position
pointerLayer.bounds = bounds
pointerLayer.position = position
update()
}
如果你把它去掉,那么你的视图将不再居中。设置框架将其留在左上角而设置边界导致它位于中心的原因是因为设置边界 x/y 不会覆盖框架 x/y。当您设置框架时,稍后您的代码只设置边界,因此框架 x/y 永远不会被覆盖,因此视图停留在左上角。但是,在第二个中没有为框架设置 x/y,所以我猜它采用了你为边界设置的内容,所以它居中。
我建议不要为视图设置边界 x/y,因为它应该始终为 0、0。如果要重新定位它,请使用框架。请记住,框架是相对于父级的,而边界是相对于它自己的。