最近UI出了一个效果图, 多人图像展示效果和qq讨论组一样, 循环且部分重叠的效果。在网上搜了一下,其效果都不理想, 就自己写了一个。
效果图:
思路如下:
图片的边缘裁剪一个弧形, 然后旋转相应角度,算出每个图标的位置, 最后将得到的多个图像绘制在一起,得到一张图像。
代码也很简单, 直接上代码吧!
import Foundation
import UIKit
struct YQ {
}
extension YQ {
struct CircleImage {
let maxCount = 4
var images: [UIImage]?
var size: CGFloat?
var boardWidth: CGFloat = 3
var boardColor: UIColor = UIColor.white
var crossWidth: CGFloat = 20
var direction: Direction = .clockwise
init(images aImages: [UIImage]?, aSize: CGFloat?) {
images = aImages
size = aSize
}
private func subImageRect(at index: Int) -> CGRect? {
guard let size = size else {
return nil
}
let count = min(images?.count ?? 0, maxCount)
guard count > 0 else {
return nil
}
guard index >= 0 && index < count else {
return nil
}
if count == 1 {
return CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: size, height: size))
} else if count == 2 {
let subSize = size / 2 + crossWidth / 2
let y = (size - subSize) / 2
switch index {
case 0:
return CGRect.init(origin: CGPoint.init(x: 0, y: y), size: CGSize.init(width: subSize, height: subSize))
case 1:
let x = (size - crossWidth) / 2
return CGRect.init(origin: CGPoint.init(x: x, y: y), size: CGSize.init(width: subSize, height: subSize))
default:
break
}
}else if count == 3 {
let subSize = (2 * CGFloat(sqrtf(3)) - 3) * size + (1 / (2 + CGFloat(sqrtf(3)))) * crossWidth * 2
var original = CGPoint.zero
switch index {
case 0:
original = CGPoint.init(x: (size - subSize) / 2, y: 0)
case 1:
original = CGPoint.init(x: (CGFloat(sqrtf(3)) + 2) / 4 * size - (2 + CGFloat(sqrtf(3))) / 2 * subSize / 2, y: 0.75 * size - 1.5 * subSize / 2)
case 2:
original = CGPoint.init(x:(2 - CGFloat(sqrtf(3))) / 4 * size + (CGFloat(sqrtf(3)) - 2) / 2 * subSize / 2 , y: 0.75 * size - 1.5 * subSize / 2)
default:
return nil
}
return CGRect.init(origin: original, size: CGSize.init(width: subSize, height: subSize))
}else if count == 4 {
let subSize = size / 2 + crossWidth / 2
let behindO = (size - crossWidth) / 2
switch index {
case 0:
return CGRect.init(x: 0, y: 0, width: subSize, height: subSize)
case 1:
return CGRect.init(x: behindO, y: 0, width: subSize, height: subSize)
case 2:
return CGRect.init(x: behindO, y: behindO, width: subSize, height: subSize)
case 3:
return CGRect.init(x: 0, y: behindO, width: subSize, height: subSize)
default:
break
}
}
return nil
}
private func subImageAngleClockwise(at index: Int) -> SubAngle?{
guard let rect = subImageRect(at: index) else {
return nil
}
let angle = acos(1 - crossWidth / rect.width)
let start = -angle
let end = angle
let count = min(images!.count, maxCount)
if count == 1 {
return SubAngle.init(start: 0, end: CGFloat.pi * 2, rotate: 0)
} else if count == 2 {
switch index {
case 0:
return SubAngle.init(start: 0, end: CGFloat.pi * 2, rotate: 0)
case 1:
return SubAngle.init(start: start, end: end, rotate: CGFloat.pi)
default:
break
}
} else if count == 3 {
var rote: CGFloat = 0
switch index {
case 0:
rote = -CGFloat.pi * 2 / 3
case 1:
rote = CGFloat.pi * 2 / 3
case 2:
rote = 0
default:
return nil
}
return SubAngle.init(start: start, end: end, rotate: rote)
} else if count == 4 {
var rote: CGFloat = 0
switch index {
case 0:
rote = -CGFloat.pi / 2
case 1:
rote = CGFloat.pi
case 2:
rote = CGFloat.pi / 2
case 3:
rote = 0
default:
return nil
}
return SubAngle.init(start: start, end: end, rotate: rote)
}
return nil
}
private func subImageAngleClockwise(at index: Int, direction: Direction) -> SubAngle? {
if direction == .clockwise {
return subImageAngleClockwise(at: index)
}
// 暂时只支持顺时针
return nil
}
fileprivate func subImage(at index: Int) -> UIImage? {
guard let rect = subImageRect(at: index) else {
return nil
}
guard let angle = subImageAngleClockwise(at: index, direction: direction) else {
return nil
}
let size = rect.height
let radius = size / 2
UIGraphicsBeginImageContextWithOptions(rect.size, false, 1)
let context = UIGraphicsGetCurrentContext()
context?.saveGState()
context?.translateBy(x: radius, y: radius)
context?.rotate(by: -angle.rotate)
context?.addArc(center: CGPoint.init(x: 0, y: 0), radius: radius, startAngle: angle.start, endAngle: angle.end, clockwise: true)
if angle.start != 0 {
context?.addArc(center: CGPoint.init(x: size * 2 - crossWidth - radius * 2, y: 0), radius: radius, startAngle: angle.start + CGFloat.pi, endAngle: angle.end + CGFloat.pi, clockwise: false)
}
context?.clip()
context?.rotate(by: angle.rotate)
let image = images![index]
image.draw(in: CGRect.init(origin: CGPoint.init(x: -radius, y: -radius), size: rect.size))
context?.rotate(by: -angle.rotate)
context?.addArc(center: CGPoint.init(x: 0, y: 0), radius: radius - boardWidth / 2, startAngle: angle.start, endAngle: angle.end, clockwise: true)
if angle.start != 0 {
context?.addArc(center: CGPoint.init(x: size * 2 - crossWidth - radius * 2, y: 0), radius: radius - boardWidth / 2, startAngle: angle.start + CGFloat.pi, endAngle: angle.end + CGFloat.pi, clockwise: false)
}
context?.setStrokeColor(boardColor.cgColor)
context?.setLineWidth(boardWidth)
context?.strokePath()
context?.restoreGState()
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resultImage
}
var circleImage: UIImage?{
guard let images = images, !images.isEmpty else {
return nil
}
guard let size = size, size != 0 else {
return nil
}
UIGraphicsBeginImageContextWithOptions(CGSize.init(width: size, height: size), false, 1)
for index in 0...min(maxCount, images.count) {
let image = subImage(at: index)
image?.draw(in: subImageRect(at: index)!)
}
let resultImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resultImage
}
}
}
extension YQ.CircleImage {
enum Direction {
case clockwise // 顺时针
case anticlockwise // 逆时针
}
private struct SubAngle {
var start: CGFloat = 0
var end: CGFloat = 0
var rotate: CGFloat = 0
}
}
使用:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let image = #imageLiteral(resourceName: "img.jpg")
let size: CGFloat = 120
let screenW = UIScreen.main.bounds.width
let space = (screenW - (2 * size)) / 3
var images = [image]
var cirImage = YQ.CircleImage.init(images: images, aSize: size * UIScreen.main.scale)
var imageView = UIImageView.init(frame: CGRect.init(x:space , y: 50, width: size, height: size))
imageView.image = cirImage.circleImage
view.addSubview(imageView)
images.append(image)
cirImage = YQ.CircleImage.init(images: images, aSize: size * UIScreen.main.scale)
imageView = UIImageView.init(frame: CGRect.init(x:space * 2 + size , y: 50, width: size, height: size))
imageView.image = cirImage.circleImage
view.addSubview(imageView)
images.append(image)
cirImage = YQ.CircleImage.init(images: images, aSize: size * UIScreen.main.scale)
imageView = UIImageView.init(frame: CGRect.init(x:space , y: 100 + size, width: size, height: size))
imageView.image = cirImage.circleImage
view.addSubview(imageView)
images.append(image)
cirImage = YQ.CircleImage.init(images: images, aSize: size * UIScreen.main.scale)
imageView = UIImageView.init(frame: CGRect.init(x:space * 2 + size , y: 100 + size, width: size, height: size))
imageView.image = cirImage.circleImage
view.addSubview(imageView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
如果你不想了解如何实现的, 拷贝代码即可使用啦
暂时只支持4个头像, 要支持更多的图像, 按相同的思路扩展即可!