Saturday, June 13, 2026
HomeiOS Developmentios - Hold similar spacing between objects in UICollectionViewFlowLayout with scaled objects

ios – Hold similar spacing between objects in UICollectionViewFlowLayout with scaled objects

[ad_1]

I’m implementing carousel with variable merchandise measurement / scale, which is determined by indexPath.
So I began with UICollectionViewFlowLayout subclass which implements required scaling logic, however I am unable to determine why spacing is appropriate solely between heart and subsequent to it objects (left and proper).
Usually I’ve 5 components, heart ingredient (0, 2) has scale issue 1, components (0, 1) and (0, 3) have scale issue 0.888, components (0, 0) and (0, 4) – 0.777.
Spacing between ingredient (0, 2) and components (0, 1) (0, 3) is appropriate and is the same as 10, however spacing between components (0, 0) (0, 1) and (0, 3) (0, 4) is the same as 20.

Right here is the code for UICollectionViewFlowLayout subclass:

open class CarouselEffectFlowLayout: UICollectionViewFlowLayout {

    public enum CarouselSpaceMode {
        case fastened(spacing: CGFloat)
    }

    public struct LayoutState {
        var measurement: CGSize
        var route: UICollectionView.ScrollDirection

        func isEqual(_ otherState: LayoutState) -> Bool {
            self.measurement.equalTo(otherState.measurement) && self.route == otherState.route
        }
    }

    @IBInspectable open var sideItemScale: CGFloat = 0.888
    @IBInspectable open var sideItemAlpha: CGFloat = 0.8

    open var spacingMode: CarouselSpaceMode = .fastened(spacing: 10)

    personal var state = LayoutState(measurement: CGSize.zero, route: .horizontal)

    override open func put together() {
        tremendous.put together()
        let currentState = LayoutState(measurement: self.collectionView!.bounds.measurement, route: self.scrollDirection)

        if !self.state.isEqual(currentState) {
            self.setupCollectionView()
            self.updateLayout()
            self.state = currentState
        }
    }

    personal func setupCollectionView() {
        guard let collectionView = self.collectionView else { return }

        if collectionView.decelerationRate != UIScrollView.DecelerationRate.quick {
            collectionView.decelerationRate = UIScrollView.DecelerationRate.quick
        }
    }

    personal func updateLayout() {
        guard let collectionView = self.collectionView else { return }

        let collectionSize = collectionView.bounds.measurement

        let yInset = (collectionSize.peak - self.itemSize.peak) / 2
        let xInset = (collectionSize.width - self.itemSize.width) / 2
        self.sectionInset = UIEdgeInsets.init(prime: yInset, left: xInset, backside: yInset, proper: xInset)

        let facet = self.itemSize.width
        let scaledItemOffset = (facet - facet * self.sideItemScale) / 2

        change self.spacingMode {
        case .fastened(let spacing):
            self.minimumLineSpacing = spacing - scaledItemOffset
        }
    }

    override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        true
    }

    override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard
            let superAttributes = tremendous.layoutAttributesForElements(in: rect),
            let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes]
        else { return nil }

        return attributes.map({ self.transformLayoutAttributes($0) })
    }

    personal func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        guard let collectionView = self.collectionView else { return attributes }

        let collectionCenter = collectionView.body.measurement.width / 2
        let offset = collectionView.contentOffset.x
        let normalizedCenter = attributes.heart.x - offset
        let maxDistance = self.itemSize.width + self.minimumLineSpacing
        let distance = abs(collectionCenter - normalizedCenter)
        let ratio = (maxDistance - distance) / maxDistance

        let alpha = ratio * (1 - self.sideItemAlpha) + self.sideItemAlpha
        let scale = ratio * (1 - self.sideItemScale) + self.sideItemScale

        attributes.alpha = alpha
        attributes.zIndex = Int(alpha * 10)
        attributes.remodel = .init(scaleX: scale, y: scale)

        return attributes
    }

    override open func targetContentOffset(
        forProposedContentOffset proposedContentOffset: CGPoint,
        withScrollingVelocity velocity: CGPoint
    ) -> CGPoint {
        guard
            let collectionView = collectionView,
            !collectionView.isPagingEnabled,
            let layoutAttributes = self.layoutAttributesForElements(in: collectionView.bounds)
        else {
            return tremendous.targetContentOffset(forProposedContentOffset: proposedContentOffset)
        }

        let midSide = collectionView.bounds.measurement.width / 2
        let proposedContentOffsetCenterOrigin = proposedContentOffset.x + midSide

        var targetContentOffset: CGPoint
        let closest = layoutAttributes.sorted {
            abs($0.heart.x - proposedContentOffsetCenterOrigin) < abs($1.heart.x - proposedContentOffsetCenterOrigin)
        }.first ?? UICollectionViewLayoutAttributes()

        targetContentOffset = CGPoint(
            x: flooring(closest.heart.x - midSide),
            y: proposedContentOffset.y
        )

        return targetContentOffset
    }
}

Picture with instance from app

[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments