Ответ 1
Здесь вы найдете решение, соответствующее вашим требованиям, а также IBDesignable, чтобы он просматривал live в интерфейсе Builder. Этот класс выложит серию квадратов (общее число равно значению IBInspectable count
). По умолчанию он просто выложит все в одну длинную линию. Но если вы установите для свойства wrap
IBInspectable значение On
, оно будет обертывать квадраты и увеличивать их высоту на основе его ограниченной ширины (например, UILabel с номером 0fLines == 0). В ячейке просмотра таблицы размеров для самостоятельной калибровки это приведет к выталкиванию сверху и снизу, чтобы обеспечить обернутый внутренний размер пользовательского представления.
Код:
import Foundation
import UIKit
@IBDesignable class WrappingView : UIView {
private class InnerWrappingView : UIView {
private var lastPoint:CGPoint = CGPointZero
private var wrap = false
private var count:Int = 100
private var size:Int = 8
private var spacing:Int = 3
private func calculatedSize() -> CGSize {
lastPoint = CGPoint(x:-(size + spacing), y: 0)
for _ in 0..<count {
var nextPoint:CGPoint!
if wrap {
nextPoint = lastPoint.x + CGFloat(size + spacing + size) <= bounds.width ? CGPoint(x: lastPoint.x + CGFloat(size + spacing), y: lastPoint.y) : CGPoint(x: 0, y: lastPoint.y + CGFloat(size + spacing))
} else {
nextPoint = CGPoint(x: lastPoint.x + CGFloat(size + spacing), y: lastPoint.y)
}
lastPoint = nextPoint
}
return CGSize(width: wrap ? bounds.width : lastPoint.x + CGFloat(size), height: lastPoint.y + CGFloat(size))
}
override func layoutSubviews() {
super.layoutSubviews()
guard bounds.size != calculatedSize() || subviews.count == 0 else {
return
}
for subview in subviews {
subview.removeFromSuperview()
}
lastPoint = CGPoint(x:-(size + spacing), y: 0)
for _ in 0..<count {
let square = createSquareView()
var nextPoint:CGPoint!
if wrap {
nextPoint = lastPoint.x + CGFloat(size + spacing + size) <= bounds.width ? CGPoint(x: lastPoint.x + CGFloat(size + spacing), y: lastPoint.y) : CGPoint(x: 0, y: lastPoint.y + CGFloat(size + spacing))
} else {
nextPoint = CGPoint(x: lastPoint.x + CGFloat(size + spacing), y: lastPoint.y)
}
square.frame = CGRect(origin: nextPoint, size: square.bounds.size)
addSubview(square)
lastPoint = nextPoint
}
let newframe = CGRect(origin: frame.origin, size: calculatedSize())
frame = newframe
invalidateIntrinsicContentSize()
setNeedsLayout()
}
private func createSquareView() -> UIView {
let square = UIView(frame: CGRect(x: 0, y: 0, width: size, height: size))
square.backgroundColor = UIColor.blueColor()
return square
}
override func intrinsicContentSize() -> CGSize {
return calculatedSize()
}
}
@IBInspectable var count:Int = 500 {
didSet {
innerView.count = count
layoutSubviews()
}
}
@IBInspectable var size:Int = 8 {
didSet {
innerView.size = size
layoutSubviews()
}
}
@IBInspectable var spacing:Int = 3 {
didSet {
innerView.spacing = spacing
layoutSubviews()
}
}
@IBInspectable var wrap:Bool = false {
didSet {
innerView.wrap = wrap
layoutSubviews()
}
}
private var _innerView:InnerWrappingView! {
didSet {
clipsToBounds = true
addSubview(_innerView)
_innerView.clipsToBounds = true
_innerView.frame = bounds
_innerView.wrap = wrap
_innerView.translatesAutoresizingMaskIntoConstraints = false
_innerView.leftAnchor.constraintEqualToAnchor(leftAnchor).active = true
_innerView.rightAnchor.constraintEqualToAnchor(rightAnchor).active = true
_innerView.topAnchor.constraintEqualToAnchor(topAnchor).active = true
_innerView.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
_innerView.setContentCompressionResistancePriority(750, forAxis: .Vertical)
_innerView.setContentHuggingPriority(251, forAxis: .Vertical)
}
}
private var innerView:InnerWrappingView! {
if _innerView == nil {
_innerView = InnerWrappingView()
}
return _innerView
}
override func layoutSubviews() {
super.layoutSubviews()
if innerView.bounds.width != bounds.width {
innerView.frame = CGRect(origin: CGPointZero, size: CGSize(width: bounds.width, height: 0))
}
innerView.layoutSubviews()
if innerView.bounds.height != bounds.height {
invalidateIntrinsicContentSize()
superview?.layoutIfNeeded()
}
}
override func intrinsicContentSize() -> CGSize {
return innerView.calculatedSize()
}
}
В моем примере приложения я установил представление таблицы для деактивации ячейки, содержащей это настраиваемое представление для каждой строки, и установил свойство count
для пользовательского представления в 20 * строку indexPath. Пользовательский вид ограничен 50% ширины ячейки, поэтому его ширина автоматически изменяется при перемещении между пейзажем и портретом. Поскольку каждая последующая ячейка таблицы обертывает более длинную и длинную строку квадратов, каждая ячейка автоматически имеет размер, который должен быть выше и выше.
При запуске он выглядит так (включает демонстрацию вращения):