Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Custom event views support #274

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CalendarKitDemo/CalendarApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
391B6FA72541611500A49621 /* MyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391B6FA62541611500A49621 /* MyEvent.swift */; };
397054E0253D390E00C33B44 /* MyEventView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 397054DF253D390E00C33B44 /* MyEventView.xib */; };
397054E2253D394100C33B44 /* MyEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397054E1253D394100C33B44 /* MyEventView.swift */; };
75776AF58133F6BBDDCF1CE7 /* Pods_CalendarApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A223D8E39EB8C7A25F4E823A /* Pods_CalendarApp.framework */; };
Expand All @@ -21,6 +22,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
391B6FA62541611500A49621 /* MyEvent.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = MyEvent.swift; sourceTree = "<group>"; };
397054DF253D390E00C33B44 /* MyEventView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MyEventView.xib; sourceTree = "<group>"; };
397054E1253D394100C33B44 /* MyEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyEventView.swift; sourceTree = "<group>"; };
786F0C3439DCA027A1D04664 /* Pods-CalendarApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CalendarApp.release.xcconfig"; path = "Target Support Files/Pods-CalendarApp/Pods-CalendarApp.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -55,6 +57,7 @@
children = (
397054DF253D390E00C33B44 /* MyEventView.xib */,
397054E1253D394100C33B44 /* MyEventView.swift */,
391B6FA62541611500A49621 /* MyEvent.swift */,
);
path = CustomEventView;
sourceTree = "<group>";
Expand Down Expand Up @@ -227,6 +230,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
391B6FA72541611500A49621 /* MyEvent.swift in Sources */,
397054E2253D394100C33B44 /* MyEventView.swift in Sources */,
7924836B23CB7BFE003E3D27 /* AppDelegate.swift in Sources */,
7924836E23CB7BFE003E3D27 /* ExampleNotificationController.swift in Sources */,
Expand Down
36 changes: 26 additions & 10 deletions CalendarKitDemo/CalendarApp/CustomCalendarExampleController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe
override func loadView() {
calendar = customCalendar
dayView = DayView(calendar: calendar)
dayView.timelinePagerView.timelineViewAppearance = self
view = dayView
}

Expand Down Expand Up @@ -125,10 +126,15 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe

