问题描述
我正在尝试构建一个UIView
,其中有几个UIImageView
以圆形,重叠的方式排列(请参见下图)。假设我们有N张图片。画出第一个N-1很容易,只需使用sin / cos函数将UIImageView
的中心围绕一个圆排列即可。 问题出在最后一张看似具有两个z-index值的图像!我知道这是可能的,因为kik Messenger拥有类似的个人资料照片。
到目前为止,我想到的最好的主意是拍摄最后一张图像,分成“上半部分”和“下半部分”,并为每个图像分配不同的z值。当图像是最左边的图像时,这似乎可行,但是如果图像是最上面的图像,会发生什么呢?在这种情况下,我需要左右分开而不是上下分开。
由于这个问题,它可能不是顶部,左侧或右侧,而是更像是从整个面板中心到UIImageView
中心的某个假想轴上的分裂。 我该怎么做?!
下面的代码将在圆圈中布局UIImageView
您需要导入SDWebImage
并提供一些图片网址以运行下面的代码。
import Foundation
import UIKit
import SDWebImage
class EventDetailsFacepileView: UIView {
static let dimension: CGFloat = 66.0
static let radius: CGFloat = dimension / 1.68
private var profilePicViews: [UIImageView] = []
var profilePicURLs: [URL] = [] {
didSet {
updateView()
}
}
func updateView() {
self.profilePicViews = profilePicURLs.map({ (profilePic) -> UIImageView in
let imageView = UIImageView()
imageView.sd_setimage(with: profilePic)
imageView.roundImage(imageDimension: EventDetailsFacepileView.dimension,showsBorder: true)
imageView.sd_imageTransition = .fade
return imageView
})
self.profilePicViews.forEach { (imageView) in
self.addSubview(imageView)
}
self.setNeedsLayout()
self.layer.borderColor = UIColor.green.cgColor
self.layer.borderWidth = 2
}
override func layoutSubviews() {
super.layoutSubviews()
let xOffset: CGFloat = 0
let yOffset: CGFloat = 0
let center = CGPoint(x: self.bounds.size.width / 2,y: self.bounds.size.height / 2)
let radius: CGFloat = EventDetailsFacepileView.radius
let angleStep: CGFloat = 2 * CGFloat(Double.pi) / CGFloat(profilePicViews.count)
var count = 0
for profilePicView in profilePicViews {
let xPos = center.x + CGFloat(cosf(Float(angleStep) * Float(count))) * (radius - xOffset)
let yPos = center.y + CGFloat(sinf(Float(angleStep) * Float(count))) * (radius - yOffset)
profilePicView.frame = CGRect(origin: CGPoint(x: xPos,y: yPos),size: CGSize(width: EventDetailsFacepileView.dimension,height: EventDetailsFacepileView.dimension))
count += 1
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let requiredSize = EventDetailsFacepileView.dimension + EventDetailsFacepileView.radius
return CGSize(width: requiredSize,height: requiredSize)
}
}
解决方法
我认为您尝试分割图像以超过/低于z索引不会成功。
一种方法是使用遮罩使其显示图像视图重叠。
总体思路是:
- 子类
UIImageView
- 在layoutSubviews() 中
- 将cornerRadius应用于图层以使图像变圆
- 从“重叠视图”中获取矩形
- 将矩形转换为本地坐标
- 将其扩展为所需的“轮廓”宽度
- 从该矩形获取一条椭圆形路径
- 将其与自我相结合
- 将其应用为遮罩层
这里是一个例子。...
我不完全确定您的尺寸计算在做什么……试图按原样使用EventDetailsFacepileView
会在视图的右下角显示小图像?
因此,我通过以下几种方式修改了您的EventDetailsFacepileView
:
- 使用名为“ pro1”至“ pro5”的本地图像(您应该可以用
SDWebImage
替换) - 使用自动布局约束,而不使用显式框架
- 使用
MyOverlapImageView
类来处理遮罩
代码-没有@IBOutlet
连接,因此只需将空白视图控制器设置为OverlapTestViewController
:
class OverlapTestViewController: UIViewController {
let facePileView = MyFacePileView()
override func viewDidLoad() {
super.viewDidLoad()
facePileView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(facePileView)
facePileView.dimension = 120
let sz = facePileView.sizeThatFits(.zero)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
facePileView.widthAnchor.constraint(equalToConstant: sz.width),facePileView.heightAnchor.constraint(equalTo: facePileView.widthAnchor),facePileView.centerXAnchor.constraint(equalTo: g.centerXAnchor),facePileView.centerYAnchor.constraint(equalTo: g.centerYAnchor),])
facePileView.profilePicNames = [
"pro1","pro2","pro3","pro4","pro5"
]
}
}
class MyFacePileView: UIView {
var dimension: CGFloat = 66.0
lazy var radius: CGFloat = dimension / 1.68
private var profilePicViews: [MyOverlapImageView] = []
var profilePicNames: [String] = [] {
didSet {
updateView()
}
}
func updateView() {
self.profilePicViews = profilePicNames.map({ (profilePic) -> MyOverlapImageView in
let imageView = MyOverlapImageView()
if let img = UIImage(named: profilePic) {
imageView.image = img
}
return imageView
})
// add MyOverlapImageViews to self
// and set width / height constraints
self.profilePicViews.forEach { (imageView) in
self.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: dimension).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
}
// start at "12 o'clock"
var curAngle: CGFloat = .pi * 1.5
// angle increment
let incAngle: CGFloat = ( 360.0 / CGFloat(self.profilePicViews.count) ) * .pi / 180.0
// calculate position for each image view
// set center constraints
self.profilePicViews.forEach { imgView in
let xPos = cos(curAngle) * radius
let yPos = sin(curAngle) * radius
imgView.centerXAnchor.constraint(equalTo: centerXAnchor,constant: xPos).isActive = true
imgView.centerYAnchor.constraint(equalTo: centerYAnchor,constant: yPos).isActive = true
curAngle += incAngle
}
// set "overlapView" property for each image view
let n = self.profilePicViews.count
for i in (1..<n).reversed() {
self.profilePicViews[i].overlapView = self.profilePicViews[i-1]
}
self.profilePicViews[0].overlapView = self.profilePicViews[n - 1]
self.layer.borderColor = UIColor.green.cgColor
self.layer.borderWidth = 2
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let requiredSize = dimension * 2.0 + radius / 2.0
return CGSize(width: requiredSize,height: requiredSize)
}
}
class MyOverlapImageView: UIImageView {
// reference to the view that is overlapping me
weak var overlapView: MyOverlapImageView?
// width of "outline"
var outlineWidth: CGFloat = 6
override func layoutSubviews() {
super.layoutSubviews()
// make image round
layer.cornerRadius = bounds.size.width * 0.5
layer.masksToBounds = true
let mask = CAShapeLayer()
if let v = overlapView {
// get bounds from overlapView
// converted to self
// inset by outlineWidth (negative numbers will make it grow)
let maskRect = v.convert(v.bounds,to: self).insetBy(dx: -outlineWidth,dy: -outlineWidth)
// oval path from mask rect
let path = UIBezierPath(ovalIn: maskRect)
// path from self bounds
let clipPath = UIBezierPath(rect: bounds)
// append paths
clipPath.append(path)
mask.path = clipPath.cgPath
mask.fillRule = .evenOdd
// apply mask
layer.mask = mask
}
}
}
结果:
(我通过在Google上搜索sample profile pictures
来获取随机图像)