Skip to content

Instantly share code, notes, and snippets.

@chanonly123
Last active January 8, 2021 11:44
Show Gist options
  • Save chanonly123/9789503cf9d874e9b98805c333427d16 to your computer and use it in GitHub Desktop.
Save chanonly123/9789503cf9d874e9b98805c333427d16 to your computer and use it in GitHub Desktop.

Automatic Profile cluster view

Usage

From Interface Builder

• Drag ProfileClusterView.swift file into your project
• Add a UIView into interface builder
• Set it's class to ProfileClusterView

From code

let profileCluster = ProfileCluster()

  

Options

spacing - spacing between profile views.

alignment - profile views alignment. (left/right/center)

startFrom - starting position profile views. (left/right)

  

Closures

configureCount - (required) return number of profile views

viewProfiles.configureCount = {
    return 10
}

configureImageView - to configure image views

viewProfiles.configureImageView = { cluster in
    cluster.imageView.image = something
}

configureMoreView - to configure more label

viewProfiles.configureMoreView = { cluster in
    cluster.label.backgroundColor = .blue
}
// Created by Chandan Karmakar on 07/04/20.
import UIKit
@IBDesignable
class ProfileClusterView: UIView {
let stackView = UIStackView()
var leadingConst: NSLayoutConstraint!
var trailingConst: NSLayoutConstraint!
var centerConst: NSLayoutConstraint!
var configureMoreView: (((label: UILabel, more: Int))->Void)?
var configureImageView: (((imageView: UIImageView, index: Int))->Void)?
var configureCount: (()->Int)?
@IBInspectable
var spacing: CGFloat = -8 {
didSet { updateStackViewSize() }
}
/// "left", "right", "center"
@IBInspectable
var alignment: String = "left" {
didSet { updateStackViewSize() }
}
/// "left", "right"
@IBInspectable
var startFrom: String = "left" {
didSet { updateStackViewSize() }
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
configureCount = { return 8 }
reloadData()
}
func commonInit() {
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(stackView)
leadingConst = stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0)
trailingConst = trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 0)
centerConst = centerXAnchor.constraint(equalTo: stackView.centerXAnchor, constant: 0)
topAnchor.constraint(equalTo: stackView.topAnchor, constant: 0).isActive = true
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
leadingConst.isActive = true
trailingConst.isActive = true
centerConst.isActive = true
updateStackViewSize()
}
override func layoutSubviews() {
super.layoutSubviews()
reloadDataInternal()
}
func updateStackViewSize() {
stackView.spacing = spacing
centerConst?.isActive = false
leadingConst?.isActive = false
trailingConst?.isActive = false
if alignment == "left" {
leadingConst?.isActive = true
trailingConst?.isActive = true
leadingConst?.priority = .defaultHigh
trailingConst?.priority = .defaultLow
} else if alignment == "right" {
leadingConst?.isActive = true
trailingConst?.isActive = true
leadingConst?.priority = .defaultLow
trailingConst?.priority = .defaultHigh
} else {
centerConst?.isActive = true
}
let scaleX: CGFloat = startFrom == "left" ? 1 : -1
stackView.transform = .init(scaleX: scaleX, y: 1)
stackView.arrangedSubviews.forEach({ $0.transform = .init(scaleX: scaleX, y: 1) })
}
func reloadData() {
prevBounds = CGRect.zero
reloadDataInternal()
}
var prevBounds = CGRect.zero
private func reloadDataInternal() {
if bounds == prevBounds { return }
prevBounds = bounds
let count = configureCount?() ?? 0
let singleWidth = bounds.height + spacing
var visibleCount = 0
if singleWidth > 0 {
visibleCount = Int((bounds.width-bounds.height) / singleWidth)
}
if bounds.width > bounds.height {
visibleCount += 1
} else {
visibleCount = 1
}
stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
if count == 0 { return }
for i in 1...visibleCount {
if i > count { break }
if i == visibleCount { // last item
let more = count - i + 1
if more > 1 {
addLabel(more: more, index: i - 1)
} else {
addImageView(index: i - 1)
}
} else {
addImageView(index: i - 1)
}
}
updateStackViewSize()
}
func addImageView(index: Int) {
let sub = UIView()
sub.translatesAutoresizingMaskIntoConstraints = false
sub.heightAnchor.constraint(equalTo: sub.widthAnchor, multiplier: 1, constant: 0).isActive = true
stackView.addArrangedSubview(sub)
let iv = UIImageView()
iv.layer.cornerRadius = bounds.height / 2
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
iv.backgroundColor = getRandomColor(index: index)
iv.contentMode = .scaleAspectFill
sub.addSubview(iv)
iv.addPinConstraints()
configureImageView?((iv, index))
}
func addLabel(more: Int, index: Int) {
let sub = UIView()
sub.translatesAutoresizingMaskIntoConstraints = false
sub.heightAnchor.constraint(equalTo: sub.widthAnchor, multiplier: 1, constant: 0).isActive = true
stackView.addArrangedSubview(sub)
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.layer.cornerRadius = bounds.height / 2
lbl.font = UIFont.boldSystemFont(ofSize: bounds.height / 2.5)
lbl.numberOfLines = 1
lbl.textAlignment = .center
lbl.clipsToBounds = true
lbl.textColor = .white
lbl.backgroundColor = getLabelColor()
lbl.text = "+\(more)"
sub.addSubview(lbl)
lbl.addPinConstraints()
configureMoreView?((lbl, more))
}
func getRandomColor(index: Int) -> UIColor {
let color1 = UIColor(red: 179/255, green: 235/255, blue: 242/255, alpha: 1)
let color2 = UIColor(red: 239/255, green: 154/255, blue: 154/255, alpha: 1)
let color3 = UIColor(red: 165/255, green: 214/255, blue: 167/255, alpha: 1)
return index % 3 == 0 ? color1 : (index % 2 == 0 ? color2 : color3)
}
func getLabelColor() -> UIColor {
return UIColor(red: 126/255, green: 87/255, blue: 194/255, alpha: 1)
}
func copyClosuresFrom(_ viewProfiles: ProfileClusterView) {
configureCount = viewProfiles.configureCount
configureImageView = viewProfiles.configureImageView
configureMoreView = viewProfiles.configureMoreView
}
}
extension UIView {
func addPinConstraints(_ val: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
leadingAnchor.constraint(equalTo: superview!.leadingAnchor, constant: val).isActive = true
trailingAnchor.constraint(equalTo: superview!.trailingAnchor, constant: -val).isActive = true
topAnchor.constraint(equalTo: superview!.topAnchor, constant: val).isActive = true
bottomAnchor.constraint(equalTo: superview!.bottomAnchor, constant: -val).isActive = true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment