Создание круглого лицевого файла из изображений профиля в Swift: как поместить последнюю фотографию под первую?

#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) , и это показалось мне прекрасным (хотя и очень маленьким и странно выглядящим из-за очень небольшой области перекрытия).