diff --git a/CalendarKitDemo/CalendarApp.xcodeproj/project.pbxproj b/CalendarKitDemo/CalendarApp.xcodeproj/project.pbxproj index fe0c6bce..d40cfdb7 100644 --- a/CalendarKitDemo/CalendarApp.xcodeproj/project.pbxproj +++ b/CalendarKitDemo/CalendarApp.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ 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 */; }; 791ACF5023FC887D00B2D602 /* TimeChunk+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 791ACF4F23FC887D00B2D602 /* TimeChunk+DateComponents.swift */; }; 7924834D23CB7B58003E3D27 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7924834C23CB7B58003E3D27 /* Assets.xcassets */; }; @@ -19,6 +22,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 391B6FA62541611500A49621 /* MyEvent.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = MyEvent.swift; sourceTree = ""; }; + 397054DF253D390E00C33B44 /* MyEventView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MyEventView.xib; sourceTree = ""; }; + 397054E1253D394100C33B44 /* MyEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyEventView.swift; sourceTree = ""; }; 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 = ""; }; 791ACF4F23FC887D00B2D602 /* TimeChunk+DateComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeChunk+DateComponents.swift"; sourceTree = ""; }; 7924834023CB7B57003E3D27 /* CalendarApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CalendarApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -46,6 +52,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 397054DE253D38F000C33B44 /* CustomEventView */ = { + isa = PBXGroup; + children = ( + 397054DF253D390E00C33B44 /* MyEventView.xib */, + 397054E1253D394100C33B44 /* MyEventView.swift */, + 391B6FA62541611500A49621 /* MyEvent.swift */, + ); + path = CustomEventView; + sourceTree = ""; + }; 57CFFDE58D70A1545DA52F6E /* Pods */ = { isa = PBXGroup; children = ( @@ -76,6 +92,7 @@ 7924834223CB7B57003E3D27 /* CalendarApp */ = { isa = PBXGroup; children = ( + 397054DE253D38F000C33B44 /* CustomEventView */, 7924836523CB7BFE003E3D27 /* AppDelegate.swift */, 791ACF4F23FC887D00B2D602 /* TimeChunk+DateComponents.swift */, 7924836623CB7BFE003E3D27 /* CustomCalendarExampleController.swift */, @@ -159,6 +176,7 @@ buildActionMask = 2147483647; files = ( 7924835023CB7B58003E3D27 /* LaunchScreen.storyboard in Resources */, + 397054E0253D390E00C33B44 /* MyEventView.xib in Resources */, 7924834D23CB7B58003E3D27 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -212,6 +230,8 @@ 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 */, 7924836C23CB7BFE003E3D27 /* CustomCalendarExampleController.swift in Sources */, @@ -356,13 +376,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = H8S6FURL87; INFOPLIST_FILE = CalendarApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = github.richardtop.CalendarApp; + PRODUCT_BUNDLE_IDENTIFIER = github.richardtop.CalendarApp1123; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -377,13 +398,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = H8S6FURL87; INFOPLIST_FILE = CalendarApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = github.richardtop.CalendarApp; + PRODUCT_BUNDLE_IDENTIFIER = github.richardtop.CalendarApp1123; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/CalendarKitDemo/CalendarApp/CustomCalendarExampleController.swift b/CalendarKitDemo/CalendarApp/CustomCalendarExampleController.swift index 686b43d1..67d39691 100644 --- a/CalendarKitDemo/CalendarApp/CustomCalendarExampleController.swift +++ b/CalendarKitDemo/CalendarApp/CustomCalendarExampleController.swift @@ -2,8 +2,7 @@ import UIKit import CalendarKit import DateToolsSwift -class CustomCalendarExampleController: DayViewController, DatePickerControllerDelegate { - +class CustomCalendarExampleController: DayViewController, DatePickerControllerDelegate, TimelineViewAppearance { var data = [["Breakfast at Tiffany's", "New York, 5th avenue"], @@ -56,6 +55,7 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe override func loadView() { calendar = customCalendar dayView = DayView(calendar: calendar) + dayView.timelinePagerView.timelineViewAppearance = self view = dayView } @@ -68,6 +68,7 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe action: #selector(ExampleController.presentDatePicker)) navigationController?.navigationBar.isTranslucent = false dayView.autoScrollToFirstEvent = true + dayView.timelinePagerView.timelineViewAppearance = self reloadData() } @@ -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)) @@ -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)") @@ -171,6 +177,16 @@ class CustomCalendarExampleController: DayViewController, DatePickerControllerDe baseColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a) return UIColor(hue: h, saturation: s * 0.3, brightness: b, alpha: a) } + + // MARK: TimelineViewAppearance + + func timelineView(_ timeloneView: TimelineView, viewFor event: EventDescriptor) -> EventView { + if (event is MyEvent) { + return MyEventView() + } else { + return DefaultEventView() + } + } // MARK: DayViewDelegate @@ -184,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()) } @@ -223,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! diff --git a/CalendarKitDemo/CalendarApp/CustomEventView/MyEvent.swift b/CalendarKitDemo/CalendarApp/CustomEventView/MyEvent.swift new file mode 100644 index 00000000..2d0c0f35 --- /dev/null +++ b/CalendarKitDemo/CalendarApp/CustomEventView/MyEvent.swift @@ -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 + } + +} diff --git a/CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.swift b/CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.swift new file mode 100644 index 00000000..396c4e80 --- /dev/null +++ b/CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.swift @@ -0,0 +1,32 @@ +// +// MyEventView.swift +// CalendarApp +// +// Created by RareScrap on 19.10.2020. +// Copyright © 2020 Richard Topchii. All rights reserved. +// + +import Foundation +import CalendarKit + +class MyEventView: EventView { + + override func configure() { + super.configure() + + let v = Bundle.main.loadNibNamed("MyEventView", owner: self, options: nil)?.first as! UIView + self.addSubview(v) + + v.translatesAutoresizingMaskIntoConstraints = false + let horizontalConstraint = v.topAnchor.constraint(equalTo: self.topAnchor) + let verticalConstraint = v.bottomAnchor.constraint(equalTo: self.bottomAnchor) + 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? + } +} diff --git a/CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.xib b/CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.xib new file mode 100644 index 00000000..bdc232f6 --- /dev/null +++ b/CalendarKitDemo/CalendarApp/CustomEventView/MyEventView.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Header/DaySelector/DateLabel.swift b/Source/Header/DaySelector/DateLabel.swift index 04824b46..9dc7b6cc 100644 --- a/Source/Header/DaySelector/DateLabel.swift +++ b/Source/Header/DaySelector/DateLabel.swift @@ -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) } diff --git a/Source/Timeline/EventDescriptor.swift b/Source/Timeline/EventDescriptor.swift index 79eab3e1..8785bfea 100644 --- a/Source/Timeline/EventDescriptor.swift +++ b/Source/Timeline/EventDescriptor.swift @@ -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? + 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() diff --git a/Source/Timeline/EventView.swift b/Source/Timeline/EventView.swift index 6df27e60..c6c774e5 100644 --- a/Source/Timeline/EventView.swift +++ b/Source/Timeline/EventView.swift @@ -2,25 +2,13 @@ import UIKit open class EventView: UIView { - public var descriptor: EventDescriptor? - public var color = SystemColors.label - - public var contentHeight: CGFloat { - return textView.frame.height - } - - public lazy var textView: UITextView = { - let view = UITextView() - view.isUserInteractionEnabled = false - view.backgroundColor = .clear - view.isScrollEnabled = false - return view - }() - + // TODO: I think this must be non optional + public var descriptor: EventDescriptor? // TODO: for what purpose this field is? + /// Resize Handle views showing up when editing the event. /// The top handle has a tag of `0` and the bottom has a tag of `1` - public lazy var eventResizeHandles = [EventResizeHandleView(), EventResizeHandleView()] - + public lazy var eventResizeHandles = [EventResizeHandleView(), EventResizeHandleView()] // TODO: Возможность кастомизации + override public init(frame: CGRect) { super.init(frame: frame) configure() @@ -31,38 +19,58 @@ open class EventView: UIView { configure() } - private func configure() { + open func configure() { clipsToBounds = false - color = tintColor - addSubview(textView) for (idx, handle) in eventResizeHandles.enumerated() { handle.tag = idx addSubview(handle) } } - - public func updateWithDescriptor(event: EventDescriptor) { - if let attributedText = event.attributedText { - textView.attributedText = attributedText - } else { - textView.text = event.text - textView.textColor = event.textColor - textView.font = event.font + + override open func layoutSubviews() { + super.layoutSubviews() + let first = eventResizeHandles.first + let last = eventResizeHandles.last + let radius: CGFloat = 40 + let yPad: CGFloat = -radius / 2 + let width = bounds.width + let height = bounds.height + let size = CGSize(width: radius, height: radius) + first?.frame = CGRect(origin: CGPoint(x: width - radius - layoutMargins.right, y: yPad), + size: size) + last?.frame = CGRect(origin: CGPoint(x: layoutMargins.left, y: height - yPad - radius), + size: size) + } + + /** + Custom implementation of the hitTest method is needed for the tap gesture recognizers + located in the ResizeHandleView to work. + Since the ResizeHandleView could be outside of the EventView's bounds, the touches to the ResizeHandleView + are ignored. + In the custom implementation the method is recursively invoked for all of the subviews, + regardless of their position in relation to the Timeline's bounds. + */ + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for resizeHandle in eventResizeHandles { + if let subSubView = resizeHandle.hitTest(convert(point, to: resizeHandle), with: event) { + return subSubView + } } + return super.hitTest(point, with: event) + } + + open func updateWithDescriptor(event: EventDescriptor) { descriptor = event - backgroundColor = event.backgroundColor - color = event.color eventResizeHandles.forEach{ $0.borderColor = event.color $0.isHidden = event.editedEvent == nil } - drawsShadow = event.editedEvent != nil setNeedsDisplay() setNeedsLayout() } - - public func animateCreation() { + + open func animateCreation() { transform = CGAffineTransform(scaleX: 0.8, y: 0.8) func scaleAnimation() { transform = .identity @@ -75,22 +83,42 @@ open class EventView: UIView { animations: scaleAnimation, completion: nil) } +} - /** - Custom implementation of the hitTest method is needed for the tap gesture recognizers - located in the ResizeHandleView to work. - Since the ResizeHandleView could be outside of the EventView's bounds, the touches to the ResizeHandleView - are ignored. - In the custom implementation the method is recursively invoked for all of the subviews, - regardless of their position in relation to the Timeline's bounds. - */ - public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - for resizeHandle in eventResizeHandles { - if let subSubView = resizeHandle.hitTest(convert(point, to: resizeHandle), with: event) { - return subSubView - } +public class DefaultEventView: EventView { // TODO: should we return it to private? + public var color = SystemColors.label + + public var contentHeight: CGFloat { + return textView.frame.height + } + + public lazy var textView: UITextView = { + let view = UITextView() + view.isUserInteractionEnabled = false + view.backgroundColor = .clear + view.isScrollEnabled = false + return view + }() + + override open func configure() { + super.configure() + color = tintColor + addSubview(textView) + } + + override open func updateWithDescriptor(event: EventDescriptor) { + if let attributedText = event.attributedText { + textView.attributedText = attributedText + } else { + textView.text = event.text + textView.textColor = event.textColor + textView.font = event.font } - return super.hitTest(point, with: event) + backgroundColor = event.backgroundColor + color = event.color + drawsShadow = event.editedEvent != nil + + super.updateWithDescriptor(event: event) } override open func draw(_ rect: CGRect) { @@ -123,17 +151,6 @@ open class EventView: UIView { textFrame.size.height += frame.minY; textView.frame = textFrame; } - let first = eventResizeHandles.first - let last = eventResizeHandles.last - let radius: CGFloat = 40 - let yPad: CGFloat = -radius / 2 - let width = bounds.width - let height = bounds.height - let size = CGSize(width: radius, height: radius) - first?.frame = CGRect(origin: CGPoint(x: width - radius - layoutMargins.right, y: yPad), - size: size) - last?.frame = CGRect(origin: CGPoint(x: layoutMargins.left, y: height - yPad - radius), - size: size) if drawsShadow { applySketchShadow(alpha: 0.13, diff --git a/Source/Timeline/TimelinePagerView.swift b/Source/Timeline/TimelinePagerView.swift index f0a68d92..eb3e497f 100644 --- a/Source/Timeline/TimelinePagerView.swift +++ b/Source/Timeline/TimelinePagerView.swift @@ -19,6 +19,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr public weak var dataSource: EventDataSource? public weak var delegate: TimelinePagerViewDelegate? + public weak var timelineViewAppearance: TimelineViewAppearance? public private(set) var calendar: Calendar = Calendar.autoupdatingCurrent @@ -135,6 +136,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr let timeline = controller.timeline timeline.longPressGestureRecognizer.addTarget(self, action: #selector(timelineDidLongPress(_:))) timeline.delegate = self + timeline.appearance = timelineViewAppearance timeline.calendar = calendar timeline.date = date.dateOnly(calendar: calendar) controller.container.delegate = self @@ -178,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) } @@ -199,7 +202,8 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr /// - Parameter event: the EventDescriptor based on which an EventView will be placed on the Timeline /// - Parameter animated: if true, CalendarKit animates event creation public func create(event: EventDescriptor, animated: Bool) { - let eventView = EventView() + // TODO: remove ! `from currentTimeline!.timeline` + let eventView = timelineViewAppearance?.timelineView(currentTimeline!.timeline, viewFor: event) ?? DefaultEventView() // TODO: default appearance eventView.updateWithDescriptor(event: event) addSubview(eventView) // layout algo diff --git a/Source/Timeline/TimelineView.swift b/Source/Timeline/TimelineView.swift index ed29ff57..7fc6aa95 100644 --- a/Source/Timeline/TimelineView.swift +++ b/Source/Timeline/TimelineView.swift @@ -9,8 +9,13 @@ public protocol TimelineViewDelegate: AnyObject { func timelineView(_ timelineView: TimelineView, didLongPress event: EventView) } +public protocol TimelineViewAppearance: AnyObject { + func timelineView(_ timeloneView: TimelineView, viewFor event: EventDescriptor) -> EventView +} + public final class TimelineView: UIView { public weak var delegate: TimelineViewDelegate? + public weak var appearance: TimelineViewAppearance? public var date = Date() { didSet { @@ -53,7 +58,29 @@ public final class TimelineView: UIView { return allDayLayoutAttributes + regularLayoutAttributes } } - private var pool = ReusePool() + + // EventDescriptor.Type -> array of reusable EventViews + private var storage = [String : Array]() + func enqueue(views: [EventView]) { + for v in views { + v.frame = .zero + let descriptorTypeName = String(reflecting: v.descriptor!) // TODO: Is it even possible to nil here? If so, where and what should we do with it? Request a view from the appearance? + if (storage[descriptorTypeName] == nil) { + storage[descriptorTypeName] = [EventView]() + } + storage[descriptorTypeName]!.append(v) + } + } + func dequeue(event: EventDescriptor) -> EventView { + let descriptorTypeName = String(reflecting: event) + guard storage[descriptorTypeName] != nil + && !storage[descriptorTypeName]!.isEmpty else { + let view = appearance?.timelineView(self, viewFor: event) ?? DefaultEventView() + view.updateWithDescriptor(event: event) // because eventDescriptor must be not nil when enqueu() will called for this view // TODO: I think here start a big problem + return view + } + return storage[descriptorTypeName]!.removeLast() + } public var firstEventYPosition: CGFloat? { let first = regularLayoutAttributes.sorted{$0.frame.origin.y < $1.frame.origin.y}.first @@ -438,10 +465,10 @@ public final class TimelineView: UIView { } private func prepareEventViews() { - pool.enqueue(views: eventViews) + enqueue(views: eventViews) eventViews.removeAll() - for _ in regularLayoutAttributes { - let newView = pool.dequeue() + for attr in regularLayoutAttributes { + let newView = dequeue(event: attr.descriptor) if newView.superview == nil { addSubview(newView) } @@ -450,7 +477,7 @@ public final class TimelineView: UIView { } public func prepareForReuse() { - pool.enqueue(views: eventViews) + enqueue(views: eventViews) eventViews.removeAll() setNeedsDisplay() }