代码之家  ›  专栏  ›  技术社区  ›  Abraham Duran

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

  •  3
  • Abraham Duran  · 技术社区  · 7 年前

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

    1 回复  |  直到 7 年前
        1
  •  3
  •   Timur Bernikovich    7 年前

    我为你写了开始布局。你应该验证我发布的所有代码。使用如下自定义布局:

    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