private func generateEventsForDate(_ date: Date) -> [EventDescriptor] {
var workingDate = date.add(TimeChunk.dateComponents(hours: Int(arc4random_uniform(10) + 5)))
var events = [Event]()
var events = [EventDescriptor]()

for i in 0...4 {
let event = Event()
let event: EventDescriptor
if (Bool.random()) {
event = Event()
} else {
event = MyEvent()
}
let duration = Int(arc4random_uniform(160) + 60)
let datePeriod = TimePeriod(beginning: workingDate,
chunk: TimeChunk.dateComponents(minutes: duration))
Expand Down Expand Up @@ -159,7 +165,7 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe

let nextOffset = Int(arc4random_uniform(250) + 40)
workingDate = workingDate.add(TimeChunk.dateComponents(minutes: nextOffset))
event.userInfo = String(i)
// event.userInfo = String(i)
}

print("Events for \(date)")
Expand All @@ -174,8 +180,12 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe

// MARK: TimelineViewAppearance

func timelineView(_ timeloneView: TimelineView) -> EventView {
MyEventView()
func timelineView(_ timeloneView: TimelineView, viewFor event: EventDescriptor) -> EventView {
if (event is MyEvent) {
return MyEventView()
} else {
return DefaultEventView()
}
}

// MARK: DayViewDelegate
Expand All @@ -190,11 +200,12 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe
}

override func dayViewDidLongPressEventView(_ eventView: EventView) {
guard let descriptor = eventView.descriptor as? Event else {
return
}
let descriptor = eventView.descriptor!
// guard let descriptor = eventView.descriptor as? Event else { // TODO: for what puproses it was here?
// return
// }
endEventEditing()
print("Event has been longPressed: \(descriptor) \(String(describing: descriptor.userInfo))")
print("Event has been longPressed: \(descriptor) \(String(describing: descriptor.text/*.userInfo*/))") // TODO: userInfo not works now
beginEditing(event: descriptor, animated: true)
print(Date())
}
Expand Down Expand Up @@ -229,7 +240,12 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe
private func generateEventNearDate(_ date: Date) -> EventDescriptor {
let duration = Int(arc4random_uniform(160) + 60)
let startDate = date.subtract(TimeChunk.dateComponents(minutes: Int(CGFloat(duration) / 2)))
let event = Event()
var event: EventDescriptor
if (Bool.random()) {
event = Event()
} else {
event = MyEvent()
}
let datePeriod = TimePeriod(beginning: startDate,
chunk: TimeChunk.dateComponents(minutes: duration))
event.startDate = datePeriod.beginning!
Expand Down
73 changes: 73 additions & 0 deletions CalendarKitDemo/CalendarApp/CustomEventView/MyEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// MyEvent.swift
// CalendarApp
//
// Created by RareScrap on 22.10.2020.
// Copyright © 2020 Richard Topchii. All rights reserved.
//

import Foundation
import CalendarKit

class MyEvent: EventDescriptor {
public var myVar = "asdasd"
public var startDate = Date()
public var endDate = Date()
public var isAllDay = false
public var text = ""
public var attributedText: NSAttributedString?
public var color = SystemColors.systemBlue {
didSet {
updateColors()
}
}
public var backgroundColor = SystemColors.systemBlue.withAlphaComponent(0.3)
public var textColor = SystemColors.label
public var font = UIFont.boldSystemFont(ofSize: 12)
public var userInfo: Any?
public weak var editedEvent: EventDescriptor? {
didSet {
updateColors()
}
}

public init() {}

func makeEditable() -> Self {
let cloned = MyEvent()
cloned.startDate = startDate
cloned.endDate = endDate
cloned.isAllDay = isAllDay
cloned.text = text
cloned.attributedText = attributedText
cloned.color = color
cloned.backgroundColor = backgroundColor
cloned.textColor = textColor
cloned.userInfo = userInfo
cloned.editedEvent = self
return cloned as! Self
}

public func commitEditing() {
guard let edited = editedEvent else {return}
edited.startDate = startDate
edited.endDate = endDate
}

private func updateColors() {
(editedEvent != nil) ? applyEditingColors() : applyStandardColors()
}

private func applyStandardColors() {
backgroundColor = color.withAlphaComponent(0.3)
var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
color.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
textColor = UIColor(hue: h, saturation: s, brightness: b * 0.4, alpha: a)
}

private func applyEditingColors() {
backgroundColor = color
textColor = .white
}

}
4 changes: 4 additions & 0 deletions CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class MyEventView: EventView {
let widthConstraint = v.trailingAnchor.constraint(equalTo: self.trailingAnchor)
let heightConstraint = v.leadingAnchor.constraint(equalTo: self.leadingAnchor)
self.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}

override open func updateWithDescriptor(event: EventDescriptor) {
super.updateWithDescriptor(event: event)
subviews[2].backgroundColor = event.color // TODO: should we do this before super?
}
}
50 changes: 50 additions & 0 deletions CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.xib
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,56 @@
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="tJx-Da-DNp">
<rect key="frame" x="0.0" y="0.0" width="487" height="103"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="vqZ-1b-KXf">
<rect key="frame" x="189" y="33" width="109" height="37.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Катя Иванова" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dqk-02-1go">
<rect key="frame" x="0.0" y="0.0" width="109" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Дизайн Френч" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WO9-3r-kqE">
<rect key="frame" x="0.0" y="20.5" width="109" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2000₽" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qya-X6-C5g">
<rect key="frame" x="427.5" y="41.5" width="51.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ymG-Co-yZX">
<rect key="frame" x="418.5" y="31" width="1" height="41"/>
<color key="backgroundColor" red="0.57820894299279446" green="0.68182538396524617" blue="0.85096406267512692" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="C5y-yN-0RW"/>
<constraint firstAttribute="height" constant="41" id="c65-WB-E1a"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.1910511098128741" green="0.46278833306891665" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vqZ-1b-KXf" firstAttribute="centerX" secondItem="tJx-Da-DNp" secondAttribute="centerX" id="32c-oU-Laa"/>
<constraint firstItem="vqZ-1b-KXf" firstAttribute="centerY" secondItem="tJx-Da-DNp" secondAttribute="centerY" id="GZ5-5d-4po"/>
<constraint firstItem="Qya-X6-C5g" firstAttribute="leading" secondItem="ymG-Co-yZX" secondAttribute="trailing" constant="8" id="K4W-Wx-OxI"/>
<constraint firstItem="ymG-Co-yZX" firstAttribute="centerY" secondItem="tJx-Da-DNp" secondAttribute="centerY" id="Yc0-gw-dBi"/>
<constraint firstItem="Qya-X6-C5g" firstAttribute="centerY" secondItem="tJx-Da-DNp" secondAttribute="centerY" id="a05-69-KUZ"/>
<constraint firstAttribute="trailing" secondItem="Qya-X6-C5g" secondAttribute="trailing" constant="8" id="vLD-if-bbA"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="250.00000000000003" y="661.94196428571422"/>
</view>
<view contentMode="scaleToFill" id="yxy-xl-A1n">
<rect key="frame" x="0.0" y="0.0" width="382" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
Expand Down
2 changes: 1 addition & 1 deletion Source/Header/DaySelector/DateLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public final class DateLabel: UILabel, DaySelectorItemProtocol {
}
}

