问题描述
我正在尝试创建类似下面的模型的个人资料图片视图.它带有一个绿色小点,表示用户的在线状态.
I'm trying to create a profile picture view that looks like the mock-up below. It has a small green dot to denote the user's online status.
我正在以编程方式创建视图,因此可以重用它.下面是到目前为止的代码.
I'm creating the view programmatically so I can reuse it. Below is my code so far.
import UIKit
@IBDesignable
class ProfileView: UIView {
fileprivate var imageView: UIImageView!
fileprivate var onlineStatusView: UIView!
fileprivate var onlineStatusDotView: UIView!
@IBInspectable
var image: UIImage? {
get { return imageView.image }
set { imageView.image = newValue }
}
@IBInspectable
var shouldShowStatusDot: Bool = true
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
backgroundColor = .clear
imageView = UIImageView(frame: bounds)
imageView.backgroundColor = .lightGray
imageView.clipsToBounds = true
imageView.layer.cornerRadius = imageView.frame.height / 2
addSubview(imageView)
onlineStatusView = UIView(frame: CGRect(x: 0, y: 0, width: (bounds.height / 5), height: (bounds.height / 5)))
onlineStatusView.backgroundColor = .white
onlineStatusView.clipsToBounds = true
onlineStatusView.layer.cornerRadius = onlineStatusView.frame.height / 2
addSubview(onlineStatusView)
onlineStatusDotView = UIView(frame: CGRect(x: 0, y: 0, width: (onlineStatusView.bounds.height / 1.3), height: (onlineStatusView.bounds.height / 1.3)))
onlineStatusDotView.center = onlineStatusView.center
onlineStatusDotView.backgroundColor = UIColor(red: 0.17, green: 0.71, blue: 0.45, alpha: 1.0)
onlineStatusDotView.clipsToBounds = true
onlineStatusDotView.layer.cornerRadius = onlineStatusDotView.frame.height / 2
onlineStatusView.addSubview(onlineStatusDotView)
}
}
我丢失的是如何将绿色圆点视图固定在图像视图右上角的圆形边缘上.显然,视图的框架不是圆形的,因此我无法弄清楚在这种情况下要使用哪些自动布局约束.而且我也不想对值进行硬编码,因为它必须根据图像视图的大小移动.
What has me lost is how to pin the green dot view on the circular edge of the top right corner of the image view. Obviously the view's frame isn't circular so I can't figure out what auto layout constraints to use in this case. And I don't want to hardcode the values either because it has to move depending on the size of the image view.
我必须设置哪些自动布局约束才能将其放置在正确的位置?
What auto layout constraints do I have to set to get it to the right position?
我也在这里上传了演示项目.
推荐答案
将小绿色圆圈放在大圆圈的右上角:
To place the small green circle in the upper right corner of the big circle:
- 使小圆圈成为大圆圈的子视图.
- 添加一个约束,使小圆的
.centerX
等于大圆的.trailing
,且multiplier
为0.8536
. - 添加一个约束,使小圆圈的
.centerY
等于大圆圈的.bottom
且multiplier
为0.1464
的约束.
- Make the small circle a subview of the big circle.
- Add a constraint with the
.centerX
of the small circle equal to the.trailing
of the big circle with amultiplier
of0.8536
. - Add a constraint with the
.centerY
of the small circle equal to the.bottom
of the big circle with amultiplier
of0.1464
.
注意:使用三角学通过查看单位圆并计算比率(distance from top of square containing unit circle)/(height of unit circle)
和(distance from left edge of square containing unit circle)/(width of unit circle)
来计算两个multiplier
.在下面的示例代码中,我提供了一个名为computeMultipliers(angle:)
的func
,它可以计算任意angle
的度数.避免精确地放置90
和180
角度,因为这会产生 Auto Layout 不喜欢的0
乘数.
Note: The two multiplier
s were computed using Trigonometry by looking at the unit circle and computing the ratios: (distance from top of square containing unit circle)/(height of unit circle)
and (distance from left edge of square containing unit circle)/(width of unit circle)
. In the sample code below, I have provided a func
called computeMultipliers(angle:)
which computes the multipliers for any angle
in degrees. Avoid angles exactly 90
and 180
because that can create multipliers of 0
which Auto Layout does not like.
这是一个独立的示例:
class ViewController: UIViewController {
var bigCircle: UIView!
var littleCircle: UIView!
override func viewDidLoad() {
super.viewDidLoad()
bigCircle = UIView()
bigCircle.translatesAutoresizingMaskIntoConstraints = false
bigCircle.backgroundColor = .red
view.addSubview(bigCircle)
bigCircle.widthAnchor.constraint(equalToConstant: 240).isActive = true
bigCircle.heightAnchor.constraint(equalToConstant: 240).isActive = true
littleCircle = UIView()
littleCircle.translatesAutoresizingMaskIntoConstraints = false
littleCircle.backgroundColor = .green
bigCircle.addSubview(littleCircle)
bigCircle.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
bigCircle.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
littleCircle.widthAnchor.constraint(equalToConstant: 60).isActive = true
littleCircle.heightAnchor.constraint(equalToConstant: 60).isActive = true
let (hMult, vMult) = computeMultipliers(angle: 45)
// position the little green circle using a multiplier on the right and bottom
NSLayoutConstraint(item: littleCircle!, attribute: .centerX, relatedBy: .equal, toItem: bigCircle!, attribute: .trailing, multiplier: hMult, constant: 0).isActive = true
NSLayoutConstraint(item: littleCircle!, attribute: .centerY, relatedBy: .equal, toItem: bigCircle!, attribute: .bottom, multiplier: vMult, constant: 0).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
bigCircle.layer.cornerRadius = 0.5 * bigCircle.frame.height
littleCircle.layoutIfNeeded()
littleCircle.layer.cornerRadius = 0.5 * littleCircle.frame.height
}
func computeMultipliers(angle: CGFloat) -> (CGFloat, CGFloat) {
let radians = angle * .pi / 180
let h = (1.0 + cos(radians)) / 2
let v = (1.0 - sin(radians)) / 2
return (h, v)
}
}
这是您代码的修改版本.我添加了约束来设置小圆圈的大小,并移动了将cornerRadius
设置为layoutSubviews()
的代码:
Here is a modified version of your code. I added constraints to set the size of the small circle and moved the code which sets the cornerRadius
to layoutSubviews()
:
class ProfilePictureView: UIView {
var bigCircle: UIView!
var borderCircle: UIView!
var littleCircle: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
bigCircle = UIView(frame: bounds)
bigCircle.backgroundColor = .red
addSubview(bigCircle)
borderCircle = UIView()
borderCircle.translatesAutoresizingMaskIntoConstraints = false
borderCircle.backgroundColor = .white
bigCircle.addSubview(borderCircle)
borderCircle.widthAnchor.constraint(equalTo: bigCircle.widthAnchor, multiplier: 1/3).isActive = true
borderCircle.heightAnchor.constraint(equalTo: bigCircle.heightAnchor, multiplier: 1/3).isActive = true
littleCircle = UIView()
littleCircle.translatesAutoresizingMaskIntoConstraints = false
littleCircle.backgroundColor = .green
borderCircle.addSubview(littleCircle)
littleCircle.widthAnchor.constraint(equalTo: borderCircle.widthAnchor, multiplier: 1/1.3).isActive = true
littleCircle.heightAnchor.constraint(equalTo: borderCircle.heightAnchor, multiplier: 1/1.3).isActive = true
littleCircle.centerXAnchor.constraint(equalTo: borderCircle.centerXAnchor).isActive = true
littleCircle.centerYAnchor.constraint(equalTo: borderCircle.centerYAnchor).isActive = true
let (hMult, vMult) = computeMultipliers(angle: 45)
// position the border circle using a multiplier on the right and bottom
NSLayoutConstraint(item: borderCircle!, attribute: .centerX, relatedBy: .equal, toItem: bigCircle!, attribute: .trailing, multiplier: hMult, constant: 0).isActive = true
NSLayoutConstraint(item: borderCircle!, attribute: .centerY, relatedBy: .equal, toItem: bigCircle!, attribute: .bottom, multiplier: vMult, constant: 0).isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
bigCircle.layer.cornerRadius = bigCircle.frame.height / 2
borderCircle.layoutIfNeeded()
borderCircle.layer.cornerRadius = borderCircle.frame.height / 2
littleCircle.layoutIfNeeded()
littleCircle.layer.cornerRadius = littleCircle.frame.height / 2
}
private func computeMultipliers(angle: CGFloat) -> (CGFloat, CGFloat) {
let radians = angle * .pi / 180
let h = (1.0 + cos(radians)) / 2
let v = (1.0 - sin(radians)) / 2
return (h, v)
}
}
computeMultipliers(angle:)
的想法是应该计算水平约束的乘数和垂直约束的乘数.这些值是一个比例,范围为0
到1
,其中0
是垂直约束的圆的 top ,而0
是 left >水平约束的圆的边缘.同样,对于垂直约束,1
是圆的底部,对于水平约束,1
是圆的右边缘.
The idea of computeMultipliers(angle:)
is that is should compute a multiplier for the horizontal constraint and a multiplier for the vertical constraint. These values are a proportion and will range from 0
to 1
where 0
is the top of the circle for the vertical constraint and 0
is the left edge of the circle for the horizontal constraint. Likewise, 1
is the bottom of the circle for the vertical constraint and 1
is the right edge of the circle for the horizontal constraint.
乘数是通过查看 单位圆来计算的.单位圆是在坐标系上以(0, 0)
为中心的半径1
的圆.关于单位圆(根据定义)的好处是,圆上的点(从原点开始)与圆相交的点是(cos(angle), sin(angle))
,其中角度是从正x-axis
开始测量的,逆时针旋转到与圆相交的线.请注意,单位圆的宽度和高度均为2
.
The multipliers are computed by looking at the unit circle in Trigonometry. The unit circle is a circle of radius 1
centered at (0, 0)
on the coordinate system. The nice thing about the unit circle (by definition) is that the point on the circle where a line (starting at the origin) intersects the circle is (cos(angle), sin(angle))
where the angle is measured starting at positive x-axis
going counter-clockwise to the line that intersects the circle. Note the the width and height of the unit circle are each 2
.
sin(angle)
和cos(angle)
分别从-1
到1
.
等式:
1 + cos(angle)
将根据角度从0
到2
有所不同.由于我们正在寻找一个从0
到1
的值,因此我们将其除以2
:
will vary from 0
to 2
depending on the angle. Since we're looking for a value from 0
to 1
, we divide this by 2
:
// compute the horizontal multiplier based upon the angle
let h = (1.0 + cos(radians)) / 2
在垂直方向上,我们首先注意到坐标系从数学意义上是翻转的.在iOS中,y
沿向下方向增长,但是在数学中,y
沿向上方向增长.为了解决这个问题,垂直计算使用负-
代替+
:
In the vertical direction, we first note the coordinate system is flipped from the mathematical sense. In iOS, y
grows in the downward direction, but in mathematics, y
grows in the upward direction. To account for this, the vertical calculation uses minus -
instead of +
:
1 - sin(angle)
同样,由于sin
从-1
到1
,因此该计算将是从0
到2
,因此我们将其除以2
:
Again, since sin
varies from -1
to 1
, this calculation will be from 0
to 2
, so we divide by 2
:
// compute the vertical multiplier based upon the angle
let h = (1.0 - sin(radians)) / 2
这给了我们想要的结果.当角度为90
度(或.pi/2
弧度)时,sin
为1
,因此垂直乘数将为0
.当角度为270
度(或3*.pi/2
弧度)时,sin
为-1
,垂直乘数将为1
.
This gives us the desired result. When the angle is 90
degrees (or .pi/2
radians), sin
is 1
, so the vertical multiplier will be 0
. When the angle is 270
degrees (or 3*.pi/2
radians), sin
is -1
and the vertical multiplier will be 1
.
为什么要使用弧度?一旦了解弧度,弧度就会很直观.它们只是沿单位圆的圆周的弧长.圆的周长的公式为circumference = 2 * .pi * radius
,因此单位圆的周长为2 * .pi
.所以360
度是2 * .pi
弧度.
Why use radians? Radians are intuitive once you understand what they are. They are just the length of arc along the circumference of the unit circle. The formula for the circumference of a circle is circumference = 2 * .pi * radius
, so for the unit circle, the circumference is 2 * .pi
. So 360
degrees is 2 * .pi
radians.
这篇关于将子视图放置在圆形视图的边缘的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!