ios - 如何在不裁剪的情况下创建具有动态大小的图像网格/砂浆,同时保持图像纵横比?
问题描述
我需要在保持原始纵横比的同时将图像动态放置在网格/砂浆视图中。基本上,我正在尝试实现类似于 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]
}
}
推荐阅读
- regex - 如何在正则表达式的替换中评估表达式
- c# - 如何进行灵活排序
- regex - 如果字符串部分为 31 个字符或更长,则在字符串内匹配
- javascript - 尝试向单个元素添加单击事件,但希望它们一次执行一个
- javascript - 如何将 DIV 定位在具有动态大小的固定 DIV 下方
- python - 在 Python 中减少递归深度的方法
- python - SystemCheckError(django 中的 slug)
- php - 如何以低成本在 AWS 中运行 laravel php 应用程序?
- excel - 工作簿自动打开 + MsgBox 作为用户响应的运行时错误
- google-api - 即使活动当前正在直播,YouTube API 也会为直播活动返回 0