ForwardWidgets
Widgets for Douban and Trakt watchlists plus personalized recommendations, live TV streaming including PlutoTV, Yatu ra…
git clone https://github.com/shohe/InfiniteCalendar.gitshohe/InfiniteCalendarGoogle Calendar-like infinite scrollable calendar for SwiftUI.
InfiniteCalendar is infinite scrollable calendar for iOS written in Swift.
UI/UX design inspired by GoogleCalendar. Implementation inspired by JZCalendarWeekView.
InfiniteCalendar is available through Swift Package Manager.
Add it to an existing Xcode project as a package dependency:
You need to define View, ViewModel and CollectionViewCell for display cell on Calendar:
struct EventCellView: CellableView {
typealias VM = ViewModel
// MARK: ViewModel
struct ViewModel: ICEventable { ... }
// MARK: View
var viewModel: ViewModel
init(_ viewModel: ViewModel) {
self.viewModel = viewModel
}
var body: some View {
...
}
}
struct ViewModel: ICEventable {
private(set) var id: String = UUID().uuidString
var text: String
var startDate: Date
var endDate: Date?
var intraStartDate: Date
var intraEndDate: Date
var editState: EditState?
var isAllDay: Bool
init(text: String, start: Date, end: Date?, isAllDay: Bool = false, editState: EditState? = nil) {
...
}
// ! Make sure copy current object, otherwise view won't display properly when SwiftUI View is updated.
func copy() -> EventCellView.ViewModel {
var copy = ViewModel(text: text, start: startDate, end: endDate, isAllDay: isAllDay, editState: editState)
...
return copy
}
static func create(from eventable: EventCellView.ViewModel?, state: EditState?) -> EventCellView.ViewModel {
if var model = eventable?.copy() {
model.editState = state
return model
}
return ViewModel(text: "New", start: Date(), end: nil, editState: state)
}
}
final class EventCell: ViewHostingCell<EventCellView> {
override init(frame: CGRect) {
super.init(frame: frame)
}
}
struct ContentView: View {
@State var events: [EventCellView.VM] = []
@State var didTapToday: Bool = false
@ObservedObject var settings: ICViewSettings = ICViewSettings(
numOfDays: 3,
initDate: Date(),
scrollType: .sectionScroll,
moveTimeMinInterval: 15,
timeRange: (1, 23),
withVibration: true
)
var body: some View {
InfiniteCalendar<EventCellView, EventCell, ICViewSettings>(events: $events, settings: settings, didTapToday: $didTapToday)
}
}
This method will be called when changed currrent date displayed on calendar. The date can be get is the leftest date on current display.
ex.) Display 3 column dates, 4/1 | 4/2 | 4/3 -> 4/1 can be obtained.
InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onCurrentDateChanged { date in
currentDate = date
}
This method will be called when item was tapped.
InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onItemSelected { item in
selectedItem = item
}
This method will be called when item was created by long tap with drag gesture.
InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onEventAdded { item in
events.append(item)
}
This method will be called when item was created by long tap gesture on exist item.
InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onEventMoved { item in
if let index = events.firstIndex(where: {$0.id == item.id}) {
events[index] = item
}
}
This method will be called when canceled gesture event by some accident or issues.
InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onEventCanceled { item in
print("Canceled some event gesture for \(item.id).")
}
If you want to customize Settings, create SubClass of ICViewSettings.
Number of dispaly dates on a screen
Sample: numOfDays = 1, numOfDays = 3, numOfDays = 7

The display position of Date only for One-day layout.
Sample: datePosition = .top, datePosition = .left

The display date for lounch app
There is two kinds of scroll type, Section and Page.
SectionType will deside scroll amount by scroll velocity. On the other hand PageType is always scroll to next / prev page with scroll gesture.
Interval minutes time for drag gesture. Default value is 15. Which means when item was created/moved time will move every 15 minutes
The display time on time header as a label. Default value is (1, 23). Which means display 1:00 ~ 23:00.
If vibration is needed during dragging gesture. Default value is true. Vibration feedback is almost same as GoogleCalendar.
You can customize each components on the bellow.
Associatedtypes
Component class is define as typealias to customize.

//* When you customize, set two of classes to custom class you created. // TimeHeader associatedtype TimeHeaderView: ICTimeHeaderView associatedtype TimeHeader: ICTimeHeader<TimeHeaderView> // DateHeader associatedtype DateHeaderView: ICDateHeaderView associatedtype DateHeader: ICDateHeader<DateHeaderView> // DateHeaderCorner associatedtype DateCornerView: ICDateCornerView associatedtype DateCorner: ICDateCorner<DateCornerView> // AllDayHeader associatedtype AllDayHeaderView: ICAllDayHeaderView associatedtype AllDayHeader: ICAllDayHeader<AllDayHeaderView> // AllDayHeaderCorner associatedtype AllDayCornerView: ICAllDayCornerView associatedtype AllDayCorner: ICAllDayCorner<AllDayCornerView> // Timeline associatedtype TimelineView: ICTimelineView associatedtype Timeline: ICTimeline<TimelineView> // TimeHeaderBackground associatedtype TimeHeaderBackgroundView: ICTimeHeaderBackgroundView associatedtype TimeHeaderBackground: ICTimeHeaderBackground<TimeHeaderBackgroundView> // DateHeaderBackground associatedtype DateHeaderBackgroundView: ICDateHeaderBackgroundView associatedtype DateHeaderBackground: ICDateHeaderBackground<DateHeaderBackgroundView> // AllDayHeaderBackground associatedtype AllDayHeaderBackgroundView: ICAllDayHeaderBackgroundView associatedtype AllDayHeaderBackground: ICAllDayHeaderBackground<AllDayHeaderBackgroundView>
All you need is 4 steps.
// DateHeader should be inherited `ICDateHeader`.
class CustomDateHeader: ICDateHeader<CustomDateHeaderView> {}
// CustomView should be inherited `ICDateHeaderView`.
struct CustomDateHeaderView: ICDateHeaderView {
public typealias Item = ICDateHeaderItem
var item: Item
public init(_ item: Item) {
self.item = item
}
public var body: some View {
...
}
}
Create SubClass inherited ICSettings.
class CustomSettings: ICSettings {
@Published public var numOfDays: Int = 1
@Published public var initDate: Date = Date()
@Published public var scrollType: ScrollType = .pageScroll
@Published public var moveTimeMinInterval: Int = 15
@Published public var timeRange: (startTime: Int, endTime: Int) = (1, 23)
@Published public var withVibrateFeedback: Bool = true
required public init() {}
...
}
class CustomSettings: ICSettings {
typealias DateHeaderView = CustomDateHeaderView
typealias DateHeader = CustomDateHeader
...
}
...
@State var events: [EventCellView.VM] = SampleData().events
@State var didTapToday: Bool = false
@ObservedObject var settings: CustomSettings = CustomSettings()
var body: some View {
InfiniteCalendar<EventCellView, EventCell, CustomSettings>(events: $events, settings: settings, didTapToday: $didTapToday)
}
...
InfiniteCalendar is available under the MIT license. See the LICENSE file for more info.
more like this
Widgets for Douban and Trakt watchlists plus personalized recommendations, live TV streaming including PlutoTV, Yatu ra…
Vibe-coded, but not slop. A mobile-first SDR receiver for iOS, Android & Apple Watch — the live waterfall on your wrist…
A lightweight, simplified, RedNotebook-inspired journal/diary app. Built with Electron. Makes dated text files.
search projects, people, and tags