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?
I uploaded a demo project here as well.
Change your initialize function with the following: You can see the result in the given image link...
To place the small green circle in the upper right corner of the big circle:
.centerX
of the small circle equal to the.trailing
of the big circle with amultiplier
of0.8536
..centerY
of the small circle equal to the.bottom
of the big circle with amultiplier
of0.1464
.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 afunc
calledcomputeMultipliers(angle:)
which computes the multipliers for anyangle
in degrees. Avoid angles exactly90
and180
because that can create multipliers of0
which Auto Layout does not like.Here is standalone example:
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
tolayoutSubviews()
:Explanation of the math behind
computeMultipliers(angle:)
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 from0
to1
where0
is the top of the circle for the vertical constraint and0
is the left edge of the circle for the horizontal constraint. Likewise,1
is the bottom of the circle for the vertical constraint and1
is the right edge of the circle for the horizontal constraint.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 positivex-axis
going counter-clockwise to the line that intersects the circle. Note the the width and height of the unit circle are each2
.sin(angle)
andcos(angle)
each vary from-1
to1
.The equation:
will vary from
0
to2
depending on the angle. Since we're looking for a value from0
to1
, we divide this by2
: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+
:Again, since
sin
varies from-1
to1
, this calculation will be from0
to2
, so we divide by2
:This gives us the desired result. When the angle is
90
degrees (or.pi/2
radians),sin
is1
, so the vertical multiplier will be0
. When the angle is270
degrees (or3*.pi/2
radians),sin
is-1
and the vertical multiplier will be1
.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 is2 * .pi
. So360
degrees is2 * .pi
radians.