#ios #swift #uiimageview #uiimage
#iOS #swift #uiimageview #uiimage
Вопрос:
Я пытаюсь создать файл, UIView
в котором несколько UIImageView
файлов расположены круговым, перекрывающимся образом (см. Изображение ниже). Допустим, у нас есть N изображений. Нарисовать первые N — 1 легко, просто используйте функции sin / cos, чтобы расположить центры UIImageView
s по окружности. Проблема в последнем изображении, которое, по-видимому, имеет два значения z-индекса!Я знаю, что это возможно, поскольку в kik messenger есть похожие фотографии групповых профилей.
Лучшая идея, которая мне пришла на данный момент, — это взять последнее изображение, разделить его на что-то вроде «верхней половины» и «нижней половины» и присвоить каждому разные z-значения. Это кажется выполнимым, когда изображение самое левое, но что произойдет, если изображение будет самым верхним? В этом случае мне нужно было бы разделить левую и правую части вместо верхней и нижней.
Из-за этой проблемы это, вероятно, не сверху, слева или справа, а скорее похоже на разделение по некоторой воображаемой оси от центра общего лицевого файла через центр UIImageView
. Как бы я это сделал?!
Приведенный ниже код расположит UIImageView по кругу
Вам нужно будет импортировать SDWebImage
и предоставить некоторые URL-адреса изображений, чтобы запустить приведенный ниже код.
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)
}
}
Ответ №1:
Я не думаю, что вы добьетесь большого успеха, пытаясь разделить изображения, чтобы получить над / под z-индексами.
Один из подходов заключается в использовании масок, чтобы казалось, что виды изображений перекрываются.
Общая идея была бы:
- подкласс
UIImageView
- в layoutSubviews()
- примените cornerRadius к слою, чтобы сделать изображение круглым
- получить прямоугольную форму из «перекрывающегося вида»
- преобразуйте эту прямоугольную форму в локальные координаты
- расширьте этот прямоугольник на желаемую ширину «контура»
- получить овальный контур из этого прямоугольника
- объедините ее с путем из self
- примените ее в качестве слоя-маски
Вот пример….
Я не был полностью уверен, что делали ваши расчеты размеров… попытка использовать ваш 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
)
Комментарии:
1. Спасибо за ответ. В случае, если их меньше 3, кажется, что маски начинают сталкиваться с неопределенными результатами. Например, используйте 3 изображения и установите радиус равным 22 (с размером, установленным на 33). Есть какие-нибудь советы по исправлению этого?
2. Конечно, в крайних случаях, таких как только одно изображение, или только два изображения, или три изображения с необычным радиусом / размером / шириной контура / и т.д., я вижу потенциальные проблемы. Должно быть довольно просто для учета. Я попробовал ваше предложение о 3 изображениях и установил radius равным 22 (с размером, установленным на 33) , и это показалось мне прекрасным (хотя и очень маленьким и странно выглядящим из-за очень небольшой области перекрытия).