我需要在网格/砂浆 View 中动态放置图像,同时保持其原始纵横比。基本上,我正在尝试实现类似于 Adobe Lightroom 的解决方案。
我最初尝试通过固定高度、根据剩余行空间和图像比例动态更改单元格宽度来实现此目的。但是,因为我使用的是 scaleAspectFit
,图像会缩放,这意味着有时会裁剪一些图像。
我的猜测是我也必须动态地调整高度,但我不知道该怎么做。
我用来执行规范化过程的代码是:
var i = 0
while i < sizes.count {
var maxWidth = collectionViewWidth // the width of the UICollectionView
var rowWidth: CGFloat = 0.0
var j = i
while rowWidth < maxWidth, j < sizes.count {
let belowThreshold = sizes[j].width < (maxWidth - rowWidth) * 1.30
let remainsEnough = (maxWidth - rowWidth) / maxWidth > 0.3 && belowThreshold
if belowThreshold || remainsEnough {
rowWidth += sizes[j].width
j += 1
} else { break }
}
let spacing = CGFloat((j - i - 1) * 2)
maxWidth -= spacing
var newRowWidth: CGFloat = 0
for l in i..<j {
let x = (sizes[l].width * maxWidth) / rowWidth
sizes[l].width = x.rounded(to: 3)
newRowWidth += sizes[l].width
}
if newRowWidth >= maxWidth {
let width = sizes[j-1].width - (newRowWidth - maxWidth).rounded(to: 3)
sizes[j-1].width = width.rounded(to: 3)
}
i = j
}
更新 1
这是我目前拥有的示例项目的 GitHub URL:https://github.com/abrahamduran/ios-mortar-view
最佳答案
我为您编写了起始布局。您应该验证我发布的所有代码。使用这样的自定义布局:
let layout = CustomLayout()
layout.minimumLineSpacing = 4
layout.minimumInteritemSpacing = 4
collectionView.collectionViewLayout = layout
collectionView.backgroundColor = .lightGray
...
// Delegate
func collectionView(_ collectionView: UICollectionView, sizeForPhotoAtIndexPath indexPath: IndexPath) -> CGSize {
return images[indexPath.row].size
}
自定义布局的代码。
import UIKit
protocol CustomLayoutDelegate: class {
func collectionView(_ collectionView: UICollectionView, sizeForPhotoAtIndexPath indexPath: IndexPath) -> CGSize
}
class CustomLayout: UICollectionViewFlowLayout {
var preferredHeight: CGFloat = 100 {
didSet {
invalidateLayout()
}
}
fileprivate var cache = [UICollectionViewLayoutAttributes]()
fileprivate var contentSize: CGSize = .zero
override func prepare() {
super.prepare()
cache.removeAll()
guard let collectionView = collectionView,
let delegate = collectionView.delegate as? CustomLayoutDelegate else {
return
}
var sizes: [IndexPath: CGSize] = [:]
let maxRowWidth = collectionView.frame.width - (collectionView.contentInset.left + collectionView.contentInset.right)
var offsetY: CGFloat = 0
var rowIndexes: [IndexPath] = []
var rowWidth: CGFloat = 0
let spacing = minimumInteritemSpacing
let numberOfItems = collectionView.numberOfItems(inSection: 0)
for item in 0..<numberOfItems {
let indexPath = IndexPath(item: item, section: 0)
let size = delegate.collectionView(collectionView, sizeForPhotoAtIndexPath: indexPath)
sizes[indexPath] = size
let aspectRatio = size.width / size.height
let preferredWidth = preferredHeight * aspectRatio
rowWidth += preferredWidth
rowIndexes.append(indexPath)
if rowIndexes.count > 1 {
// Check if we fit row width.
let rowWidthWithSpacing = rowWidth + CGFloat(rowIndexes.count - 1) * spacing
if rowWidthWithSpacing > maxRowWidth {
let previousRowWidthWithSpacing = rowWidthWithSpacing - spacing - preferredWidth
let diff = abs(maxRowWidth - rowWidthWithSpacing)
let previousDiff = abs(maxRowWidth - previousRowWidthWithSpacing)
let scale: CGFloat
let finalRowIndexPaths: [IndexPath]
if previousDiff < diff {
rowWidth -= preferredWidth
rowIndexes.removeLast()
finalRowIndexPaths = rowIndexes
scale = maxRowWidth / rowWidth
rowWidth = preferredWidth
rowIndexes = [indexPath]
} else {
finalRowIndexPaths = rowIndexes
scale = maxRowWidth / rowWidth
rowWidth = 0
rowIndexes = []
}
let finalHeight = preferredHeight * scale
var offsetX: CGFloat = 0
finalRowIndexPaths.forEach {
let size = sizes[$0]!
let scale = finalHeight / size.height
let attributes = UICollectionViewLayoutAttributes(forCellWith: $0)
attributes.frame = CGRect(x: offsetX, y: offsetY, width: size.width * scale, height: size.height * scale).integral
offsetX = attributes.frame.maxX + spacing
cache.append(attributes)
}
offsetY = (cache.last?.frame.maxY ?? 0) + minimumLineSpacing
}
}
if numberOfItems == item + 1 && !rowIndexes.isEmpty {
let finalHeight = preferredHeight
var offsetX: CGFloat = 0
rowIndexes.forEach {
let size = sizes[$0]!
let scale = finalHeight / size.height
let attributes = UICollectionViewLayoutAttributes(forCellWith: $0)
attributes.frame = CGRect(x: offsetX, y: offsetY, width: size.width * scale, height: size.height * scale).integral
offsetX = attributes.frame.maxX + spacing
cache.append(attributes)
}
offsetY = (cache.last?.frame.maxY ?? 0) + minimumLineSpacing
}
contentSize = CGSize(width: collectionView.frame.width, height: cache.last?.frame.maxY ?? 0)
}
}
override var collectionViewContentSize: CGSize {
return contentSize
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
}
关于ios - 如何在保持图像纵横比的同时创建具有动态大小的图像网格/砂浆而不裁剪?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54497328/