将子视图放置在圆形视图的边缘

Isu*_*uru 1 position uiview ios autolayout swift

我正在尝试创建一个个人资料图片视图,看起来像下面的模型。它带有一个小绿点,表示用户的在线状态。

在此处输入图片说明

我正在以编程方式创建视图,因此可以重用它。下面是到目前为止的代码。

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

我丢失的是如何将绿色圆点视图固定在图像视图右上角的圆形边缘上。显然,视图的框架不是圆形的,因此我无法弄清楚在这种情况下要使用哪些自动布局约束。而且我也不想对值进行硬编码,因为它必须根据图像视图的大小移动。

我必须设置哪些自动布局约束才能将其放置在正确的位置?

我也在这里上传了一个演示项目

vac*_*ama 5

要将小绿色圆圈放在大圆圈的右上角:

  1. 将小圆圈作为大圆圈的子视图。
  2. 添加一个约束.centerX,其中小圆的等于.trailinga multiplier的大圆的约束0.8536
  3. 添加一个约束.centerY,其中小圆的等于.bottoma multiplier的大圆的约束0.1464

注意:这两个multiplier小号使用计算三角通过查看单位圆并计算比率:(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)。在下面的示例代码中,我提供了一个func被调用的对象computeMultipliers(angle:),可以计算任何angle度数的乘数。避免角度正好90180,因为这可以创造乘数的0自动布局不喜欢。


这是一个独立的示例:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

在模拟器中运行的示例代码的图像


这是您代码的修改版本。我添加的约束来设定的小圆圈的大小和移动它设置的代码cornerRadiuslayoutSubviews()

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

第二个带有白色边框的图像


背后的数学解释 computeMultipliers(angle:)

的想法computeMultipliers(angle:)是应该计算水平约束的乘数和垂直约束的乘数。这些值是一个比例,范围从0到到1,其中垂直约束的圆0顶部0是水平约束的圆的边缘。同样,对于垂直约束,是圆1底部,对于水平约束,是圆1边缘。

通过查看三角学中的单位圆来计算乘数。单位圆是以坐标系1为中心的半径的圆(0, 0)。关于单位圆(根据定义)的好处是,圆上的点(从原点开始)与圆相交的点(cos(angle), sin(angle))是从正数开始测量的角度,该角度从x-axis与圆相交的线逆时针开始。请注意,单位圆的宽度和高度均为2

sin(angle)并且cos(angle)每个都从-1到更改1

等式:

1 + cos(angle)
Run Code Online (Sandbox Code Playgroud)

02取决于角度。由于我们正在寻找从0到的值1,因此我们将其除以2

// compute the horizontal multiplier based upon the angle
let h = (1.0 + cos(radians)) / 2
Run Code Online (Sandbox Code Playgroud)

在垂直方向上,我们首先注意到坐标系是从数学意义上翻转的。在iOS中,y向下增长,但在数学上,y向上增长。为了解决这个问题,垂直计算使用负号-代替+

1 - sin(angle)
Run Code Online (Sandbox Code Playgroud)

同样,由于sin-1到变化1,该计算将从02,因此我们除以2

// compute the vertical multiplier based upon the angle
let h = (1.0 - sin(radians)) / 2
Run Code Online (Sandbox Code Playgroud)

这给了我们想要的结果。当该角度90度(或.pi/2弧度),sin1的,所以垂直乘数会0。当角度为270度(或3*.pi/2弧度)时,sin-1,垂直乘数将为1

为什么要使用弧度? 一旦了解弧度,弧度就会很直观。它们只是沿单位圆的圆周的弧长。圆的周长公式为circumference = 2 * .pi * radius,因此单位圆的周长为2 * .pi。所以360度数是2 * .pi弧度。