Skip to content
This repository was archived by the owner on Feb 17, 2021. It is now read-only.
This repository was archived by the owner on Feb 17, 2021. It is now read-only.

Problem with ViewReuseId  #164

@spekke

Description

@spekke

Hi,
As I understand it, you should be able to use the same viewReuseId for different layouts as long as they create the same UIView class and configure the same view properties. When I'm doing this and using my layouts with ReloadableViewLayoutAdapter on a UICollectionView, it's not cleaning up views correctly on a re-used UICollectionViewCell.

Here's a Playground example that reproduces the problem:

import Foundation
import UIKit
import LayoutKit
import PlaygroundSupport

struct FeedItem {
    let name: String
    let text: String
}

struct BannerItem {
    let headline: String
    let subtitle: String?
}

class MyViewController : UIViewController {

    let items: [Any] = [
        FeedItem(name: "User X", text: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo"),
        BannerItem(headline: "Ad 1", subtitle: "Best market ever!"),
        FeedItem(name: "User Y", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. An nisi populari fama? Scaevolam M. At enim sequor utilitatem. Duo Reges: constructio interrete. Bonum valitudo: miser morbus."),
        BannerItem(headline: "Ad 2", subtitle: nil),
        FeedItem(name: "User Z", text: "Rationis enim perfectio est virtus; Certe non potest. Ille incendat? Ut pulsi recurrant?"),
        BannerItem(headline: "Ad 3", subtitle: "Best lib ever!")
    ]

    let collectionViewLayout: UICollectionViewFlowLayout = {
        let layout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
        return layout
    }()

    lazy var layoutAdapterCollectionView: LayoutAdapterCollectionView = {
        let collectionView = LayoutAdapterCollectionView(frame: .zero, collectionViewLayout: self.collectionViewLayout)
        collectionView.backgroundColor = .lightGray
        collectionView.alwaysBounceVertical = true
        return collectionView
    }()

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        self.layoutAdapterCollectionView.frame = self.view.bounds
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        self.view.addSubview(self.layoutAdapterCollectionView)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let sections: [[Any]] = [
            self.items,
            self.items,
            self.items
        ]

        self.layoutAdapterCollectionView.layoutAdapter.reload(
            width: self.layoutAdapterCollectionView.bounds.width,
            synchronous: true,
            layoutProvider: { (Void) -> [Section<[Layout]>] in

                let sections: [Section<[Layout]>] = sections.enumerated().flatMap({ (index, section) in
                    let layouts: [Layout] = section.flatMap { item in
                        if let feedItem = item as? FeedItem {
                            return self.feedItemLayout(for: feedItem)
                        }
                        if let bannerItem = item as? BannerItem {
                            return self.bannerLayout(for: bannerItem)
                        }
                        return nil
                    }
                    let sectionLayout = self.sectionLayout(index: index)
                    return Section<[Layout]>(header: sectionLayout, items: layouts)
                })

                return sections
            },
            completion: nil
        )
    }

    private func sectionLayout(index: Int) -> Layout {
        let labelLayout = LabelLayout(
            text: "Section \(index)",
            font: UIFont.boldSystemFont(ofSize: 20),
            config: { label in
                label.textColor = .white
            }
        )
        return InsetLayout(
            insets: UIEdgeInsets(top: 20, left: 10, bottom: 0, right: 10),
            sublayout: labelLayout,
            config: { view in
                view.backgroundColor = .black
            }
        )
    }

    private func bannerLayout(for bannerItem: BannerItem) -> Layout {

        let headlineLayout = LabelLayout(
            text: bannerItem.headline,
            font: UIFont.systemFont(ofSize: 18),
            numberOfLines: 0,
            viewReuseId: "labelWithTextColorAndBackgroundColor",
            config: { label in
                label.textColor = .white
                label.backgroundColor = .black
            }
        )

        var subtitleLayout: Layout?
        if let subtitle = bannerItem.subtitle {
            subtitleLayout = LabelLayout(
                text: subtitle,
                font: UIFont.systemFont(ofSize: 18),
                numberOfLines: 0,
                viewReuseId: "labelWithTextColorAndBackgroundColor",
                config: { label in
                    label.textColor = .green
                    label.backgroundColor = .red
                }
            )
        }

        return StackLayout(
            axis: .vertical,
            viewReuseId: "viewWithBackgroundColor",
            sublayouts: [
                headlineLayout,
                subtitleLayout
            ].flatMap({ $0 }),
            config: { view in
                view.backgroundColor = .white
            }
        )
    }

    private func feedItemLayout(for feedItem: FeedItem) -> Layout {

        let imagePlaceholderLayout = SizeLayout(
            width: 80,
            height: 80,
            viewReuseId: "viewWithBackgroundColor",
            config: { view in
                view.backgroundColor = .gray
            }
        )

        let nameLayout = LabelLayout(
            text: feedItem.name,
            font: UIFont.systemFont(ofSize: 14),
            numberOfLines: 0,
            viewReuseId: "labelWithTextColorAndBackgroundColor",
            config: { label in
                label.textColor = .black
                label.backgroundColor = .clear
            }
        )

        let textLayout = LabelLayout(
            text: feedItem.text,
            font: UIFont.systemFont(ofSize: 12),
            numberOfLines: 0,
            viewReuseId: "labelWithTextColorAndBackgroundColor",
            config: { label in
                label.textColor = .black
                label.backgroundColor = UIColor.groupTableViewBackground
            }
        )

        let verticalStackLayout = StackLayout(
            axis: .vertical,
            sublayouts: [
                nameLayout,
                textLayout
            ]
        )

        return StackLayout(
            axis: .horizontal,
            viewReuseId: "viewWithBackgroundColor",
            sublayouts: [
                imagePlaceholderLayout,
                verticalStackLayout
            ],
            config: { view in
                view.backgroundColor = .yellow
            }
        )
    }
}

PlaygroundPage.current.liveView = MyViewController()
PlaygroundPage.current.needsIndefiniteExecution = true

It initially builds a list that looks like this:
screen shot 2017-09-23 at 13 45 14

But after scrolling down and then back up it looks like this:
screen shot 2017-09-23 at 13 45 44

Ad 2 and the item below now has a yellow view that is not removed after a re-use.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions