Swiftpack.co - Package - zjfjack/JZCalendarWeekView
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.



Build Status CocoaPods Carthage compatible Platform Swift 5.0 license MIT

iOS Calendar Week/Day View in Swift

Inspired from WRCalendarView

Features

  • ☑ X-Day per Page (Day view: 1-day, 3-day view, weekview: 7-day)
  • ☑ Two Scroll types: One-Day scroll (scroll a section) or Page scroll
  • ☑ Two Types of Long Press Gestures: Add a new event & Move an existing event
  • ☑ Events display on calendar view (supports events with conflict time and events crossing few days)
  • ☑ Set horizontal scrollable range dates
  • ☑ Support all device orientations (including iPhone X Landscape) and iPad (Slide Over and Split View)
  • ☑ Customise your own current timeline
  • ☑ All-Day Events

Usage

ViewController

In your viewController, you only need do few things.

  1. Setup your own custom calendarWeekView in viewDidLoad
calendarWeekView.setupCalendar(numOfDays: 7,
                               setDate: Date(),
                               allEvents: JZWeekViewHelper.getIntraEventsByDate(originalEvents: events),
                               scrollType: .pageScroll,
                               firstDayOfWeek: .Monday)
  1. Override viewWillTransition and call viewTransitionHandler in JZWeekViewHelper to support all device orientations
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    JZWeekViewHelper.viewTransitionHandler(to: size, weekView: calendarWeekView)
}
  1. Setup your own custom flowLayout style in viewDidLoad (optional)
calendarWeekView.updateFlowLayout(JZWeekViewFlowLayout(hourHeight: 50, rowHeaderWidth: 50, columnHeaderHeight: 50, hourGridDivision: JZHourGridDivision.noneDiv))

JZBaseWeekView

Create your own WeekView class inheriting from JZBaseWeekView, and you should override the following functions.

  1. Register function: Register your own UICollectionReusableView here. (CollectionViewCell, SupplementaryView or DecorationView)
override func registerViewClasses() {
    super.registerViewClasses()

    // Register CollectionViewCell
    self.collectionView.register(UINib(nibName: "EventCell", bundle: nil), forCellWithReuseIdentifier: "EventCell")

    // Register DecorationView: must provide corresponding JZDecorationViewKinds
    self.flowLayout.register(BlackGridLine.self, forDecorationViewOfKind: JZDecorationViewKinds.verticalGridline)
    self.flowLayout.register(BlackGridLine.self, forDecorationViewOfKind: JZDecorationViewKinds.horizontalGridline)

    // Register SupplementrayView: must override collectionView viewForSupplementaryElementOfKind
    collectionView.register(RowHeader.self, forSupplementaryViewOfKind: JZSupplementaryViewKinds.rowHeader, withReuseIdentifier: "RowHeader")
}

If you want to use your own supplementryView (including your current timeline), you should register it and override the following function

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
  1. CollectionView cellForItemAt: Use your custom collectionViewCell
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let date = flowLayout.dateForColumnHeader(at: indexPath)
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EventCell.className, for: indexPath) as! EventCell
    cell.updateView(event: allEventsBySection[date]![indexPath.row] as! Event)
    return cell
}

JZLongPressView

This view is inheriated from JZBaseWeekView and implements the long press gestures. You can simply follow the setup rules of JZBaseWeekView.
In order to achieve the long press gestures, you should implement the JZLongPressViewDelegate and JZLongPressViewDataSource in your ViewController.

public protocol JZLongPressViewDelegate: class {
    /// When addNew long press gesture ends, this function will be called.
    func weekView(_ weekView: JZLongPressWeekView, didEndAddNewLongPressAt startDate: Date)
    /// When Move long press gesture ends, this function will be called.
    func weekView(_ weekView: JZLongPressWeekView, editingEvent: JZBaseEvent, didEndMoveLongPressAt startDate: Date)
    /// Sometimes the longPress will be cancelled because some curtain reason.
    func weekView(_ weekView: JZLongPressWeekView, longPressType: JZLongPressWeekView.LongPressType, didCancelLongPressAt startDate: Date)
}

public protocol JZLongPressViewDataSource: class {
    /// Implement this function to customise your own AddNew longPressView
    func weekView(_ weekView: JZLongPressWeekView, viewForAddNewLongPressAt startDate: Date) -> UIView
    /// Implement this function to customise your own Move longPressView
    func weekView(_ weekView: JZLongPressWeekView, movingCell: UICollectionViewCell, viewForMoveLongPressAt startDate: Date) -> UIView
}

Also, you should provide the long press types and there are some other properties you can change.

calendarWeekView.longPressDelegate = self
calendarWeekView.longPressDataSource = self
calendarWeekView.longPressTypes = [.addNew, .move]

// Optional
calendarWeekView.addNewDurationMins = 120
calendarWeekView.moveTimeMinInterval = 15

If you want to use the move type long press, you have to inherit your UICollectionViewCell from JZLongPressEventCell to allow retrieving editing JZBaseEvent because of UICollectionView reuse problem. Also, remember to set your cell backgroundColor in cell contentView.

JZBaseEvent

In JZCalendarWeekView, the data model is using [Date: [Event]] dictionary because for each day (a section in collectionView), there might be some events.

A static function called getIntraEventsByDate provided in JZWeekViewHelper allow you to tranform your events list into [Date: [Event]] dictionary.

open class func getIntraEventsByDate<T: JZBaseEvent>(originalEvents: [T]) -> [Date: [T]]

In order to call this function, you should create a subclass of JZBaseEvent and also implement the NSCopying protocol.
For the intraStartDate and intraEndDate in JZBaseEvent, it means that if a event crosses two days, it should be divided into two events but with different intraStartDate and intraEndDate.
eg. startDate = 180329 14:00, endDate = 180330 03:00, then two events should be generated: 1. 180329 14:00(IntraStart) - 23:59(IntraEnd) 2. 180330 00:00(IntraStart) - 03:00(IntraEnd)

All-Day Events

All-Day feature is aimed to display all-day events separately, but only events tagged isAllDay true can be shown. For those events crossing few days would better keep them isAllDay false. (Refer to Apple Calendar & Google Calendar)
In order to active all-day feature, there are only two things you need to do.

  1. Inherit your Event class from JZAllDayEvent to ensure the isAllDay variable added.
  2. In your customised CalendarViewWeekView, override the viewForSupplementaryElementOfKind and use updateView in AllDayHeader to update your all-day view with your own views. Example

Horizontal Scrollable Range

Horizontal scrollable range dates allow you to set your preferred scrollable range. CalendarWeekView can only be horizontal scrollable between startDate(including) and endDate(including). nil means no limit.

  1. You can set scrollableRange when you call setupCalendar() or simply change this variable.
  2. If you change scrollType without calling forceReload(), you should call setHorizontalEdgesOffsetX() to reset the edges, because for different scroll types, the edges are different.

For futher usage, you can also check the example project, some comments in code or just email me.

Requirements

  • iOS 9.0+
  • Xcode 10+
  • Swift 4.2

Installation

Swift Package Manager

JZCalendarWeekView can be added to your project by adding the following dependency to your Package.swift:

.package(url: "https://github.com/zjfjack/JZCalendarWeekView.git", .upToNextMajor(from: "0.7.2"))

Cocoapods

JZCalendarWeekView can be added to your project by adding the following line to your Podfile:

# Latest release in CocoaPods (recommend to use latest version before v1.0.0 release, optional: provide version number)
pod 'JZCalendarWeekView'

Carthage

JZCalendarWeekView can be added to your project by adding the following line to your Cartfile:

# Latest release on Carthage (recommend to use latest version before v1.0.0 release, optional: provide version number)
github "zjfjack/JZCalendarWeekView"

