ios - 如何在保持图像纵横比的同时创建具有动态大小的图像网格/砂浆而不裁剪?

标签 ios swift uicollectionview

我需要在网格/砂浆 View 中动态放置图像,同时保持其原始纵横比。基本上,我正在尝试实现类似于 Adob​​e Lightroom 的解决方案。

adobe lightroom cc

我最初尝试通过固定高度、根据剩余行空间和图像比例动态更改单元格宽度来实现此目的。但是,因为我使用的是 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]
    }

}

Sample

关于ios - 如何在保持图像纵横比的同时创建具有动态大小的图像网格/砂浆而不裁剪?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54497328/

相关文章:

ios - 在 Swift 中设置一个 UICollectionView 删除按钮

ios - Eureka 通过标签获取 SelectableSection

ios - 从抽屉 Controller 退出左 View Controller

ios - 如果没有初始化程序,回调在类中无法正常工作?

arrays - 从 Rest WebService 获取 PickerData 数据

ios - 在容器 View 中嵌入 Collection View - 顶部的额外空白

ios - iOS 会屏蔽所有应用程序图标还是仅屏蔽主屏幕上的应用程序图标?

ios - 在AlertView中选择“取消”按钮时,如何从阵列中消除所有AlertView?

ios - 不能只用名称填充 TableView

ios - 删除尺寸类的 UICollectionView