private var isToday: Bool {
public /*private*/ var isToday: Bool { // TODO: should we return it to private?
return calendar.isDateInToday(date)
}

Expand Down
10 changes: 5 additions & 5 deletions Source/Timeline/EventDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import UIKit
public protocol EventDescriptor: AnyObject {
var startDate: Date {get set}
var endDate: Date {get set}
var isAllDay: Bool {get}
var text: String {get}
var isAllDay: Bool {get set} // TODO: should we return this to read-only?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these properties should be read-only, as the CalendarKit doesn't modify them.

Copy link
Contributor Author

@RareScrap RareScrap Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in this case, we cannot use EventDescriptor as a generic type for custom descriptors. I find it quite convenient:

var event: EventDescriptor
if (Bool.random()) {
    event = Event()
} else {
    event = MyEvent()
}
// imposible with read-only properties
event.text = info.reduce("", {$0 + $1 + "\n"})
event.color = colors[Int(arc4random_uniform(UInt32(colors.count)))]

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that's not a requirement by the library.
You're free to create own, stricter protocol requirement and use it. Meanwhile, it would inherit from EventDescriptor, so it would be perfectly compatible.

var text: String {get set} // TODO: should we return this to read-only?
var attributedText: NSAttributedString? {get}
var font : UIFont {get}
var color: UIColor {get}
var textColor: UIColor {get}
var backgroundColor: UIColor {get}
var color: UIColor {get set} // TODO: should we return this to read-only?
var textColor: UIColor {get set} // TODO: should we return this to read-only?
var backgroundColor: UIColor {get set} // TODO: should we return this to read-only?
var editedEvent: EventDescriptor? {get set}
func makeEditable() -> Self
func commitEditing()
Expand Down
2 changes: 1 addition & 1 deletion Source/Timeline/EventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ open class EventView: UIView {
}
}

class DefaultEventView: EventView {
public class DefaultEventView: EventView { // TODO: should we return it to private?
public var color = SystemColors.label

public var contentHeight: CGFloat {
Expand Down
3 changes: 2 additions & 1 deletion Source/Timeline/TimelinePagerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr
let day = TimePeriod(beginning: date,
chunk: TimeChunk.dateComponents(days: 1))
let validEvents = events.filter{$0.datePeriod.overlaps(with: day)}
timeline.appearance = self.timelineViewAppearance // TODO: is this a propper place for setting the appearance?
timeline.layoutAttributes = validEvents.map(EventLayoutAttributes.init)
}

Expand All @@ -202,7 +203,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr
/// - Parameter animated: if true, CalendarKit animates event creation
public func create(event: EventDescriptor, animated: Bool) {
// TODO: remove ! `from currentTimeline!.timeline`
let eventView = timelineViewAppearance?.timelineView(currentTimeline!.timeline) ?? DefaultEventView() // TODO: default appearance
let eventView = timelineViewAppearance?.timelineView(currentTimeline!.timeline, viewFor: event) ?? DefaultEventView() // TODO: default appearance
eventView.updateWithDescriptor(event: event)
addSubview(eventView)
// layout algo
Expand Down
30 changes: 19 additions & 11 deletions Source/Timeline/TimelineView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public protocol TimelineViewDelegate: AnyObject {
}

public protocol TimelineViewAppearance: AnyObject {
func timelineView(_ timeloneView: TimelineView/*, viewFor event: EventDescriptor*/) -> EventView
func timelineView(_ timeloneView: TimelineView, viewFor event: EventDescriptor) -> EventView
}

public final class TimelineView: UIView {
Expand Down Expand Up @@ -59,16 +59,24 @@ public final class TimelineView: UIView {
}
}

private var storage = [EventView]()
// EventDescriptor.Type -> array of reusable EventViews
private var storage = [String : Array<EventView>]()
func enqueue(views: [EventView]) {
views.forEach{$0.frame = .zero}
storage.append(contentsOf: views)
}
func dequeue() -> EventView {
guard !storage.isEmpty else {
return appearance?.timelineView(self) ?? DefaultEventView() // TODO: default appearance
for v in views {
v.frame = .zero
let descriptorTypeName = String(reflecting: v.descriptor)
if (storage[descriptorTypeName] == nil) {
storage[descriptorTypeName] = [EventView]()
}
return storage.removeLast()
storage[descriptorTypeName]!.append(v)
}
}
func dequeue(event: EventDescriptor) -> EventView {
let descriptorTypeName = String(reflecting: event)
guard (storage[descriptorTypeName]?.isEmpty) != nil else {
return appearance?.timelineView(self, viewFor: event) ?? DefaultEventView()
}
return storage[descriptorTypeName]!.removeLast()
}

public var firstEventYPosition: CGFloat? {
Expand Down Expand Up @@ -456,8 +464,8 @@ public final class TimelineView: UIView {
private func prepareEventViews() {
enqueue(views: eventViews)
eventViews.removeAll()
for _ in regularLayoutAttributes {
let newView = dequeue()
for attr in regularLayoutAttributes {
let newView = dequeue(event: attr.descriptor)
if newView.superview == nil {
addSubview(newView)
}
Expand Down