Skip to content

Commit

Permalink
Merge pull request #48 from daangn/using-cachedSize-for-performance
Browse files Browse the repository at this point in the history
노출된 View 사이즈 캐시 로직을 구현해요.
  • Loading branch information
jaxtynSong authored Jan 24, 2025
2 parents 2bdee75 + b597f56 commit 3ec6dab
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Copyright (c) 2025 Danggeun Market Inc.
//

import UIKit

extension UIView {

func shouldInvalidateContentSize(
previousTraitCollection: UITraitCollection?
) -> Bool {
if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory {
return true
}

if traitCollection.legibilityWeight != previousTraitCollection?.legibilityWeight {
return true
}

if traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass ||
traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass {
return true
}

return false
}
}
12 changes: 12 additions & 0 deletions Sources/KarrotListKit/FeatureFlag/DefaultFeatureFlagProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Copyright (c) 2025 Danggeun Market Inc.
//

import Foundation

final class DefaultFeatureFlagProvider: FeatureFlagProviding {

func featureFlags() -> [FeatureFlagItem] {
[]
}
}
28 changes: 28 additions & 0 deletions Sources/KarrotListKit/FeatureFlag/FeatureFlagItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Copyright (c) 2025 Danggeun Market Inc.
//

import Foundation

/// Representing a feature flag item.
public struct FeatureFlagItem {

/// The type of the feature flag.
public let type: FeatureFlagType

/// A Boolean value indicating whether the feature flag is enabled.
public let isEnabled: Bool

/// Initializes a new `FeatureFlagItem`.
///
/// - Parameters:
/// - type: The type of the feature flag.
/// - isEnabled: A Boolean value indicating whether the feature flag is enabled.
public init(
type: FeatureFlagType,
isEnabled: Bool
) {
self.type = type
self.isEnabled = isEnabled
}
}
23 changes: 23 additions & 0 deletions Sources/KarrotListKit/FeatureFlag/FeatureFlagProviding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Copyright (c) 2025 Danggeun Market Inc.
//

import Foundation

/// A protocol for providing feature flags.
public protocol FeatureFlagProviding {

/// Returns an array of feature flags.
///
/// - Returns: An array of `FeatureFlagItem`.
func featureFlags() -> [FeatureFlagItem]
}

extension FeatureFlagProviding {

func isEnabled(for type: FeatureFlagType) -> Bool {
featureFlags()
.first(where: { $0.type == type })?
.isEnabled ?? false
}
}
13 changes: 13 additions & 0 deletions Sources/KarrotListKit/FeatureFlag/FeatureFlagType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright (c) 2025 Danggeun Market Inc.
//

import Foundation

/// Define the feature flags
public enum FeatureFlagType: Equatable {

/// Improve scrolling performance using calculated view size.
/// You can find more information at https://developer.apple.com/documentation/uikit/building-high-performance-lists-and-collection-views
case usesCachedViewSize
}
15 changes: 15 additions & 0 deletions Sources/KarrotListKit/FeatureFlag/KarrotListKitFeatureFlag.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Copyright (c) 2025 Danggeun Market Inc.
//

import Foundation

/// An interface for injecting a feature flag provider.
public enum KarrotListKitFeatureFlag {

/// The feature flag provider used by `KarrotListKit`.
///
/// By default, this is set to `DefaultFeatureFlagProvider`.
/// You can replace it with a custom provider to change the feature flag behavior.
public static var provider: FeatureFlagProviding = DefaultFeatureFlagProvider()
}
31 changes: 31 additions & 0 deletions Sources/KarrotListKit/View/UICollectionComponentReusableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ final class UICollectionComponentReusableView: UICollectionReusableView, Compone

var onSizeChanged: ((CGSize) -> Void)?

private var previousBounds: CGSize = .zero

// MARK: - Initializing

@available(*, unavailable)
Expand All @@ -29,6 +31,30 @@ final class UICollectionComponentReusableView: UICollectionReusableView, Compone

