将子视图放置在圆形视图的边缘 [英] Position a subview on the edge of a circular shaped view

查看:132
本文介绍了将子视图放置在圆形视图的边缘的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建类似下面的模型的个人资料图片视图.它带有一个绿色小点,表示用户的在线状态.

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:

  1. 使小圆圈成为大圆圈的子视图.
  2. 添加一个约束,使小圆的.centerX等于大圆的.trailing,且multiplier0.8536.
  3. 添加一个约束,使小圆圈的.centerY等于大圆圈的.bottommultiplier0.1464的约束.
  1. Make the small circle a subview of the big circle.
  2. Add a constraint with the .centerX of the small circle equal to the .trailing of the big circle with a multiplier of 0.8536.
  3. Add a constraint with the .centerY of the small circle equal to the .bottom of the big circle with a multiplier of 0.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的度数.避免精确地放置90180角度,因为这会产生 Auto Layout 不喜欢的0乘数.

Note: The two multipliers 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:)的想法是应该计算水平约束的乘数和垂直约束的乘数.这些值是一个比例,范围为01,其中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)分别从-11.

等式:

1 + cos(angle)

将根据角度从02有所不同.由于我们正在寻找一个从01的值,因此我们将其除以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-11,因此该计算将是从02,因此我们将其除以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弧度)时,sin1,因此垂直乘数将为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.

这篇关于将子视图放置在圆形视图的边缘的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