From b3f05a6ae8af237cfba736311441f2a36f2c661c Mon Sep 17 00:00:00 2001 From: Bryan Keller Date: Tue, 28 Jul 2020 01:36:50 -0700 Subject: [PATCH] Add content inset support via layout margins --- Sources/Internal/FrameProvider.swift | 8 +++-- Sources/Internal/VisibleItemsProvider.swift | 15 ++++++--- Sources/Public/CalendarView.swift | 35 +++++++++++++++++++-- Tests/FrameProviderTests.swift | 4 +++ Tests/VisibleItemsProviderTests.swift | 4 +++ 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/Sources/Internal/FrameProvider.swift b/Sources/Internal/FrameProvider.swift index 1b60bd7..3b8ce5c 100644 --- a/Sources/Internal/FrameProvider.swift +++ b/Sources/Internal/FrameProvider.swift @@ -13,8 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import CoreGraphics -import Foundation +import UIKit /// Provides frame and size information about all core layout items. The calendar is laid out lazily, starting with an initial known layout /// frame (like the frame of an initially visible month header). All subsequent layout calculations are done by laying out items adjacent to @@ -26,17 +25,19 @@ final class FrameProvider { init( content: CalendarViewContent, size: CGSize, + layoutMargins: NSDirectionalEdgeInsets, scale: CGFloat, monthHeaderHeight: CGFloat) { self.content = content self.size = size + self.layoutMargins = layoutMargins self.scale = scale self.monthHeaderHeight = monthHeaderHeight switch content.monthsLayout { case .vertical: - monthWidth = size.width + monthWidth = size.width - layoutMargins.leading - layoutMargins.trailing case .horizontal(let _monthWidth): monthWidth = _monthWidth } @@ -53,6 +54,7 @@ final class FrameProvider { // MARK: Internal let size: CGSize + let layoutMargins: NSDirectionalEdgeInsets let scale: CGFloat let daySize: CGSize diff --git a/Sources/Internal/VisibleItemsProvider.swift b/Sources/Internal/VisibleItemsProvider.swift index 4e020b0..e19a563 100644 --- a/Sources/Internal/VisibleItemsProvider.swift +++ b/Sources/Internal/VisibleItemsProvider.swift @@ -26,6 +26,7 @@ final class VisibleItemsProvider { calendar: Calendar, content: CalendarViewContent, size: CGSize, + layoutMargins: NSDirectionalEdgeInsets, scale: CGFloat, monthHeaderHeight: CGFloat) { @@ -38,6 +39,7 @@ final class VisibleItemsProvider { frameProvider = FrameProvider( content: content, size: size, + layoutMargins: layoutMargins, scale: scale, monthHeaderHeight: monthHeaderHeight) } @@ -50,6 +52,10 @@ final class VisibleItemsProvider { frameProvider.size } + var layoutMargins: NSDirectionalEdgeInsets { + frameProvider.layoutMargins + } + var scale: CGFloat { frameProvider.scale } @@ -701,18 +707,19 @@ final class VisibleItemsProvider { switch content.monthsLayout { case .vertical(let options): minimumScrollOffset = monthFrame.minY - - (options.pinDaysOfWeekToTop ? frameProvider.daySize.height : 0) + (options.pinDaysOfWeekToTop ? frameProvider.daySize.height : 0) - + layoutMargins.top case .horizontal: - minimumScrollOffset = monthFrame.minX + minimumScrollOffset = monthFrame.minX - layoutMargins.leading } } if month == content.dayRange.upperBound.month { switch content.monthsLayout { case .vertical: - maximumScrollOffset = monthFrame.maxY + maximumScrollOffset = monthFrame.maxY + layoutMargins.bottom case .horizontal: - maximumScrollOffset = monthFrame.maxX + maximumScrollOffset = monthFrame.maxX + layoutMargins.trailing } } } diff --git a/Sources/Public/CalendarView.swift b/Sources/Public/CalendarView.swift index 8a89d47..f4a37aa 100644 --- a/Sources/Public/CalendarView.swift +++ b/Sources/Public/CalendarView.swift @@ -142,6 +142,28 @@ public final class CalendarView: UIView { } } + /// `CalendarView` only supports positive values for `layoutMargins`. Negative values will be changed to `0`. + public override var layoutMargins: UIEdgeInsets { + didSet { + super.layoutMargins = UIEdgeInsets( + top: max(layoutMargins.top, 0), + left: max(layoutMargins.left, 0), + bottom: max(layoutMargins.bottom, 0), + right: max(layoutMargins.right, 0)) + } + } + + /// `CalendarView` only supports positive values for `directionalLayoutMargins`. Negative values will be changed to `0`. + public override var directionalLayoutMargins: NSDirectionalEdgeInsets { + didSet { + super.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: max(directionalLayoutMargins.top, 0), + leading: max(directionalLayoutMargins.leading, 0), + bottom: max(directionalLayoutMargins.bottom, 0), + trailing: max(directionalLayoutMargins.trailing, 0)) + } + } + public override func didMoveToWindow() { super.didMoveToWindow() @@ -150,6 +172,10 @@ public final class CalendarView: UIView { } } + public override func layoutMarginsDidChange() { + setNeedsLayout() + } + public override func layoutSubviews() { super.layoutSubviews() @@ -388,6 +414,7 @@ public final class CalendarView: UIView { if let existingVisibleItemsProvider = _visibleItemsProvider, existingVisibleItemsProvider.size == bounds.size, + existingVisibleItemsProvider.layoutMargins == directionalLayoutMargins, existingVisibleItemsProvider.scale == scale { return existingVisibleItemsProvider @@ -396,6 +423,7 @@ public final class CalendarView: UIView { calendar: calendar, content: content, size: bounds.size, + layoutMargins: directionalLayoutMargins, scale: scale, monthHeaderHeight: monthHeaderHeight()) _visibleItemsProvider = visibleItemsProvider @@ -404,9 +432,12 @@ public final class CalendarView: UIView { } private var initialMonthHeaderAnchorLayoutItem: LayoutItem { - visibleItemsProvider.anchorMonthHeaderItem( + let offset = CGPoint( + x: scrollView.contentOffset.x + directionalLayoutMargins.leading, + y: scrollView.contentOffset.y + directionalLayoutMargins.top) + return visibleItemsProvider.anchorMonthHeaderItem( for: content.monthRange.lowerBound, - offset: scrollView.contentOffset, + offset: offset, scrollPosition: .firstFullyVisiblePosition) } diff --git a/Tests/FrameProviderTests.swift b/Tests/FrameProviderTests.swift index eb1b953..79677a7 100644 --- a/Tests/FrameProviderTests.swift +++ b/Tests/FrameProviderTests.swift @@ -39,6 +39,7 @@ final class FrameProviderTests: XCTestCase { .withVerticalDayMargin(20) .withHorizontalDayMargin(10), size: size, + layoutMargins: .zero, scale: 3, monthHeaderHeight: monthHeaderHeight) verticalPinnedDaysOfWeekFrameProvider = FrameProvider( @@ -51,6 +52,7 @@ final class FrameProviderTests: XCTestCase { .withVerticalDayMargin(20) .withHorizontalDayMargin(10), size: size, + layoutMargins: .zero, scale: 3, monthHeaderHeight: monthHeaderHeight) verticalPartialMonthFrameProvider = FrameProvider( @@ -64,6 +66,7 @@ final class FrameProviderTests: XCTestCase { .withVerticalDayMargin(20) .withHorizontalDayMargin(10), size: size, + layoutMargins: .zero, scale: 3, monthHeaderHeight: monthHeaderHeight) horizontalFrameProvider = FrameProvider( @@ -76,6 +79,7 @@ final class FrameProviderTests: XCTestCase { .withVerticalDayMargin(20) .withHorizontalDayMargin(10), size: size, + layoutMargins: .zero, scale: 3, monthHeaderHeight: monthHeaderHeight) } diff --git a/Tests/VisibleItemsProviderTests.swift b/Tests/VisibleItemsProviderTests.swift index 8a129c6..ab1c1b6 100644 --- a/Tests/VisibleItemsProviderTests.swift +++ b/Tests/VisibleItemsProviderTests.swift @@ -1535,6 +1535,7 @@ final class VisibleItemsProviderTests: XCTestCase { visibleDateRange: dateRange, monthsLayout: .vertical(options: VerticalMonthsLayoutOptions()))), size: size, + layoutMargins: .zero, scale: 2, monthHeaderHeight: 50) @@ -1546,6 +1547,7 @@ final class VisibleItemsProviderTests: XCTestCase { visibleDateRange: dateRange, monthsLayout: .vertical(options: VerticalMonthsLayoutOptions(pinDaysOfWeekToTop: true)))), size: size, + layoutMargins: .zero, scale: 2, monthHeaderHeight: 50) @@ -1558,6 +1560,7 @@ final class VisibleItemsProviderTests: XCTestCase { monthsLayout: .vertical( options: VerticalMonthsLayoutOptions(alwaysShowCompleteBoundaryMonths: false)))), size: size, + layoutMargins: .zero, scale: 2, monthHeaderHeight: 50) @@ -1569,6 +1572,7 @@ final class VisibleItemsProviderTests: XCTestCase { visibleDateRange: dateRange, monthsLayout: .horizontal(monthWidth: 300))), size: size, + layoutMargins: .zero, scale: 2, monthHeaderHeight: 50)