Skip to content

Instantly share code, notes, and snippets.

@algal
Last active November 26, 2015 14:39
Show Gist options
  • Save algal/406ce37c775cfd552ba2 to your computer and use it in GitHub Desktop.
Save algal/406ce37c775cfd552ba2 to your computer and use it in GitHub Desktop.
CenteredTagCloudView playground code
//
// MultilineLabelCloudView.swift
//
//
import UIKit
/**
Presents a wrapping grid of tokens presented as pills.
Implements sizeThatFits, so it can communicate its
required height to auto layout or other consumers.
*/
class MultilineLabelCloudView: UIView, UICollectionViewDataSource
{
var pillBackgroundColor:UIColor = UIColor.lightGrayColor()
var pillTextColor:UIColor = UIColor.darkGrayColor()
var pillTextFontSize:CGFloat = CGFloat(9)
var horizontalSpacing:CGFloat = CGFloat(8)
var tokens:[String] = [] { didSet {
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
self.setNeedsLayout()
self.layoutIfNeeded()
} }
private weak var collectionView:UICollectionView!
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
private func setup() {
self.backgroundColor = UIColor.clearColor()
let layout = CenteredFlowLayout()
layout.minimumInteritemSpacing = self.horizontalSpacing
let cv = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
cv.registerClass(LabelViewCell.self, forCellWithReuseIdentifier: "LabelViewCellIdentifier")
cv.dataSource = self
layout.estimatedItemSize = CGSize(width: 60, height: 60)
cv.backgroundColor = self.backgroundColor
self.addSubview(cv)
fillSuperview(cv)
self.collectionView = cv
}
/// Returns the size that would present all its content
override func sizeThatFits(size: CGSize) -> CGSize {
return self.collectionView.contentSize
}
// MARK: UICollectionViewDataSource
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tokens.count
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("LabelViewCellIdentifier", forIndexPath: indexPath) as! LabelViewCell
cell.labelView.label.text = self.tokens[indexPath.row]
cell.labelView.labelBackgroundColor = pillBackgroundColor
cell.labelView.label.font = UIFont.systemFontOfSize(self.pillTextFontSize)
cell.labelView.label.textColor = self.pillTextColor
return cell
}
}
class CenteredFlowLayout : UICollectionViewFlowLayout
{
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let modifiedLayoutAttributes = self.layoutAttributesForElementsInRect(CGRectInfinite)
// TODO: re-implement for better perf
if let desiredLayAttr = modifiedLayoutAttributes?.filter({ indexPath.isEqual($0.indexPath) }).first as? UICollectionViewLayoutAttributes {
return desiredLayAttr
}
else
{
NSLog("error")
return super.layoutAttributesForItemAtIndexPath(indexPath)
}
}
func modifiedLayoutAttribuets(attributes:[UICollectionViewLayoutAttributes]) -> [UICollectionViewLayoutAttributes]
{
return attributes
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]?
{
typealias Row = [UICollectionViewLayoutAttributes]
let superAttributes = super.layoutAttributesForElementsInRect(rect) as! [UICollectionViewLayoutAttributes]
// partition the objs into rows
func inRow(lay1:UICollectionViewLayoutAttributes, row:Row) -> Bool {
let sameRowEpsilon:CGFloat = 1
if let lay2 = row.first {
let absDistance = fabs(CGRectGetMidY(lay1.frame) - CGRectGetMidY(lay2.frame))
return (absDistance < sameRowEpsilon)
}
return false
}
func consumeItem(var result:[Row], item:UICollectionViewLayoutAttributes) -> [Row]
{
for (index,row) in enumerate(result) {
if inRow(item,row) {
result[index].append(item)
return result
}
}
// assert: did not match any existing rows
result.append([item])
return result
}
let rowCollections = reduce(superAttributes, [], consumeItem)
// update the layout one row at a time
let collectionViewWidth = CGRectGetWidth(self.collectionView!.bounds)
for items in rowCollections {
let itemsInRow = items.count
let aggregateInterItemSpacing = self.minimumInteritemSpacing * CGFloat(itemsInRow - 1)
let aggregateItemWidth = items.map({CGRectGetWidth($0.frame)}).reduce(0, combine: +)
let alignmentWidth = aggregateItemWidth + aggregateInterItemSpacing
let alignmentXOffset = (collectionViewWidth - alignmentWidth) / 2.0
var previousFrame = CGRectZero
for item in items {
var itemFrame = item.frame
if CGRectEqualToRect(previousFrame, CGRectZero) {
itemFrame.origin.x = alignmentXOffset
} else {
itemFrame.origin.x = CGRectGetMaxX(previousFrame) + self.minimumInteritemSpacing
}
item.frame = itemFrame
previousFrame = itemFrame
}
}
return superAttributes
}
}
class LabelViewCell : UICollectionViewCell
{
private weak var labelView:LabelView!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
self.contentView.backgroundColor = UIColor.clearColor()
let label = LabelView(frame: self.bounds)
self.contentView.addSubview(label)
fillSuperview(label)
self.labelView = label
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
assertionFailure("unimplemented NSCoding initializer")
}
}
/** Draws a light gray pill-shaped view with an inset label showing dark gray text
Defines its own preferred size via auto layout.
*/
class LabelView: UIView
{
var labelBackgroundColor:UIColor? {
get { return self.backgroundColor }
set {
self.backgroundColor = newValue
self.label.backgroundColor = newValue
}
}
let label = UILabel(frame: CGRectZero)
let insetting = UIEdgeInsetsMake(5, 15, 5, 15)
override convenience init(frame:CGRect) {
// defaults
let kpillBackgroundColor:UIColor = UIColor.greenColor()
let kpillTextColor:UIColor = UIColor.whiteColor()
let kpillTextFontSize:CGFloat = CGFloat(9)
let kpillTextFont:UIFont = UIFont.systemFontOfSize(kpillTextFontSize)
self.init(frame:frame, pillBackgroundColor:kpillBackgroundColor,pillTextColor:kpillTextColor,pillTextFont:kpillTextFont)
}
init(frame:CGRect, pillBackgroundColor:UIColor,pillTextColor:UIColor,pillTextFont:UIFont) {
super.init(frame:frame)
self.setTranslatesAutoresizingMaskIntoConstraints(false)
self.autoresizingMask = UIViewAutoresizing.None
self.backgroundColor = pillBackgroundColor
self.clipsToBounds = true
label.backgroundColor = pillBackgroundColor
label.textColor = pillTextColor
label.font = pillTextFont
label.frame = UIEdgeInsetsInsetRect(self.bounds, self.insetting)
self.addSubview(label)
let views = ["label":label]
self.label.setTranslatesAutoresizingMaskIntoConstraints(false)
self.layoutMargins = insetting
["V:|-[label]-|","H:|-[label]-|"].map( { (vfl:String) -> String in
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(vfl, options: .allZeros, metrics: nil, views: views))
return vfl
})
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.label.frame = UIEdgeInsetsInsetRect(self.bounds,self.insetting)
self.layer.cornerRadius = self.layer.bounds.size.height / 2.0
}
}
func fillSuperview(view:UIView)
{
if let superview = view.superview {
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.frame = superview.bounds
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[v]|", options: NSLayoutFormatOptions.allZeros, metrics: nil, views: ["v":view]))
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v]|", options: NSLayoutFormatOptions.allZeros, metrics: nil, views: ["v":view]))
}
}
let cloudView = MultilineLabelCloudView(frame: CGRect(x: 0, y: 0, width: 310, height: 50))
cloudView.tokens = ["phonenumber","ssn","card","mytoken","word","another word"]
cloudView.backgroundColor = UIColor.yellowColor()
cloudView
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment