Skip to content

Commit

Permalink
Horizontal calendar (#8)
Browse files Browse the repository at this point in the history
* added configuration for calendar orientation

* added changes for horizontal paging

Co-authored-by: Ishmeet Sethi <[email protected]>
  • Loading branch information
ThasianX and sethi-ishmeet authored Aug 4, 2020
1 parent d8e7103 commit d96eb04
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@ extension ExampleMonthlyCalendarView: MonthlyCalendarDelegate {

struct ExampleMonthlyCalendarView_Previews: PreviewProvider {
static var previews: some View {
ExampleYearlyCalendarView(ascVisits: Visit.mocks(start: .daysFromToday(-365*2), end: .daysFromToday(365*2)), initialYear: nil)
ExampleMonthlyCalendarView(ascVisits: Visit.mocks(start: .daysFromToday(-365*2), end: .daysFromToday(365*2)), initialMonth: nil)
}
}
11 changes: 11 additions & 0 deletions Sources/ElegantCalendar/Helpers/Extensions/Axis+Invert.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Kevin Li - 12:07 PM - 8/4/20

import SwiftUI

extension Axis {

var inverted: Axis {
(self == .vertical) ? .horizontal : .vertical
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Kevin Li - 12:43 PM - 8/4/20

import SwiftUI

public protocol AxisModifiable: Buildable {

var axis: Axis { get set }

}

extension AxisModifiable {

/// Sets the axis of the calendar to vertical
public func vertical() -> Self {
axis(.vertical)
}

/// Sets the axis of the calendar to vertical
public func horizontal() -> Self {
axis(.horizontal)
}

/// Sets the axis of the calendar
///
/// - Parameter axis: the intended axis of the calendar
public func axis(_ axis: Axis) -> Self {
mutating(keyPath: \.axis, value: axis)
}

}

extension MonthlyCalendarView: AxisModifiable { }
extension YearlyCalendarView: AxisModifiable { }
extension ElegantCalendarView: AxisModifiable { }
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
import SwiftUI

/// Adds a helper function to mutate a properties and help implement _Builder_ pattern
protocol Buildable { }
public protocol Buildable { }

extension Buildable {

Expand Down
27 changes: 23 additions & 4 deletions Sources/ElegantCalendar/Views/ElegantCalendarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SwiftUI
public struct ElegantCalendarView: View {

var theme: CalendarTheme = .default
public var axis: Axis = .horizontal

public let calendarManager: ElegantCalendarManager

Expand All @@ -14,20 +15,38 @@ public struct ElegantCalendarView: View {
}

public var body: some View {
ElegantHPages(manager: calendarManager.pagesManager) {
yearlyCalendarView
monthlyCalendarView
content
}

private var content: some View {
Group {
if axis == .vertical {
ElegantVPages(manager: calendarManager.pagesManager) {
yearlyCalendarView
monthlyCalendarView
}
.onPageChanged(calendarManager.scrollToYearIfOnYearlyView)
.erased
} else {
ElegantHPages(manager: calendarManager.pagesManager) {
yearlyCalendarView
monthlyCalendarView
}
.onPageChanged(calendarManager.scrollToYearIfOnYearlyView)
.erased
}
}
.onPageChanged(calendarManager.scrollToYearIfOnYearlyView)
}

private var yearlyCalendarView: some View {
YearlyCalendarView(calendarManager: calendarManager.yearlyManager)
.axis(axis.inverted)
.theme(theme)
}

private var monthlyCalendarView: some View {
MonthlyCalendarView(calendarManager: calendarManager.monthlyManager)
.axis(axis.inverted)
.theme(theme)
}

Expand Down
27 changes: 21 additions & 6 deletions Sources/ElegantCalendar/Views/Monthly/MonthlyCalendarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SwiftUI
public struct MonthlyCalendarView: View, MonthlyCalendarManagerDirectAccess {

var theme: CalendarTheme = .default
public var axis: Axis = .vertical

@ObservedObject public var calendarManager: MonthlyCalendarManager

Expand All @@ -32,12 +33,8 @@ public struct MonthlyCalendarView: View, MonthlyCalendarManagerDirectAccess {
CalendarConstants.Monthly.cellWidth = geometry.size.width

return ZStack(alignment: .top) {
ElegantVList(manager: listManager,
pageTurnType: .monthlyEarlyCutoff,
viewForPage: monthView)
.onPageChanged(configureNewMonth)
.frame(width: CalendarConstants.Monthly.cellWidth)

monthsList

if isTodayWithinDateRange && !isCurrentMonthYearSameAsTodayMonthYear {
leftAlignedScrollBackToTodayButton
.padding(.trailing, CalendarConstants.Monthly.outerHorizontalPadding)
Expand All @@ -48,6 +45,24 @@ public struct MonthlyCalendarView: View, MonthlyCalendarManagerDirectAccess {
.frame(height: CalendarConstants.cellHeight)
}

private var monthsList: some View {
Group {
if axis == .vertical {
ElegantVList(manager: listManager,
pageTurnType: .monthlyEarlyCutoff,
viewForPage: monthView)
.onPageChanged(configureNewMonth)
.frame(width: CalendarConstants.Monthly.cellWidth)
} else {
ElegantHList(manager: listManager,
pageTurnType: .monthlyEarlyCutoff,
viewForPage: monthView)
.onPageChanged(configureNewMonth)
.frame(width: CalendarConstants.Monthly.cellWidth)
}
}
}

private func monthView(for page: Int) -> AnyView {
MonthView(calendarManager: calendarManager, month: months[page])
.environment(\.calendarTheme, theme)
Expand Down
82 changes: 59 additions & 23 deletions Sources/ElegantCalendar/Views/Yearly/YearlyCalendarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SwiftUI
public struct YearlyCalendarView: View, YearlyCalendarManagerDirectAccess {

var theme: CalendarTheme = .default
public var axis: Axis = .vertical

@ObservedObject public var calendarManager: YearlyCalendarManager

Expand Down Expand Up @@ -36,16 +37,34 @@ public struct YearlyCalendarView: View, YearlyCalendarManagerDirectAccess {
}

private var yearsList: some View {
YearlyCalendarScrollView(calendarManager: calendarManager) {
ForEach(self.years, id: \.self) { year in
YearView(calendarManager: self.calendarManager, year: year)
.environment(\.calendarTheme, self.theme)
}
YearlyCalendarScrollView(axis, calendarManager: calendarManager) {
self.yearsStack
}
.frame(width: CalendarConstants.Yearly.cellWidth,
height: CalendarConstants.cellHeight)
}

private var yearsStack: some View {
Group {
if axis == .vertical {
VStack(spacing: 0) {
calendarContent
}
} else {
HStack(spacing: 0) {
calendarContent
}
}
}
}

private var calendarContent: some View {
ForEach(self.years, id: \.self) { year in
YearView(calendarManager: self.calendarManager, year: year)
.environment(\.calendarTheme, self.theme)
}
}

private var scrollBackToTodayButton: some View {
ScrollBackToTodayButton(scrollBackToToday: calendarManager.scrollBackToToday,
color: theme.primary)
Expand All @@ -59,9 +78,19 @@ private struct YearlyCalendarScrollView: UIViewControllerRepresentable {

@ObservedObject var calendarManager: YearlyCalendarManager

let axis: Axis
let content: AnyView

init<Content: View>(calendarManager: YearlyCalendarManager, @ViewBuilder content: @escaping () -> Content) {
var pageLength: CGFloat {
(axis == .vertical) ? CalendarConstants.cellHeight : CalendarConstants.Yearly.cellWidth
}

var destinationOffset: CGFloat {
pageLength * CGFloat(calendarManager.currentPage.index)
}

init<Content: View>(_ axis: Axis, calendarManager: YearlyCalendarManager, @ViewBuilder content: @escaping () -> Content) {
self.axis = axis
self.calendarManager = calendarManager
self.content = AnyView(content())
}
Expand All @@ -71,16 +100,23 @@ private struct YearlyCalendarScrollView: UIViewControllerRepresentable {
}

func makeUIViewController(context: Context) -> UIScrollViewViewController {
UIScrollViewViewController(content: content, delegate: context.coordinator)
UIScrollViewViewController(axis, content: content, delegate: context.coordinator)
}

func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
viewController.hosting.rootView = content

switch calendarManager.currentPage.state {
case .scroll:
let destinationPoint: CGPoint
if axis == .vertical {
destinationPoint = CGPoint(x: 0, y: destinationOffset)
} else {
destinationPoint = CGPoint(x: destinationOffset, y: 0)
}

DispatchQueue.main.async {
viewController.scrollView.setContentOffset(CGPoint(x: 0, y: self.calendarManager.destinationOffset),
viewController.scrollView.setContentOffset(destinationPoint,
animated: true)
}
case .completed:
Expand All @@ -102,14 +138,15 @@ private struct YearlyCalendarScrollView: UIViewControllerRepresentable {

// Called after the user manually drags from one page to another
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let page = Int(scrollView.contentOffset.y / CalendarConstants.cellHeight)
let contentOffset = (parent.axis == .vertical) ? scrollView.contentOffset.y : scrollView.contentOffset.x
let page = Int(contentOffset / parent.pageLength)
calendarManager.willDisplay(page: page)
}

// Called after the `scrollToToday` or any `scrollToYear` animation finishes
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if calendarManager.currentPage.state == .scroll {
let page = Int(calendarManager.destinationOffset / CalendarConstants.cellHeight)
let page = Int(parent.destinationOffset / parent.pageLength)
calendarManager.willDisplay(page: page)
}
}
Expand All @@ -118,31 +155,30 @@ private struct YearlyCalendarScrollView: UIViewControllerRepresentable {

}

private extension YearlyCalendarManager {

var destinationOffset: CGFloat {
CalendarConstants.cellHeight * CGFloat(currentPage.index)
}

}

private class UIScrollViewViewController: UIViewController {

let hosting: UIHostingController<AnyView>
let scrollView: UIScrollView

init(content: AnyView, delegate: UIScrollViewDelegate) {
init(_ axis: Axis, content: AnyView, delegate: UIScrollViewDelegate) {
hosting = UIHostingController(rootView: content)
scrollView = UIScrollView().withPagination(delegate: delegate)
super.init(nibName: nil, bundle: nil)

let size = hosting.view.sizeThatFits(CGSize(width: screen.width, height: .greatestFiniteMagnitude))
let fittingSize: CGSize
if axis == .vertical {
fittingSize = CGSize(width: screen.width, height: .greatestFiniteMagnitude)
} else {
fittingSize = CGSize(width: .greatestFiniteMagnitude, height: screen.height)
}

let size = hosting.view.sizeThatFits(fittingSize)
hosting.view.frame = CGRect(x: 0, y: 0,
width: screen.width,
width: size.width,
height: size.height)

scrollView.addSubview(hosting.view)
scrollView.contentSize = CGSize(width: screen.width, height: size.height)
scrollView.contentSize = CGSize(width: size.width, height: size.height)
}

required init?(coder: NSCoder) {
Expand All @@ -156,7 +192,7 @@ private class UIScrollViewViewController: UIViewController {
pinEdges(of: scrollView, to: view)

hosting.willMove(toParent: self)
scrollView.addSubview(self.hosting.view)
scrollView.addSubview(hosting.view)
pinEdges(of: hosting.view, to: scrollView)
hosting.didMove(toParent: self)
}
Expand Down

0 comments on commit d96eb04

Please sign in to comment.