From 697c6f22f7e2f6da69fd0ca5dca0d08c973ea3a8 Mon Sep 17 00:00:00 2001 From: Bryan Keller Date: Thu, 14 Nov 2024 11:31:23 -0800 Subject: [PATCH] Fix sizing month header on every frame and add more calendar model caching (#321) --- CHANGELOG.md | 2 + Sources/Internal/VisibleItemsProvider.swift | 81 ++++++++++++--------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f90d3ae..31da660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed an issue that could cause accessibility focus to shift unexpectedly - Fixed a screen-pixel alignment issue +- Fixed a performance issue caused by month headers recalculating their size too often +- Fixed a performance issue caused by the item models for day ranges and month backgrounds not being cached ### Changed - Rewrote accessibility code to avoid posting notifications, which causes poor Voice Over performance and odd focus bugs diff --git a/Sources/Internal/VisibleItemsProvider.swift b/Sources/Internal/VisibleItemsProvider.swift index d16fe92..2331654 100644 --- a/Sources/Internal/VisibleItemsProvider.swift +++ b/Sources/Internal/VisibleItemsProvider.swift @@ -277,6 +277,7 @@ final class VisibleItemsProvider { // Handle background items handleMonthBackgroundItemsIfNeeded(context: &context) + previousHeightsForVisibleMonthHeaders = context.heightsForVisibleMonthHeaders previousCalendarItemModelCache = context.calendarItemModelCache return VisibleItemsDetails( @@ -301,10 +302,8 @@ final class VisibleItemsProvider { private var sizingMonthHeaderViewsForViewDifferentiators = [ _CalendarItemViewDifferentiator: UIView ]() - - private var previousCalendarItemModelCache: [ - VisibleItem.ItemType: AnyCalendarItemModel - ]? + private var previousHeightsForVisibleMonthHeaders: [Month: CGFloat]? + private var previousCalendarItemModelCache: [VisibleItem.ItemType: AnyCalendarItemModel]? private var calendar: Calendar { content.calendar @@ -442,31 +441,35 @@ final class VisibleItemsProvider { context.heightsForVisibleMonthHeaders.value( for: month, missingValueProvider: { - let monthHeaderItemModel = content.monthHeaderItemProvider(month) - let monthHeaderView = sizingMonthHeaderViewsForViewDifferentiators.value( - for: monthHeaderItemModel._itemViewDifferentiator, - missingValueProvider: { - monthHeaderItemModel._makeView() - }) - monthHeaderItemModel._setContent(onViewOfSameType: monthHeaderView) - - let monthWidth: CGFloat - switch content.monthsLayout { - case .vertical: - monthWidth = size.width - case .horizontal(let options): - monthWidth = options.monthWidth( - calendarWidth: size.width, - interMonthSpacing: content.interMonthSpacing) - } - - let size = monthHeaderView.systemLayoutSizeFitting( - CGSize(width: monthWidth, height: UIView.layoutFittingCompressedSize.height), - withHorizontalFittingPriority: .required, - verticalFittingPriority: .fittingSizeLevel) + previousHeightsForVisibleMonthHeaders?[month] ?? monthHeaderHeight(for: month) + }) + } - return size.height + private func monthHeaderHeight(for month: Month) -> CGFloat { + let monthHeaderItemModel = content.monthHeaderItemProvider(month) + let monthHeaderView = sizingMonthHeaderViewsForViewDifferentiators.value( + for: monthHeaderItemModel._itemViewDifferentiator, + missingValueProvider: { + monthHeaderItemModel._makeView() }) + monthHeaderItemModel._setContent(onViewOfSameType: monthHeaderView) + + let monthWidth: CGFloat + switch content.monthsLayout { + case .vertical: + monthWidth = size.width + case .horizontal(let options): + monthWidth = options.monthWidth( + calendarWidth: size.width, + interMonthSpacing: content.interMonthSpacing) + } + + let size = monthHeaderView.systemLayoutSizeFitting( + CGSize(width: monthWidth, height: UIView.layoutFittingCompressedSize.height), + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel) + + return size.height } private func layoutItem( @@ -698,8 +701,7 @@ final class VisibleItemsProvider { calendarItemModel = context.calendarItemModelCache.value( for: itemType, missingValueProvider: { - previousCalendarItemModelCache?[itemType] - ?? content.monthHeaderItemProvider(month) + previousCalendarItemModelCache?[itemType] ?? content.monthHeaderItemProvider(month) }) // Create a visible item for the separator view, if needed. @@ -871,10 +873,15 @@ final class VisibleItemsProvider { daysAndFrames: dayRangeLayoutContext.daysAndFrames, boundingUnionRectOfDayFrames: dayRangeLayoutContext.boundingUnionRectOfDayFrames) + let itemType = VisibleItem.ItemType.dayRange(dayRange) context.visibleItems.insert( VisibleItem( - calendarItemModel: dayRangeItemProvider(dayRangeLayoutContext), - itemType: .dayRange(dayRange), + calendarItemModel: context.calendarItemModelCache.value( + for: itemType, + missingValueProvider: { + previousCalendarItemModelCache?[itemType] ?? dayRangeItemProvider(dayRangeLayoutContext) + }), + itemType: itemType, frame: frame)) } @@ -1027,10 +1034,18 @@ final class VisibleItemsProvider { dayOfWeekPositionsAndFrames: dayOfWeekPositionsAndFrames, daysAndFrames: daysAndFrames.sorted(by: { $0.day < $1.day }), bounds: CGRect(origin: .zero, size: expandedMonthFrame.size)) - if let itemModel = monthBackgroundItemProvider(monthLayoutContext) { + + let itemType = VisibleItem.ItemType.monthBackground(month) + let itemModel = context.calendarItemModelCache.optionalValue( + for: itemType, + missingValueProvider: { + previousCalendarItemModelCache?[itemType] ?? + monthBackgroundItemProvider(monthLayoutContext) + }) + if let itemModel { let visibleItem = VisibleItem( calendarItemModel: itemModel, - itemType: .monthBackground(month), + itemType: itemType, frame: expandedMonthFrame) context.visibleItems.insert(visibleItem) }