首页 > 解决方案 > 如何在不裁剪的情况下创建具有动态大小的图像网格/砂浆,同时保持图像纵横比?

问题描述

我需要在保持原始纵横比的同时将图像动态放置在网格/砂浆视图中。基本上,我正在尝试实现类似于 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

标签: iosswiftuicollectionview

解决方案


我为你写了起始布局。您应该验证我发布的所有代码。使用这样的自定义布局:

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]
    }

}

样本


推荐阅读