Todo

  • ☐ DecorationView for different background views (refer to #12)
  • ☐ Limited date range: start time and end Time (vertical) in CalendarView
  • ☐ Theme implementation
  • ☐ New scroll type: Infinite scroll
  • ☐ Support different types of event arrangment rules

Author

Jeff Zhang, zekejeff@gmail.com
If you have any questions and suggestions, feel free to contact me.

License

JZCalendarWeekView is available under the MIT license. See the LICENSE for more info.

Github

link
Stars: 262

Releases

Major Release: Redesign Pagination Effect For JZCalendarWeekView - 2019-05-26T06:55:12

The goal of this v0.7.0 release is to redesign JZCalendarWeekView scroll pagination effect.

This issue is actually caused by using setContentOffset method to do pagination effect. The scenario is when users swiped view(willEndDragging called) then they swipe again or touch screen. The result might be

  • Flickering view issue #40 #46 #51
  • view stopped at the touch position(wrong position). For further touch screen description, you can refer to this StackOverflow question which is asked and answered by myself.

In order to redesign the pagination effect, I have to change setContentOffset method to settargetContentOffset and the property initialContentOffset set in willBeginDragging should be deleted because it shouldn't be considered. I don't care where it started, I only care where it should scroll to with the current state. Also, scrollSections for sectionScroll type has been deleted too.

In order to achieve this goal, some methods are refactored and fixed as well.

  1. 3f976fb getDateForX, getDateForY and getDateForPoint are quite confused and misused in the past because the calculation method for gesture point x in UICollectionView is different from UICollectionView contentOffset x. As a result, those methods are refactored to 6 different methods, which are getDateForContentOffsetX, getDateForContentOffsetY, getDateForContentOffset (For contentOffset) getDateForPointX, getDateForPointY, getDateForPoint (For gesture point)

  2. f027db9 ScrollDirection has been redesigned, the previous solution is actually implemented by WRCalendarView, which cannot get actual direction sometimes and it highly depends on the property initialContentOffset which we want to delete.

Unfortunately, two small issues are also introduced in this new pagination effect.

  1. Even though I set the deceleratingRate to fast, the last few milliseconds of scrolling animation are still very slow, which is not as fast as setContentOffset.

  2. Because of the first issue, it will take more time to scroll to the required targetContentOffset, if you scroll again at the same time, it won't work. It means you cannot scroll very fast to the third page only if the last animation finished and view reloaded.

If you have any good suggestions for the deceleratingRate issue, let me know.

One more issue fixed out of this goal is af9fc0a Fixed cell rendering issue #54 by replacing overlap calculation method

Fixed Cell rendering issue with improved overlapping calculation algorithm - 2019-04-28T12:04:12

  1. Replace the old, wrong and inefficient overlapping calculation method implemented by WRCalendarView to a correct and efficient way with complexity O(nlogn), which is from Maximum number of overlapping intervals.

  2. Xcode 10.2 support

Fix some issues - 2019-01-02T22:51:07

  1. Fix JZLongPressWeekView scrollable range issue 56558f1a6cefa4c6b7e01b3ff8ceed71c0c2f96c
  2. Fix vertical ScrollWeekView issue ed125ce2bd2b5527a842b0899f8a1131fe7846ce
  3. Make JZColumnHeader dateFormatter public #43 a2dd6eadbffa918c3f10c312b8124998484456d9

Many important issues fix - 2018-12-18T05:36:40

  1. Bugfix all-day scroll from bottom

Issue: When calendarView is scrolling at the bottom, and at the same time AllDayHeader height decreased, it will cause some view jumping and flickering issue. It is probably because the contentSize height become shorter, the view should scroll up automatically but it reaches the bottom currently, then it will cause that kind of issue.

Solution: Fixed this issue in quick swipe condition, but for slow swipe, I just keep the same height until the swipe ends.

  1. Section grid width different in some cases #30

Issue: Because previous framework is using some nearbyint method to cast the CGFloat to int, which will cause the distance between different grid x might be different. As a result, when using sectionScroll, you will notice some events' sizes changed, actually they are the same, only because the grids distance is different.

Solution: Automatically increase the rowHeaderWidth if the sectionWidth cannot be Int. For sectionWidth decimal part, 0-0.25 -> 0, 0.25-0.75 -> 0.5, 0.75-0.99 -> +1 which means the rowHeaderWidth can only be changed within 0.25*numOfDays CGFloat, and it should be reasonable.

  1. Fix AllDayHeader constraints issues & Improve AllDayHeader updateView logic For those iOS 9 devices, the Xcode console prints warnings when AllDayHeader height become 0 because the padding constraints being set to a value before. I added priority to the constraint to resolve this issue.

  2. Update LongPressWeekView time label text access level #34

Horizontal scrollable range & Redo part of reload methods - 2018-12-18T05:09:02

  1. You can now set the horizontal scrollable range to control users' scrollable range.
  2. Redo some reload methods, which will cause not reload issue when scrolling with sectionSection type

Redesign visible time logic - 2018-09-24T01:43:15

This release will change the visible time when weekView first time appears y offset, even if you don't change any code.

You can now set your own visible time in setupCalendar method with variable visibleTime, and the default value for it is current time. If you want something different, you can set this variable by yourself.

You can refer to the CustomViewController in example project. Also, a new open method can be used to vertically scroll the weekView.

open func scrollCollectionViewTo(time: Date)

Swift 4.2 and Xcode 10 Support - 2018-09-17T04:50:49

Redesign current timeline view - 2018-08-28T06:52:04

  1. Now you can customise your own current timeline as other supplementaryViews
  2. Provided another type of timeline, but it is not 100% completed yet

Totally fix move long press cell usage bugs - 2018-05-04T07:30:30

  1. Bug: For cross-day events, all cells in several sections cannot be hidden or translucent together

    • New variable id in JZBaseEvent is introduced to identify the same event in different cell
  2. Bug: Once move the cell to another page, the previous cell contentView with alpha with be reused, and one random cell with another event will reuse that cell with translucent contentView.

    • New list allOpacityContentViews is utilised to store the translucent contentView, once cancel and end longPress, all the contentViews layer opacity will be reset back to 1
    • Change set alpha to layer opacity in case user set custom alpha value for contentView
  3. Big issue: For both changing alpha or layer opacity for cell contentView, if user set cell background, then it will only work on view in contentView instead of background, which means the background color will not be transparent. However, if you set alpha or layer opacity for cell itself, when collectionView scrolling, the alpha will go back to 1 and will not call willDisplayCell.

    • The temporary solution is forcing users to set backgroundColor in contentView rather than cell itself

LongPress improvements and bug fix - 2018-05-02T07:33:43

  1. Improve the Move long press position relative to the long press view, for now, it will be the same as you originally press. (Same as Apple Calendar)

  2. Fix a serious bug caused by CollectionView Reusability.

    • The CurrentMovingCell used before will be replaced when that reference is reused. Which could cause the editing reference totally wrong.
    • Also, with reusing, the editing cell will be refreshed if it has been reused, which means when you scroll back to that page, this event will be there instead of hidden or translucent.
    • In order to fix that problem, in Move longPress, the custom CollectionViewCell has to inherit from JZBaseEventCell in order to get the JZBaseEvent and find the original editing cell because of the reusability of UICollectionView