// MARK: - Override Methods

public override func traitCollectionDidChange(
_ previousTraitCollection: UITraitCollection?
) {
super.traitCollectionDidChange(previousTraitCollection)

if shouldInvalidateContentSize(
previousTraitCollection: previousTraitCollection
) {
previousBounds = .zero
}
}

public override func prepareForReuse() {
super.prepareForReuse()

previousBounds = .zero
}

public override func layoutSubviews() {
super.layoutSubviews()

previousBounds = bounds.size
}

override func preferredLayoutAttributesFitting(
_ layoutAttributes: UICollectionViewLayoutAttributes
) -> UICollectionViewLayoutAttributes {
Expand All @@ -38,6 +64,11 @@ final class UICollectionComponentReusableView: UICollectionReusableView, Compone
return attributes
}

if KarrotListKitFeatureFlag.provider.isEnabled(for: .usesCachedViewSize),
previousBounds == attributes.size {
return attributes
}

let size = renderedContent.sizeThatFits(bounds.size)

if renderedComponent != nil {
Expand Down
26 changes: 26 additions & 0 deletions Sources/KarrotListKit/View/UICollectionViewComponentCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public final class UICollectionViewComponentCell: UICollectionViewCell, Componen

var onSizeChanged: ((CGSize) -> Void)?

private var previousBounds: CGSize = .zero

// MARK: - Initializing

@available(*, unavailable)
Expand All @@ -37,12 +39,31 @@ public final class UICollectionViewComponentCell: UICollectionViewCell, Componen

// MARK: - Override Methods

public override func traitCollectionDidChange(
_ previousTraitCollection: UITraitCollection?
) {
super.traitCollectionDidChange(previousTraitCollection)

if shouldInvalidateContentSize(
previousTraitCollection: previousTraitCollection
) {
previousBounds = .zero
}
}

public override func prepareForReuse() {
super.prepareForReuse()

previousBounds = .zero
cancellables?.forEach { $0.cancel() }
}

public override func layoutSubviews() {
super.layoutSubviews()

previousBounds = bounds.size
}

public override func preferredLayoutAttributesFitting(
_ layoutAttributes: UICollectionViewLayoutAttributes
) -> UICollectionViewLayoutAttributes {
Expand All @@ -52,6 +73,11 @@ public final class UICollectionViewComponentCell: UICollectionViewCell, Componen
return attributes
}

if KarrotListKitFeatureFlag.provider.isEnabled(for: .usesCachedViewSize),
previousBounds == attributes.size {
return attributes
}

let size = renderedContent.sizeThatFits(contentView.bounds.size)

if renderedComponent != nil {
Expand Down
46 changes: 46 additions & 0 deletions Tests/KarrotListKitTests/FeatureFlagProviderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright (c) 2025 Danggeun Market Inc.
//

import Foundation
import XCTest

@testable import KarrotListKit

final class FeatureFlagProviderTests: XCTestCase {

final class FeatureFlagProviderStub: FeatureFlagProviding {

var featureFlagsStub: [FeatureFlagItem] = []

func featureFlags() -> [FeatureFlagItem] {
featureFlagsStub
}
}

func test_default_featureFlags_is_empty() {
// given
let sut = KarrotListKitFeatureFlag.provider

// when
let featureFlags = sut.featureFlags()

// then
XCTAssertTrue(featureFlags.isEmpty)
}

func test_usesCachedViewSize_isEnabled() {
[true, false].forEach { flag in
// given
let provider = FeatureFlagProviderStub()
provider.featureFlagsStub = [.init(type: .usesCachedViewSize, isEnabled: flag)]
KarrotListKitFeatureFlag.provider = provider

// when
let isEnabled = KarrotListKitFeatureFlag.provider.isEnabled(for: .usesCachedViewSize)

// then
XCTAssertEqual(isEnabled, flag)
}
}
}

0 comments on commit 3ec6dab

Please sign in to comment.