Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

노출된 View 사이즈 캐시 로직을 구현해요. #48

Merged
merged 2 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
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
}
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()
}
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)
}
}
}