如果您不习惯使用集合视图,这里有另一种方法。
考虑行和列。行数将是项目总数平方根的上限,列数将是项目总数除以行数的上限。
然后您可以轻松计算“平铺”尺寸。
示例代码:
class ArrangeViewController: UIViewController {
// Add a view button
let addButton: UIButton = {
let v = UIButton()
v.setTitle("Add", for: .normal)
return v
}()
// Remove a view button
let remButton: UIButton = {
let v = UIButton()
v.setTitle("Remove", for: [])
return v
}()
// horizontal stackview to hold the buttons
let btnsStack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fillEqually
v.spacing = 20
return v
}()
// view to hold the added views
let tilesContainerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .systemRed
v.clipsToBounds = true
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
// button properties
[addButton, remButton].forEach { b in
b.backgroundColor = .yellow
b.setTitleColor(.blue, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
}
// add the buttons to the stack view
btnsStack.addArrangedSubview(addButton)
btnsStack.addArrangedSubview(remButton)
// add buttons stack to the view
view.addSubview(btnsStack)
// add border container to the view
view.addSubview(tilesContainerView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain buttons stack Top / Leading / Trailing with a little "padding"
btnsStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 12.0),
btnsStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
btnsStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// buttons height to 40-pts (just for asthetics)
btnsStack.heightAnchor.constraint(equalToConstant: 40.0),
// constrain border container
// 20-pts below buttons
tilesContainerView.topAnchor.constraint(equalTo: btnsStack.bottomAnchor, constant: 20.0),
// 20-pts from view bottom
tilesContainerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// 20-pts Leading and Trailing
tilesContainerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
tilesContainerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
// add actions for the Add and Delete buttons
addButton.addTarget(self, action: #selector(addTapped(_:)), for: .touchUpInside)
remButton.addTarget(self, action: #selector(remTapped(_:)), for: .touchUpInside)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
}) { [unowned self] _ in
self.arrangeViews()
}
}
@objc func addTapped(_ sender: Any?) -> Void {
// instantiate a new custom view and add it to
// the inner container view
let v = MyArrangeView()
tilesContainerView.addSubview(v)
v.theLabel.text = "\(tilesContainerView.subviews.count)"
// update the arrangement
arrangeViews()
}
@objc func remTapped(_ sender: Any?) -> Void {
// if inner container has at least one custom view
if let v = tilesContainerView.subviews.last {
// remove it
v.removeFromSuperview()
// update the arrangement
arrangeViews()
}
}
func arrangeViews() -> Void {
// make sure there is at least 1 subview to arrange
guard tilesContainerView.subviews.count > 0 else { return }
// init local vars to use
// Note: making them all CGFLoats makes it easier to use in expressions - avoids a lot of casting CGFloat(var)
var numCols: CGFloat = 0
var numRows: CGFloat = 0
var w: CGFloat = 0
var h: CGFloat = 0
// this is the frame we need to fit inside
let containerWidth: CGFloat = tilesContainerView.frame.size.width
let containerHeight: CGFloat = tilesContainerView.frame.size.height
// number of views to arrange
let numTiles: CGFloat = CGFloat(tilesContainerView.subviews.count)
// get the ceil of the square root of number of tiles
// that's the number of rows
numRows = CGFloat(ceil(sqrt(numTiles)))
// get the ceil of the number of tiles divided by number of rows
// that's the number of columns
numCols = CGFloat(ceil(numTiles / numRows))
// size of each tile
w = containerWidth / numCols
h = containerHeight / numRows
var x: CGFloat = 0.0
var y: CGFloat = 0.0
UIView.animate(withDuration: 0.3, animations: {
// loop through, doing the actual layout (setting each item's frame)
self.tilesContainerView.subviews.forEach { v in
v.frame = CGRect(x: x, y: y, width: w, height: h)
x += w
if x + w > containerWidth + 1 {
x = 0.0
y += h
}
}
self.view.layoutIfNeeded()
})
}
}
// simple bordered custom view with a label in a "content container"
class MyArrangeView: UIView {
// this will hold the "content" of the custom view
// for this example, it just holds a label
let theContentView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .systemTeal
return v
}()
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .clear
v.textAlignment = .center
v.font = UIFont.systemFont(ofSize: 14.0)
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
self.backgroundColor = .systemYellow
// add the label to the content view
theContentView.addSubview(theLabel)
// add the content view to self
addSubview(theContentView)
NSLayoutConstraint.activate([
// constrain the label to all 4 sides of the content view
theLabel.topAnchor.constraint(equalTo: theContentView.topAnchor, constant: 0.0),
theLabel.bottomAnchor.constraint(equalTo: theContentView.bottomAnchor, constant: 0.0),
theLabel.leadingAnchor.constraint(equalTo: theContentView.leadingAnchor, constant: 0.0),
theLabel.trailingAnchor.constraint(equalTo: theContentView.trailingAnchor, constant: 0.0),
// constrain the content view to all 4 sides of self with 5-pts "padding"
theContentView.topAnchor.constraint(equalTo: topAnchor, constant: 5.0),
theContentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5.0),
theContentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5.0),
theContentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5.0),
])
layer.borderWidth = 1
layer.borderColor = UIColor.systemGray.cgColor
}
}
然后,旋转...