From 2ffdcec15cf2cfca933686c42784c0953b371dd6 Mon Sep 17 00:00:00 2001 From: Igor Verebei Date: Mon, 26 Sep 2022 13:59:25 +0300 Subject: [PATCH 01/38] Injection tableView to CalendarKit supporting --- Sources/CalendarStyle.swift | 12 ++++---- Sources/DayView.swift | 35 ++++++++++++++++++---- Sources/DayViewController.swift | 1 + Sources/Header/DaySelector/DateLabel.swift | 2 ++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Sources/CalendarStyle.swift b/Sources/CalendarStyle.swift index 73ffad76..bb72127e 100644 --- a/Sources/CalendarStyle.swift +++ b/Sources/CalendarStyle.swift @@ -28,16 +28,18 @@ public struct DayHeaderStyle { } public struct DaySelectorStyle { - public var activeTextColor = SystemColors.systemBackground - public var selectedBackgroundColor = SystemColors.label - + // TODO: Use colors from WPX palette + public var activeTextColor = UIColor.white + public var selectedBackgroundColor = UIColor.systemBlue + public var borderColor = UIColor.systemBlue + public var weekendTextColor = SystemColors.secondaryLabel public var inactiveTextColor = SystemColors.label public var inactiveBackgroundColor = UIColor.clear - public var todayInactiveTextColor = SystemColors.systemRed + public var todayInactiveTextColor = SystemColors.label public var todayActiveTextColor = UIColor.white - public var todayActiveBackgroundColor = SystemColors.systemRed + public var todayActiveBackgroundColor = SystemColors.systemBlue public var font = UIFont.systemFont(ofSize: 18) public var todayFont = UIFont.boldSystemFont(ofSize: 18) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 7177a2ab..da1c037b 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -52,6 +52,11 @@ public class DayView: UIView, TimelinePagerViewDelegate { public let dayHeaderView: DayHeaderView public let timelinePagerView: TimelinePagerView + public var tableView: UITableView? { + didSet { + self.configureTableView() + } + } public var state: DayViewState? { didSet { @@ -61,7 +66,7 @@ public class DayView: UIView, TimelinePagerViewDelegate { } public var calendar: Calendar = Calendar.autoupdatingCurrent - + public var eventEditingSnappingBehavior: EventEditingSnappingBehavior { get { timelinePagerView.eventEditingSnappingBehavior @@ -94,6 +99,21 @@ public class DayView: UIView, TimelinePagerViewDelegate { super.init(coder: aDecoder) configure() } + + private func configureTableView() { + if #available(iOS 11.0, *) { + guard let tableView = tableView else { return } + tableView.translatesAutoresizingMaskIntoConstraints = false + addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), + tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + + tableView.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + } + } private func configure() { addSubview(timelinePagerView) @@ -120,10 +140,15 @@ public class DayView: UIView, TimelinePagerViewDelegate { heightConstraint.priority = .defaultLow heightConstraint.isActive = true - timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true - timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true - timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true - timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true +// tableView?.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true +// tableView?.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true +// tableView?.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true +// tableView?.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + +// timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true +// timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true +// timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true +// timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true } } diff --git a/Sources/DayViewController.swift b/Sources/DayViewController.swift index d7fd725c..432c67d7 100644 --- a/Sources/DayViewController.swift +++ b/Sources/DayViewController.swift @@ -1,4 +1,5 @@ import UIKit +//@_exported import MailRiver open class DayViewController: UIViewController, EventDataSource, DayViewDelegate { public lazy var dayView: DayView = DayView() diff --git a/Sources/Header/DaySelector/DateLabel.swift b/Sources/Header/DaySelector/DateLabel.swift index af46ace0..fd1a1984 100644 --- a/Sources/Header/DaySelector/DateLabel.swift +++ b/Sources/Header/DaySelector/DateLabel.swift @@ -63,6 +63,8 @@ public final class DateLabel: UILabel, DaySelectorItemProtocol { font = style.font textColor = today ? style.todayInactiveTextColor : notTodayColor backgroundColor = style.inactiveBackgroundColor + layer.borderWidth = today ? 1 : 0 + layer.borderColor = style.borderColor.cgColor } } From 307f4bee90f1b111d4a6b5ae7a58977077885728 Mon Sep 17 00:00:00 2001 From: Igor Verebei Date: Tue, 27 Sep 2022 14:05:33 +0300 Subject: [PATCH 02/38] Added delegate methods to check calendar date did set after scroll --- Sources/DayView.swift | 14 +++++++++++++- Sources/DayViewController.swift | 6 ++++++ Sources/Header/DayHeaderView.swift | 9 +++++++++ Sources/Header/DaySelector/DateLabel.swift | 2 +- Sources/Header/SwipeLabelView.swift | 5 +++-- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index da1c037b..64e291c0 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -10,9 +10,11 @@ public protocol DayViewDelegate: AnyObject { func dayView(dayView: DayView, willMoveTo date: Date) func dayView(dayView: DayView, didMoveTo date: Date) func dayView(dayView: DayView, didUpdate event: EventDescriptor) + func didTapOnDate(date: Date) + func didMoveHeaderViewToDate(date: Date) } -public class DayView: UIView, TimelinePagerViewDelegate { +public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public weak var dataSource: EventDataSource? { get { timelinePagerView.dataSource @@ -120,6 +122,7 @@ public class DayView: UIView, TimelinePagerViewDelegate { addSubview(dayHeaderView) configureLayout() timelinePagerView.delegate = self + dayHeaderView.delegate = self if state == nil { let newState = DayViewState(date: Date(), calendar: calendar) @@ -235,4 +238,13 @@ public class DayView: UIView, TimelinePagerViewDelegate { public func timelinePager(timelinePager: TimelinePagerView, didUpdate event: EventDescriptor) { delegate?.dayView(dayView: self, didUpdate: event) } + + // MARK: - DayViewDelegate + public func didTapOpDate(date: Date) { + delegate?.didTapOnDate(date: date) + } + + public func didMoveHeaderViewToDate(date: Date) { + delegate?.didMoveHeaderViewToDate(date: date) + } } diff --git a/Sources/DayViewController.swift b/Sources/DayViewController.swift index 432c67d7..39f66b03 100644 --- a/Sources/DayViewController.swift +++ b/Sources/DayViewController.swift @@ -112,6 +112,12 @@ open class DayViewController: UIViewController, EventDataSource, DayViewDelegate open func dayView(dayView: DayView, didUpdate event: EventDescriptor) { } + + open func didTapOnDate(date: Date) { + } + + open func didMoveHeaderViewToDate(date: Date) { + } // MARK: - Editing diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index 31bc78de..12641254 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -1,9 +1,16 @@ import UIKit +public protocol DayHeaderViewDelegate: AnyObject { + func didTapOpDate(date: Date) + func didMoveHeaderViewToDate(date: Date) +} + public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdating, UIPageViewControllerDataSource, UIPageViewControllerDelegate { public private(set) var daysInWeek = 7 public let calendar: Calendar + public var delegate: DayHeaderViewDelegate? + private var style = DayHeaderStyle() private var currentSizeClass = UIUserInterfaceSizeClass.compact @@ -126,6 +133,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat public func dateSelectorDidSelectDate(_ date: Date) { state?.move(to: date) + delegate?.didTapOpDate(date: date) } // MARK: DayViewStateUpdating @@ -190,6 +198,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat selector.selectedIndex = currentWeekdayIndex if let selectedDate = selector.selectedDate { state?.client(client: self, didMoveTo: selectedDate) + delegate?.didMoveHeaderViewToDate(date: selectedDate) } } // Deselect all the views but the currently visible one diff --git a/Sources/Header/DaySelector/DateLabel.swift b/Sources/Header/DaySelector/DateLabel.swift index fd1a1984..17922196 100644 --- a/Sources/Header/DaySelector/DateLabel.swift +++ b/Sources/Header/DaySelector/DateLabel.swift @@ -82,7 +82,7 @@ public final class DateLabel: UILabel, DaySelectorItemProtocol { private func animate(){ UIView.transition(with: self, - duration: 0.4, + duration: 0.07, options: .transitionCrossDissolve, animations: { self.updateState() diff --git a/Sources/Header/SwipeLabelView.swift b/Sources/Header/SwipeLabelView.swift index ec53bf33..45614969 100644 --- a/Sources/Header/SwipeLabelView.swift +++ b/Sources/Header/SwipeLabelView.swift @@ -85,7 +85,8 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { secondLabel.frame = bounds secondLabel.frame.origin.x -= CGFloat(shiftRatio * screenWidth * 3) * multiplier - UIView.animate(withDuration: 0.3, animations: { +// UIView.animate(withDuration: 0.3, animations: { + UIView.animate(withDuration: 0, animations: { self.secondLabel.frame = self.bounds self.firstLabel.frame.origin.x += CGFloat(shiftRatio * screenWidth) * multiplier self.secondLabel.alpha = 1 @@ -120,7 +121,7 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { private func formattedDate(date: Date) -> String { let timezone = calendar.timeZone let formatter = DateFormatter() - formatter.dateStyle = .full + formatter.dateStyle = .medium formatter.timeStyle = .none formatter.timeZone = timezone formatter.locale = Locale.init(identifier: Locale.preferredLanguages[0]) From 6671fcc7254852ff46e43364e0f578ec31c4bf68 Mon Sep 17 00:00:00 2001 From: Igor Verebei Date: Tue, 27 Sep 2022 17:02:26 +0300 Subject: [PATCH 03/38] Added horizontal table spacing --- Sources/DayView.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 64e291c0..6bdf39d0 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -35,6 +35,12 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { configureLayout() } } + + public var horizontalSpacing: CGFloat = 0 { + didSet { + configureTableView() + } + } public var timelineScrollOffset: CGPoint { timelinePagerView.timelineScrollOffset @@ -103,14 +109,15 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { } private func configureTableView() { + tableView?.removeFromSuperview() if #available(iOS 11.0, *) { guard let tableView = tableView else { return } tableView.translatesAutoresizingMaskIntoConstraints = false addSubview(tableView) NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), - tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), + tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing), tableView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) @@ -143,11 +150,6 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { heightConstraint.priority = .defaultLow heightConstraint.isActive = true -// tableView?.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true -// tableView?.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true -// tableView?.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true -// tableView?.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - // timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true // timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true // timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true From 7aaf305b9a827da1ac09bd72efe5530dbfe7b647 Mon Sep 17 00:00:00 2001 From: Igor Verebei Date: Wed, 28 Sep 2022 16:25:10 +0300 Subject: [PATCH 04/38] =?UTF-8?q?=D0=90=D0=B4=D0=B0=D0=BF=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=B0=D1=82=20=D0=BF=D0=BE=D0=B4=20iPad=20(=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D1=87=D0=BD=D0=BE=D0=B5=20iPhone)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Package.swift | 2 +- Sources/DayView.swift | 49 ++++++++------------ Sources/Header/DayHeaderView.swift | 2 +- Sources/Header/DaySelector/DayDateCell.swift | 14 +++--- 4 files changed, 30 insertions(+), 37 deletions(-) diff --git a/Package.swift b/Package.swift index fc9aba6f..db29e572 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ let package = Package( name: "CalendarKit", defaultLocalization: "en", platforms: [ - .iOS(.v10), + .iOS(.v13), ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 6bdf39d0..e0259e53 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -60,11 +60,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public let dayHeaderView: DayHeaderView public let timelinePagerView: TimelinePagerView - public var tableView: UITableView? { - didSet { - self.configureTableView() - } - } + public var tableView: UITableView? public var state: DayViewState? { didSet { @@ -110,18 +106,15 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { private func configureTableView() { tableView?.removeFromSuperview() - if #available(iOS 11.0, *) { - guard let tableView = tableView else { return } - tableView.translatesAutoresizingMaskIntoConstraints = false - addSubview(tableView) - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), - tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), - tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing), - - tableView.bottomAnchor.constraint(equalTo: bottomAnchor) - ]) - } + guard let tableView = tableView else { return } + tableView.translatesAutoresizingMaskIntoConstraints = false + addSubview(tableView) + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), + tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), + tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing), + tableView.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) } private func configure() { @@ -139,7 +132,6 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { } private func configureLayout() { - if #available(iOS 11.0, *) { dayHeaderView.translatesAutoresizingMaskIntoConstraints = false timelinePagerView.translatesAutoresizingMaskIntoConstraints = false @@ -154,7 +146,6 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { // timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true // timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true // timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - } } public func updateStyle(_ newStyle: CalendarStyle) { @@ -183,16 +174,16 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { state?.move(to: date) } - override public func layoutSubviews() { - super.layoutSubviews() - if #available(iOS 11, *) {} else { - dayHeaderView.frame = CGRect(origin: CGPoint(x: 0, y: layoutMargins.top), - size: CGSize(width: bounds.width, height: headerHeight)) - let timelinePagerHeight = bounds.height - dayHeaderView.frame.maxY - timelinePagerView.frame = CGRect(origin: CGPoint(x: 0, y: dayHeaderView.frame.maxY), - size: CGSize(width: bounds.width, height: timelinePagerHeight)) - } - } +// override public func layoutSubviews() { +// super.layoutSubviews() +// if #available(iOS 11, *) {} else { +// dayHeaderView.frame = CGRect(origin: CGPoint(x: 0, y: layoutMargins.top), +// size: CGSize(width: bounds.width, height: headerHeight)) +// let timelinePagerHeight = bounds.height - dayHeaderView.frame.maxY +// timelinePagerView.frame = CGRect(origin: CGPoint(x: 0, y: dayHeaderView.frame.maxY), +// size: CGSize(width: bounds.width, height: timelinePagerHeight)) +// } +// } public func transitionToHorizontalSizeClass(_ sizeClass: UIUserInterfaceSizeClass) { dayHeaderView.transitionToHorizontalSizeClass(sizeClass) diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index 12641254..7584b1ce 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -125,7 +125,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat public func transitionToHorizontalSizeClass(_ sizeClass: UIUserInterfaceSizeClass) { currentSizeClass = sizeClass - daySymbolsView.isHidden = sizeClass == .regular +// daySymbolsView.isHidden = sizeClass == .regular (pagingViewController.children as? [DaySelectorController])?.forEach{$0.transitionToHorizontalSizeClass(sizeClass)} } diff --git a/Sources/Header/DaySelector/DayDateCell.swift b/Sources/Header/DaySelector/DayDateCell.swift index 3bde62b6..a6a5d4ef 100644 --- a/Sources/Header/DaySelector/DayDateCell.swift +++ b/Sources/Header/DaySelector/DayDateCell.swift @@ -49,7 +49,8 @@ public final class DayDateCell: UIView, DaySelectorItemProtocol { private func configure() { clipsToBounds = true - [dayLabel, dateLabel].forEach(addSubview(_:)) +// [dayLabel, dateLabel].forEach(addSubview(_:)) + addSubview(dateLabel) } public func updateStyle(_ newStyle: DaySelectorStyle) { @@ -90,17 +91,18 @@ public final class DayDateCell: UIView, DaySelectorItemProtocol { override public func layoutSubviews() { super.layoutSubviews() - dayLabel.sizeToFit() - dayLabel.center.y = center.y +// dayLabel.sizeToFit() +// dayLabel.center.y = center.y let interItemSpacing: CGFloat = selected ? 5 : 3 dateLabel.center.y = center.y - dateLabel.frame.origin.x = dayLabel.frame.maxX + interItemSpacing +// dateLabel.frame.origin.x = dayLabel.frame.maxX + interItemSpacing + dateLabel.center.x = self.center.x dateLabel.frame.size = CGSize(width: 30, height: 30) let freeSpace = bounds.width - (dateLabel.frame.origin.x + dateLabel.frame.width) let padding = freeSpace / 2 - [dayLabel, dateLabel].forEach { (label) in - label.frame.origin.x += padding + [dateLabel].forEach { (label) in + label.center.x = bounds.midX } } override public func tintColorDidChange() { From 4de349493c2638fab11eca6d81a918a70e1652fc Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 29 Sep 2022 17:25:00 +0300 Subject: [PATCH 05/38] Add selected date --- Sources/Header/DayHeaderView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index 7584b1ce..4f717f31 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -8,6 +8,7 @@ public protocol DayHeaderViewDelegate: AnyObject { public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdating, UIPageViewControllerDataSource, UIPageViewControllerDelegate { public private(set) var daysInWeek = 7 public let calendar: Calendar + public var selectedDate: Date? public var delegate: DayHeaderViewDelegate? @@ -134,13 +135,14 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat public func dateSelectorDidSelectDate(_ date: Date) { state?.move(to: date) delegate?.didTapOpDate(date: date) + selectedDate = date } // MARK: DayViewStateUpdating public func move(from oldDate: Date, to newDate: Date) { let newDate = newDate.dateOnly(calendar: calendar) - + selectedDate = newDate let centerView = pagingViewController.viewControllers![0] as! DaySelectorController let startDate = centerView.startDate.dateOnly(calendar: calendar) From f92656dfee5fe31e15e3e232318c8ced751a1cb4 Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 29 Sep 2022 23:17:26 +0300 Subject: [PATCH 06/38] Remove unnecessary code for layoutSubviews --- Sources/DayView.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index e0259e53..e51b052c 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -174,17 +174,6 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { state?.move(to: date) } -// override public func layoutSubviews() { -// super.layoutSubviews() -// if #available(iOS 11, *) {} else { -// dayHeaderView.frame = CGRect(origin: CGPoint(x: 0, y: layoutMargins.top), -// size: CGSize(width: bounds.width, height: headerHeight)) -// let timelinePagerHeight = bounds.height - dayHeaderView.frame.maxY -// timelinePagerView.frame = CGRect(origin: CGPoint(x: 0, y: dayHeaderView.frame.maxY), -// size: CGSize(width: bounds.width, height: timelinePagerHeight)) -// } -// } - public func transitionToHorizontalSizeClass(_ sizeClass: UIUserInterfaceSizeClass) { dayHeaderView.transitionToHorizontalSizeClass(sizeClass) updateStyle(style) From 61e0ad475a288042fd6bbb52ee7c1459cc275d65 Mon Sep 17 00:00:00 2001 From: igover Date: Fri, 30 Sep 2022 16:33:40 +0300 Subject: [PATCH 07/38] Fix layout for custom tableView --- Sources/DayView.swift | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index e51b052c..3097f728 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -38,7 +38,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public var horizontalSpacing: CGFloat = 0 { didSet { - configureTableView() + layoutTableView() } } @@ -60,7 +60,11 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public let dayHeaderView: DayHeaderView public let timelinePagerView: TimelinePagerView - public var tableView: UITableView? + public var tableView: UITableView? { + didSet { + addTableView() + } + } public var state: DayViewState? { didSet { @@ -104,11 +108,15 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { configure() } - private func configureTableView() { - tableView?.removeFromSuperview() + private func addTableView() { guard let tableView = tableView else { return } - tableView.translatesAutoresizingMaskIntoConstraints = false addSubview(tableView) + } + + // TODO: Create global constaints for tableView and set here + private func layoutTableView() { + guard let tableView = tableView else { return } + tableView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), @@ -141,7 +149,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { let heightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: headerHeight) heightConstraint.priority = .defaultLow heightConstraint.isActive = true - + // timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true // timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true // timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true From c0e10646d6938150bce74c9f6761a63873c267bf Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 3 Oct 2022 11:57:44 +0300 Subject: [PATCH 08/38] Code cleaning --- Sources/DayView.swift | 2 +- Sources/Header/DayHeaderView.swift | 7 ++----- Sources/Header/DaySelector/DayDateCell.swift | 4 ---- Sources/Header/SwipeLabelView.swift | 1 - 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 3097f728..ef10aa28 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -46,7 +46,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { timelinePagerView.timelineScrollOffset } - private static let headerVisibleHeight: CGFloat = 88 + private static let headerVisibleHeight: CGFloat = 68 public var headerHeight: CGFloat = headerVisibleHeight public var autoScrollToFirstEvent: Bool { diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index 4f717f31..b1d9f45c 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -58,7 +58,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat } private func configure() { - [daySymbolsView, swipeLabelView, separator].forEach(addSubview) + [daySymbolsView, separator].forEach(addSubview) backgroundColor = style.backgroundColor configurePagingViewController() } @@ -116,9 +116,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat size: CGSize(width: bounds.width, height: daySymbolsViewHeight)) pagingViewController.view?.frame = CGRect(origin: CGPoint(x: 0, y: daySymbolsViewHeight), size: CGSize(width: bounds.width, height: pagingScrollViewHeight)) - swipeLabelView.frame = CGRect(origin: CGPoint(x: 0, y: bounds.height - 10 - swipeLabelViewHeight), - size: CGSize(width: bounds.width, height: swipeLabelViewHeight)) - + let separatorHeight = 1 / UIScreen.main.scale separator.frame = CGRect(origin: CGPoint(x: 0, y: bounds.height - separatorHeight), size: CGSize(width: bounds.width, height: separatorHeight)) @@ -126,7 +124,6 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat public func transitionToHorizontalSizeClass(_ sizeClass: UIUserInterfaceSizeClass) { currentSizeClass = sizeClass -// daySymbolsView.isHidden = sizeClass == .regular (pagingViewController.children as? [DaySelectorController])?.forEach{$0.transitionToHorizontalSizeClass(sizeClass)} } diff --git a/Sources/Header/DaySelector/DayDateCell.swift b/Sources/Header/DaySelector/DayDateCell.swift index a6a5d4ef..2ecdfd8c 100644 --- a/Sources/Header/DaySelector/DayDateCell.swift +++ b/Sources/Header/DaySelector/DayDateCell.swift @@ -49,7 +49,6 @@ public final class DayDateCell: UIView, DaySelectorItemProtocol { private func configure() { clipsToBounds = true -// [dayLabel, dateLabel].forEach(addSubview(_:)) addSubview(dateLabel) } @@ -91,11 +90,8 @@ public final class DayDateCell: UIView, DaySelectorItemProtocol { override public func layoutSubviews() { super.layoutSubviews() -// dayLabel.sizeToFit() -// dayLabel.center.y = center.y let interItemSpacing: CGFloat = selected ? 5 : 3 dateLabel.center.y = center.y -// dateLabel.frame.origin.x = dayLabel.frame.maxX + interItemSpacing dateLabel.center.x = self.center.x dateLabel.frame.size = CGSize(width: 30, height: 30) diff --git a/Sources/Header/SwipeLabelView.swift b/Sources/Header/SwipeLabelView.swift index 45614969..db940be4 100644 --- a/Sources/Header/SwipeLabelView.swift +++ b/Sources/Header/SwipeLabelView.swift @@ -85,7 +85,6 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { secondLabel.frame = bounds secondLabel.frame.origin.x -= CGFloat(shiftRatio * screenWidth * 3) * multiplier -// UIView.animate(withDuration: 0.3, animations: { UIView.animate(withDuration: 0, animations: { self.secondLabel.frame = self.bounds self.firstLabel.frame.origin.x += CGFloat(shiftRatio * screenWidth) * multiplier From 5cd2c6a992ff493aa12166f5c3d7fa10fc5bd128 Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 3 Oct 2022 14:58:47 +0300 Subject: [PATCH 09/38] Supporting switch between custom TableView and TimeLineView --- Sources/DayView.swift | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index ef10aa28..ebad5374 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -14,6 +14,11 @@ public protocol DayViewDelegate: AnyObject { func didMoveHeaderViewToDate(date: Date) } +public enum CalendarMode { + case agenda + case day +} + public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public weak var dataSource: EventDataSource? { get { @@ -25,6 +30,8 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { } public weak var delegate: DayViewDelegate? + + public var calendarMode: CalendarMode? /// Hides or shows header view public var isHeaderViewVisible = true { @@ -150,11 +157,18 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { heightConstraint.priority = .defaultLow heightConstraint.isActive = true -// timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true -// timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true -// timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true -// timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true + timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true + timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true + timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + timelinePagerView.isHidden = true } + + public func switchView() { + tableView?.isHidden.toggle() + timelinePagerView.isHidden.toggle() + calendarMode = timelinePagerView.isHidden ? .agenda : .day + } public func updateStyle(_ newStyle: CalendarStyle) { style = newStyle From a061cbecf8b5f3db34137f5146b0f9c20c597aa5 Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 3 Oct 2022 20:16:52 +0300 Subject: [PATCH 10/38] Remove DayViewController --- Sources/DayView.swift | 33 +++++--- Sources/DayViewController.swift | 135 -------------------------------- Sources/Localization.swift | 2 +- 3 files changed, 22 insertions(+), 148 deletions(-) delete mode 100644 Sources/DayViewController.swift diff --git a/Sources/DayView.swift b/Sources/DayView.swift index ebad5374..dbe7d2e1 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -1,8 +1,8 @@ import UIKit public protocol DayViewDelegate: AnyObject { - func dayViewDidSelectEventView(_ eventView: EventView) - func dayViewDidLongPressEventView(_ eventView: EventView) +// func dayViewDidSelectEventView(_ eventView: EventView) +// func dayViewDidLongPressEventView(_ eventView: EventView) func dayView(dayView: DayView, didTapTimelineAt date: Date) func dayView(dayView: DayView, didLongPressTimelineAt date: Date) func dayViewDidBeginDragging(dayView: DayView) @@ -43,9 +43,16 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { } } + public var isTimelineViewVisibleAtStart = true { + didSet { + timelinePagerView.isHidden = !isTimelineViewVisibleAtStart + } + } + public var horizontalSpacing: CGFloat = 0 { didSet { layoutTableView() + layoutTimelinePagerView() } } @@ -135,6 +142,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { private func configure() { addSubview(timelinePagerView) addSubview(dayHeaderView) + configureLayout() timelinePagerView.delegate = self dayHeaderView.delegate = self @@ -148,22 +156,23 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { private func configureLayout() { dayHeaderView.translatesAutoresizingMaskIntoConstraints = false - timelinePagerView.translatesAutoresizingMaskIntoConstraints = false - + dayHeaderView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true dayHeaderView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true dayHeaderView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor).isActive = true let heightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: headerHeight) heightConstraint.priority = .defaultLow heightConstraint.isActive = true - - timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true - timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true - timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true - timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - timelinePagerView.isHidden = true } + private func layoutTimelinePagerView() { + timelinePagerView.translatesAutoresizingMaskIntoConstraints = false + timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing).isActive = true + timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing).isActive = true + timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true + timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + public func switchView() { tableView?.isHidden.toggle() timelinePagerView.isHidden.toggle() @@ -216,10 +225,10 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { // MARK: TimelinePagerViewDelegate public func timelinePagerDidSelectEventView(_ eventView: EventView) { - delegate?.dayViewDidSelectEventView(eventView) +// delegate?.dayViewDidSelectEventView(eventView) } public func timelinePagerDidLongPressEventView(_ eventView: EventView) { - delegate?.dayViewDidLongPressEventView(eventView) +// delegate?.dayViewDidLongPressEventView(eventView) } public func timelinePagerDidBeginDragging(timelinePager: TimelinePagerView) { delegate?.dayViewDidBeginDragging(dayView: self) diff --git a/Sources/DayViewController.swift b/Sources/DayViewController.swift deleted file mode 100644 index 39f66b03..00000000 --- a/Sources/DayViewController.swift +++ /dev/null @@ -1,135 +0,0 @@ -import UIKit -//@_exported import MailRiver - -open class DayViewController: UIViewController, EventDataSource, DayViewDelegate { - public lazy var dayView: DayView = DayView() - public var dataSource: EventDataSource? { - get { - dayView.dataSource - } - set(value) { - dayView.dataSource = value - } - } - - public var delegate: DayViewDelegate? { - get { - dayView.delegate - } - set(value) { - dayView.delegate = value - } - } - - public var calendar = Calendar.autoupdatingCurrent { - didSet { - dayView.calendar = calendar - } - } - - public var eventEditingSnappingBehavior: EventEditingSnappingBehavior { - get { - dayView.eventEditingSnappingBehavior - } - set { - dayView.eventEditingSnappingBehavior = newValue - } - } - - open override func loadView() { - view = dayView - } - - override open func viewDidLoad() { - super.viewDidLoad() - edgesForExtendedLayout = [] - view.tintColor = SystemColors.systemRed - dataSource = self - delegate = self - dayView.reloadData() - - let sizeClass = traitCollection.horizontalSizeClass - configureDayViewLayoutForHorizontalSizeClass(sizeClass) - } - - open override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - dayView.scrollToFirstEventIfNeeded() - } - - open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { - super.willTransition(to: newCollection, with: coordinator) - configureDayViewLayoutForHorizontalSizeClass(newCollection.horizontalSizeClass) - } - - open func configureDayViewLayoutForHorizontalSizeClass(_ sizeClass: UIUserInterfaceSizeClass) { - dayView.transitionToHorizontalSizeClass(sizeClass) - } - - // MARK: - CalendarKit API - - open func move(to date: Date) { - dayView.move(to: date) - } - - open func reloadData() { - dayView.reloadData() - } - - open func updateStyle(_ newStyle: CalendarStyle) { - dayView.updateStyle(newStyle) - } - - open func eventsForDate(_ date: Date) -> [EventDescriptor] { - return [Event]() - } - - // MARK: - DayViewDelegate - - open func dayViewDidSelectEventView(_ eventView: EventView) { - } - - open func dayViewDidLongPressEventView(_ eventView: EventView) { - } - - open func dayView(dayView: DayView, didTapTimelineAt date: Date) { - } - - open func dayViewDidBeginDragging(dayView: DayView) { - } - - open func dayViewDidTransitionCancel(dayView: DayView) { - } - - open func dayView(dayView: DayView, willMoveTo date: Date) { - } - - open func dayView(dayView: DayView, didMoveTo date: Date) { - } - - open func dayView(dayView: DayView, didLongPressTimelineAt date: Date) { - } - - open func dayView(dayView: DayView, didUpdate event: EventDescriptor) { - } - - open func didTapOnDate(date: Date) { - } - - open func didMoveHeaderViewToDate(date: Date) { - } - - // MARK: - Editing - - open func create(event: EventDescriptor, animated: Bool = false) { - dayView.create(event: event, animated: animated) - } - - open func beginEditing(event: EventDescriptor, animated: Bool = false) { - dayView.beginEditing(event: event, animated: animated) - } - - open func endEventEditing() { - dayView.endEventEditing() - } -} diff --git a/Sources/Localization.swift b/Sources/Localization.swift index 2f52b67c..70e37f06 100644 --- a/Sources/Localization.swift +++ b/Sources/Localization.swift @@ -11,7 +11,7 @@ extension Bundle { Bundle.main.resourceURL, // Bundle should be present here when the package is linked into a framework. - Bundle(for: DayViewController.self).resourceURL, +// Bundle(for: DayViewController.self).resourceURL, // For command-line tools. Bundle.main.bundleURL, From 4db0d084793874fcfafe617d23b35dac4f916126 Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 3 Oct 2022 20:30:51 +0300 Subject: [PATCH 11/38] Add TODO for renaming --- Sources/DayView.swift | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index dbe7d2e1..e1d31fbc 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -1,6 +1,7 @@ import UIKit public protocol DayViewDelegate: AnyObject { + // TODO: UncommentAfter renaming EventView to AppointmentView // func dayViewDidSelectEventView(_ eventView: EventView) // func dayViewDidLongPressEventView(_ eventView: EventView) func dayView(dayView: DayView, didTapTimelineAt date: Date) @@ -126,18 +127,6 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { guard let tableView = tableView else { return } addSubview(tableView) } - - // TODO: Create global constaints for tableView and set here - private func layoutTableView() { - guard let tableView = tableView else { return } - tableView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), - tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), - tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing), - tableView.bottomAnchor.constraint(equalTo: bottomAnchor) - ]) - } private func configure() { addSubview(timelinePagerView) @@ -157,14 +146,29 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { private func configureLayout() { dayHeaderView.translatesAutoresizingMaskIntoConstraints = false - dayHeaderView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true - dayHeaderView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true - dayHeaderView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor).isActive = true let heightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: headerHeight) heightConstraint.priority = .defaultLow - heightConstraint.isActive = true + + NSLayoutConstraint.activate([ + dayHeaderView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + dayHeaderView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + dayHeaderView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + heightConstraint + ]) } + // TODO: Create global constaints for tableView and set here + private func layoutTableView() { + guard let tableView = tableView else { return } + tableView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), + tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), + tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing), + tableView.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + } + private func layoutTimelinePagerView() { timelinePagerView.translatesAutoresizingMaskIntoConstraints = false timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing).isActive = true @@ -225,9 +229,11 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { // MARK: TimelinePagerViewDelegate public func timelinePagerDidSelectEventView(_ eventView: EventView) { + // TODO: UncommentAfter renaming EventView to AppointmentView // delegate?.dayViewDidSelectEventView(eventView) } public func timelinePagerDidLongPressEventView(_ eventView: EventView) { + // TODO: UncommentAfter renaming EventView to AppointmentView // delegate?.dayViewDidLongPressEventView(eventView) } public func timelinePagerDidBeginDragging(timelinePager: TimelinePagerView) { From 915e71bd7d0cd2abfd5d8f3d5d11f299e16dc122 Mon Sep 17 00:00:00 2001 From: igover Date: Wed, 5 Oct 2022 16:25:15 +0300 Subject: [PATCH 12/38] Added UIStackView to show event info --- Sources/DayView.swift | 12 ++++--- Sources/Timeline/Event.swift | 2 ++ Sources/Timeline/EventDescriptor.swift | 1 + Sources/Timeline/EventView.swift | 49 +++++++++++++++++++++----- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index e1d31fbc..79e88786 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -161,6 +161,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { private func layoutTableView() { guard let tableView = tableView else { return } tableView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), @@ -171,10 +172,13 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { private func layoutTimelinePagerView() { timelinePagerView.translatesAutoresizingMaskIntoConstraints = false - timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing).isActive = true - timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing).isActive = true - timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor).isActive = true - timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + + NSLayoutConstraint.activate([ + timelinePagerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: horizontalSpacing), + timelinePagerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -horizontalSpacing), + timelinePagerView.topAnchor.constraint(equalTo: dayHeaderView.bottomAnchor), + timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) } public func switchView() { diff --git a/Sources/Timeline/Event.swift b/Sources/Timeline/Event.swift index d9cbc0d2..18b953d3 100644 --- a/Sources/Timeline/Event.swift +++ b/Sources/Timeline/Event.swift @@ -4,6 +4,7 @@ public final class Event: EventDescriptor { public var dateInterval = DateInterval() public var isAllDay = false public var text = "" + public var location = "" public var attributedText: NSAttributedString? public var lineBreakMode: NSLineBreakMode? public var color = SystemColors.systemBlue { @@ -28,6 +29,7 @@ public final class Event: EventDescriptor { cloned.dateInterval = dateInterval cloned.isAllDay = isAllDay cloned.text = text + cloned.location = location cloned.attributedText = attributedText cloned.lineBreakMode = lineBreakMode cloned.color = color diff --git a/Sources/Timeline/EventDescriptor.swift b/Sources/Timeline/EventDescriptor.swift index eefe0c69..36ba47cc 100644 --- a/Sources/Timeline/EventDescriptor.swift +++ b/Sources/Timeline/EventDescriptor.swift @@ -5,6 +5,7 @@ public protocol EventDescriptor: AnyObject { var dateInterval: DateInterval {get set} var isAllDay: Bool {get} var text: String {get} + var location: String {get} var attributedText: NSAttributedString? {get} var lineBreakMode: NSLineBreakMode? {get} var font : UIFont {get} diff --git a/Sources/Timeline/EventView.swift b/Sources/Timeline/EventView.swift index fc818271..f96c0240 100644 --- a/Sources/Timeline/EventView.swift +++ b/Sources/Timeline/EventView.swift @@ -5,8 +5,40 @@ open class EventView: UIView { public var color = SystemColors.label public var contentHeight: CGFloat { - textView.frame.height + stackView.frame.height } + + public private(set) lazy var stackView: UIStackView = { + let view = UIStackView() + view.layoutMargins = UIEdgeInsets(top: 2, left: 1, bottom: 2, right: 0) + view.isLayoutMarginsRelativeArrangement = true + view.isUserInteractionEnabled = false + view.backgroundColor = .clear + view.axis = .vertical + view.spacing = 5 + return view + }() + + public private(set) lazy var subjectLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 17, weight: .medium) + label.lineBreakMode = .byTruncatingTail + label.textColor = .black + label.numberOfLines = 0 + return label + }() + + public private(set) lazy var locationLabel: UITextView = { + let label = UITextView() + label.font = UIFont.systemFont(ofSize: 15, weight: .medium) + label.textContainer.lineBreakMode = .byTruncatingTail + label.textContainer.lineFragmentPadding = 0.0 + label.textContainer.maximumNumberOfLines = 1 + label.textContainerInset = .zero + label.backgroundColor = .clear + label.textColor = .darkGray + return label + }() public private(set) lazy var textView: UITextView = { let view = UITextView() @@ -33,7 +65,9 @@ open class EventView: UIView { private func configure() { clipsToBounds = false color = tintColor - addSubview(textView) + stackView.addArrangedSubview(subjectLabel) + stackView.addArrangedSubview(locationLabel) + addSubview(stackView) for (idx, handle) in eventResizeHandles.enumerated() { handle.tag = idx @@ -45,9 +79,8 @@ open class EventView: UIView { if let attributedText = event.attributedText { textView.attributedText = attributedText } else { - textView.text = event.text - textView.textColor = event.textColor - textView.font = event.font + subjectLabel.text = event.text + locationLabel.text = event.location } if let lineBreakMode = event.lineBreakMode { textView.textContainer.lineBreakMode = lineBreakMode @@ -119,7 +152,7 @@ open class EventView: UIView { override open func layoutSubviews() { super.layoutSubviews() - textView.frame = { + stackView.frame = { if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft { return CGRect(x: bounds.minX, y: bounds.minY, width: bounds.width - 3, height: bounds.height) } else { @@ -127,10 +160,10 @@ open class EventView: UIView { } }() if frame.minY < 0 { - var textFrame = textView.frame; + var textFrame = stackView.frame; textFrame.origin.y = frame.minY * -1; textFrame.size.height += frame.minY; - textView.frame = textFrame; + stackView.frame = textFrame; } let first = eventResizeHandles.first let last = eventResizeHandles.last From 658294e25efe539a69a4ce1d2295324f5d4bb831 Mon Sep 17 00:00:00 2001 From: igover Date: Wed, 5 Oct 2022 17:55:25 +0300 Subject: [PATCH 13/38] Fixed switching calendar view mode --- Sources/DayView.swift | 37 +++++++++++++++++++------------- Sources/Timeline/EventView.swift | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 79e88786..117c88a7 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -32,7 +32,11 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public weak var delegate: DayViewDelegate? - public var calendarMode: CalendarMode? + public var calendarMode: CalendarMode? { + didSet { + switchModeTo(calendarMode: calendarMode!) + } + } /// Hides or shows header view public var isHeaderViewVisible = true { @@ -43,13 +47,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { configureLayout() } } - - public var isTimelineViewVisibleAtStart = true { - didSet { - timelinePagerView.isHidden = !isTimelineViewVisibleAtStart - } - } - + public var horizontalSpacing: CGFloat = 0 { didSet { layoutTableView() @@ -141,8 +139,23 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { newState.move(to: Date()) state = newState } + + if calendarMode == nil { + switchModeTo(calendarMode: .agenda) + } } + private func switchModeTo(calendarMode: CalendarMode) { + switch calendarMode { + case .agenda: + tableView?.isHidden = false + timelinePagerView.isHidden = true + case .day: + tableView?.isHidden = true + timelinePagerView.isHidden = false + } + } + private func configureLayout() { dayHeaderView.translatesAutoresizingMaskIntoConstraints = false @@ -180,13 +193,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { timelinePagerView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } - - public func switchView() { - tableView?.isHidden.toggle() - timelinePagerView.isHidden.toggle() - calendarMode = timelinePagerView.isHidden ? .agenda : .day - } - + public func updateStyle(_ newStyle: CalendarStyle) { style = newStyle dayHeaderView.updateStyle(style.header) diff --git a/Sources/Timeline/EventView.swift b/Sources/Timeline/EventView.swift index f96c0240..8b09da8f 100644 --- a/Sources/Timeline/EventView.swift +++ b/Sources/Timeline/EventView.swift @@ -21,7 +21,7 @@ open class EventView: UIView { public private(set) lazy var subjectLabel: UILabel = { let label = UILabel() - label.font = UIFont.systemFont(ofSize: 17, weight: .medium) + label.font = UIFont.systemFont(ofSize: 16, weight: .medium) label.lineBreakMode = .byTruncatingTail label.textColor = .black label.numberOfLines = 0 From 83c2c40054afcf4d55993638d795f20d689b9078 Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 6 Oct 2022 10:18:35 +0300 Subject: [PATCH 14/38] Color palette changed --- Sources/CalendarStyle.swift | 5 ++--- Sources/Extensions/UIColor+SystemColors.swift | 6 ++++++ Sources/Timeline/EventView.swift | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/CalendarStyle.swift b/Sources/CalendarStyle.swift index bb72127e..6379e775 100644 --- a/Sources/CalendarStyle.swift +++ b/Sources/CalendarStyle.swift @@ -22,13 +22,12 @@ public struct DayHeaderStyle { public var daySymbols = DaySymbolsStyle() public var daySelector = DaySelectorStyle() public var swipeLabel = SwipeLabelStyle() - public var backgroundColor = SystemColors.systemBackground + public var backgroundColor = SystemColors.tertiarySystemBackground public var separatorColor = SystemColors.systemSeparator public init() {} } public struct DaySelectorStyle { - // TODO: Use colors from WPX palette public var activeTextColor = UIColor.white public var selectedBackgroundColor = UIColor.systemBlue public var borderColor = UIColor.systemBlue @@ -65,7 +64,7 @@ public struct TimelineStyle { public var timeIndicator = CurrentTimeIndicatorStyle() public var timeColor = SystemColors.secondaryLabel public var separatorColor = SystemColors.systemSeparator - public var backgroundColor = SystemColors.systemBackground + public var backgroundColor = SystemColors.tertiarySystemBackground public var font = UIFont.boldSystemFont(ofSize: 11) public var dateStyle : DateStyle = .system public var eventsWillOverlap: Bool = false diff --git a/Sources/Extensions/UIColor+SystemColors.swift b/Sources/Extensions/UIColor+SystemColors.swift index b4f7c057..bbe90da0 100644 --- a/Sources/Extensions/UIColor+SystemColors.swift +++ b/Sources/Extensions/UIColor+SystemColors.swift @@ -25,6 +25,12 @@ public enum SystemColors { } return UIColor(white: 247/255, alpha: 1) } + public static var tertiarySystemBackground: UIColor { + if #available(iOS 13, *) { + return .tertiarySystemBackground + } + return .white + } public static var systemRed: UIColor { if #available(iOS 13, *) { return .systemRed diff --git a/Sources/Timeline/EventView.swift b/Sources/Timeline/EventView.swift index 8b09da8f..9d8afc22 100644 --- a/Sources/Timeline/EventView.swift +++ b/Sources/Timeline/EventView.swift @@ -23,7 +23,7 @@ open class EventView: UIView { let label = UILabel() label.font = UIFont.systemFont(ofSize: 16, weight: .medium) label.lineBreakMode = .byTruncatingTail - label.textColor = .black + label.textColor = .label label.numberOfLines = 0 return label }() @@ -36,7 +36,7 @@ open class EventView: UIView { label.textContainer.maximumNumberOfLines = 1 label.textContainerInset = .zero label.backgroundColor = .clear - label.textColor = .darkGray + label.textColor = .secondaryLabel return label }() From ca572751fc6e0381a5d987f21c42c530f4895580 Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 6 Oct 2022 10:58:29 +0300 Subject: [PATCH 15/38] Renaming EventView to AppointmentView --- Sources/DayView.swift | 15 ++++++--------- Sources/Timeline/AllDayView.swift | 4 ++-- .../{EventView.swift => AppointmentView.swift} | 2 +- Sources/Timeline/TimelinePagerView.swift | 14 +++++++------- Sources/Timeline/TimelineView.swift | 10 +++++----- 5 files changed, 21 insertions(+), 24 deletions(-) rename Sources/Timeline/{EventView.swift => AppointmentView.swift} (99%) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 117c88a7..71ad00d6 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -1,9 +1,8 @@ import UIKit public protocol DayViewDelegate: AnyObject { - // TODO: UncommentAfter renaming EventView to AppointmentView -// func dayViewDidSelectEventView(_ eventView: EventView) -// func dayViewDidLongPressEventView(_ eventView: EventView) + func dayViewDidSelectEventView(_ eventView: AppointmentView) + func dayViewDidLongPressEventView(_ eventView: AppointmentView) func dayView(dayView: DayView, didTapTimelineAt date: Date) func dayView(dayView: DayView, didLongPressTimelineAt date: Date) func dayViewDidBeginDragging(dayView: DayView) @@ -239,13 +238,11 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { // MARK: TimelinePagerViewDelegate - public func timelinePagerDidSelectEventView(_ eventView: EventView) { - // TODO: UncommentAfter renaming EventView to AppointmentView -// delegate?.dayViewDidSelectEventView(eventView) + public func timelinePagerDidSelectEventView(_ eventView: AppointmentView) { + delegate?.dayViewDidSelectEventView(eventView) } - public func timelinePagerDidLongPressEventView(_ eventView: EventView) { - // TODO: UncommentAfter renaming EventView to AppointmentView -// delegate?.dayViewDidLongPressEventView(eventView) + public func timelinePagerDidLongPressEventView(_ eventView: AppointmentView) { + delegate?.dayViewDidLongPressEventView(eventView) } public func timelinePagerDidBeginDragging(timelinePager: TimelinePagerView) { delegate?.dayViewDidBeginDragging(dayView: self) diff --git a/Sources/Timeline/AllDayView.swift b/Sources/Timeline/AllDayView.swift index a06c1754..11dac8ed 100644 --- a/Sources/Timeline/AllDayView.swift +++ b/Sources/Timeline/AllDayView.swift @@ -12,7 +12,7 @@ public final class AllDayView: UIView { } } - public private(set) var eventViews = [EventView]() + public private(set) var eventViews = [AppointmentView]() private lazy var textLabel: UILabel = { let label = UILabel() @@ -148,7 +148,7 @@ public final class AllDayView: UIView { for (index, anEventDescriptor) in self.events.enumerated() { // create event - let eventView = EventView(frame: CGRect.zero) + let eventView = AppointmentView(frame: CGRect.zero) eventView.updateWithDescriptor(event: anEventDescriptor) eventView.heightAnchor.constraint(equalToConstant: allDayEventHeight).isActive = true diff --git a/Sources/Timeline/EventView.swift b/Sources/Timeline/AppointmentView.swift similarity index 99% rename from Sources/Timeline/EventView.swift rename to Sources/Timeline/AppointmentView.swift index 9d8afc22..de9cad6d 100644 --- a/Sources/Timeline/EventView.swift +++ b/Sources/Timeline/AppointmentView.swift @@ -1,6 +1,6 @@ import UIKit -open class EventView: UIView { +open class AppointmentView: UIView { public var descriptor: EventDescriptor? public var color = SystemColors.label diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index 0f895d5a..a13d7b0b 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -1,8 +1,8 @@ import UIKit public protocol TimelinePagerViewDelegate: AnyObject { - func timelinePagerDidSelectEventView(_ eventView: EventView) - func timelinePagerDidLongPressEventView(_ eventView: EventView) + func timelinePagerDidSelectEventView(_ eventView: AppointmentView) + func timelinePagerDidLongPressEventView(_ eventView: AppointmentView) func timelinePager(timelinePager: TimelinePagerView, didTapTimelineAt date: Date) func timelinePagerDidBeginDragging(timelinePager: TimelinePagerView) func timelinePagerDidTransitionCancel(timelinePager: TimelinePagerView) @@ -207,7 +207,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } /// Event view with editing mode active. Can be either edited or newly created event - private var editedEventView: EventView? + private var editedEventView: AppointmentView? /// The `EventDescriptor` that is being edited. It's editable copy is used by the `editedEventView` private var editedEvent: EventDescriptor? @@ -218,7 +218,7 @@ 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() + let eventView = AppointmentView() eventView.updateWithDescriptor(event: event) addSubview(eventView) // layout algo @@ -408,7 +408,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr return date } - private func update(descriptor: EventDescriptor, with eventView: EventView) { + private func update(descriptor: EventDescriptor, with eventView: AppointmentView) { if let currentTimeline = currentTimeline { let timeline = currentTimeline.timeline let eventFrame = eventView.frame @@ -515,11 +515,11 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr delegate?.timelinePager(timelinePager: self, didLongPressTimelineAt: date) } - public func timelineView(_ timelineView: TimelineView, didTap event: EventView) { + public func timelineView(_ timelineView: TimelineView, didTap event: AppointmentView) { delegate?.timelinePagerDidSelectEventView(event) } - public func timelineView(_ timelineView: TimelineView, didLongPress event: EventView) { + public func timelineView(_ timelineView: TimelineView, didLongPress event: AppointmentView) { delegate?.timelinePagerDidLongPressEventView(event) } } diff --git a/Sources/Timeline/TimelineView.swift b/Sources/Timeline/TimelineView.swift index b175ada1..a66d8b58 100644 --- a/Sources/Timeline/TimelineView.swift +++ b/Sources/Timeline/TimelineView.swift @@ -3,8 +3,8 @@ import UIKit public protocol TimelineViewDelegate: AnyObject { func timelineView(_ timelineView: TimelineView, didTapAt date: Date) func timelineView(_ timelineView: TimelineView, didLongPressAt date: Date) - func timelineView(_ timelineView: TimelineView, didTap event: EventView) - func timelineView(_ timelineView: TimelineView, didLongPress event: EventView) + func timelineView(_ timelineView: TimelineView, didTap event: AppointmentView) + func timelineView(_ timelineView: TimelineView, didLongPress event: AppointmentView) } public final class TimelineView: UIView { @@ -20,7 +20,7 @@ public final class TimelineView: UIView { Date() } - private var eventViews = [EventView]() + private var eventViews = [AppointmentView]() public private(set) var regularLayoutAttributes = [EventLayoutAttributes]() public private(set) var allDayLayoutAttributes = [EventLayoutAttributes]() @@ -51,7 +51,7 @@ public final class TimelineView: UIView { setNeedsLayout() } } - private var pool = ReusePool() + private var pool = ReusePool() public var firstEventYPosition: CGFloat? { let first = regularLayoutAttributes.sorted{$0.frame.origin.y < $1.frame.origin.y}.first @@ -191,7 +191,7 @@ public final class TimelineView: UIView { } } - private func findEventView(at point: CGPoint) -> EventView? { + private func findEventView(at point: CGPoint) -> AppointmentView? { for eventView in allDayView.eventViews { let frame = eventView.convert(eventView.bounds, to: self) if frame.contains(point) { From 78a018c2dee15df53480b726689fb18140e087d3 Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 6 Oct 2022 14:12:04 +0300 Subject: [PATCH 16/38] Added date label for dayMode (initial commit) --- Sources/CalendarStyle.swift | 2 +- Sources/DayView.swift | 2 +- Sources/Header/DayHeaderView.swift | 5 +- Sources/Header/SwipeLabelView.swift | 175 ++++++++++------------------ 4 files changed, 67 insertions(+), 117 deletions(-) diff --git a/Sources/CalendarStyle.swift b/Sources/CalendarStyle.swift index 6379e775..c764cf7e 100644 --- a/Sources/CalendarStyle.swift +++ b/Sources/CalendarStyle.swift @@ -55,7 +55,7 @@ public struct DaySymbolsStyle { public struct SwipeLabelStyle { public var textColor = SystemColors.label - public var font = UIFont.systemFont(ofSize: 15) + public var font = UIFont.systemFont(ofSize: 16, weight: .medium) public init() {} } diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 71ad00d6..08b4ac16 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -58,7 +58,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { timelinePagerView.timelineScrollOffset } - private static let headerVisibleHeight: CGFloat = 68 + private static let headerVisibleHeight: CGFloat = 88 // swipe view height 20 public var headerHeight: CGFloat = headerVisibleHeight public var autoScrollToFirstEvent: Bool { diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index b1d9f45c..63b572a8 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -58,7 +58,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat } private func configure() { - [daySymbolsView, separator].forEach(addSubview) + [daySymbolsView, swipeLabelView, separator].forEach(addSubview) backgroundColor = style.backgroundColor configurePagingViewController() } @@ -117,6 +117,9 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat pagingViewController.view?.frame = CGRect(origin: CGPoint(x: 0, y: daySymbolsViewHeight), size: CGSize(width: bounds.width, height: pagingScrollViewHeight)) + swipeLabelView.frame = CGRect(origin: CGPoint(x: 0, y: bounds.height - 10 - swipeLabelViewHeight), + size: CGSize(width: bounds.width, height: swipeLabelViewHeight)) + let separatorHeight = 1 / UIScreen.main.scale separator.frame = CGRect(origin: CGPoint(x: 0, y: bounds.height - separatorHeight), size: CGSize(width: bounds.width, height: separatorHeight)) diff --git a/Sources/Header/SwipeLabelView.swift b/Sources/Header/SwipeLabelView.swift index db940be4..079aa11a 100644 --- a/Sources/Header/SwipeLabelView.swift +++ b/Sources/Header/SwipeLabelView.swift @@ -1,129 +1,76 @@ import UIKit public final class SwipeLabelView: UIView, DayViewStateUpdating { - public enum AnimationDirection { - case Forward - case Backward - mutating func flip() { - switch self { - case .Forward: - self = .Backward - case .Backward: - self = .Forward + private lazy var dateLabel: UILabel = { + let label = UILabel() + return label + }() + + public private(set) var calendar = Calendar.autoupdatingCurrent + public weak var state: DayViewState? { + willSet(newValue) { + state?.unsubscribe(client: self) + } + didSet { + state?.subscribe(client: self) + updateLabelText() } } - } - - public private(set) var calendar = Calendar.autoupdatingCurrent - public weak var state: DayViewState? { - willSet(newValue) { - state?.unsubscribe(client: self) + + private func updateLabelText() { + dateLabel.text = formattedDate(date: state!.selectedDate) } - didSet { - state?.subscribe(client: self) - updateLabelText() + + private var style = SwipeLabelStyle() + + public init(calendar: Calendar = Calendar.autoupdatingCurrent) { + self.calendar = calendar + super.init(frame: .zero) + configure() } - } - - private func updateLabelText() { - labels.first!.text = formattedDate(date: state!.selectedDate) - } - - private var firstLabel: UILabel { - labels.first! - } - - private var secondLabel: UILabel { - labels.last! - } - - private var labels = [UILabel]() - - private var style = SwipeLabelStyle() - - public init(calendar: Calendar = Calendar.autoupdatingCurrent) { - self.calendar = calendar - super.init(frame: .zero) - configure() - } - - override public init(frame: CGRect) { - super.init(frame: frame) - configure() - } - - required public init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - configure() - } - - private func configure() { - for _ in 0...1 { - let label = UILabel() - label.textAlignment = .center - labels.append(label) - addSubview(label) + + override public init(frame: CGRect) { + super.init(frame: frame) + configure() } - updateStyle(style) - } - - public func updateStyle(_ newStyle: SwipeLabelStyle) { - style = newStyle - labels.forEach { label in - label.textColor = style.textColor - label.font = style.font + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + configure() } - } - - private func animate(_ direction: AnimationDirection) { - let multiplier: CGFloat = direction == .Forward ? -1 : 1 - let shiftRatio: CGFloat = 30/375 - let screenWidth = bounds.width - - secondLabel.alpha = 0 - secondLabel.frame = bounds - secondLabel.frame.origin.x -= CGFloat(shiftRatio * screenWidth * 3) * multiplier - - UIView.animate(withDuration: 0, animations: { - self.secondLabel.frame = self.bounds - self.firstLabel.frame.origin.x += CGFloat(shiftRatio * screenWidth) * multiplier - self.secondLabel.alpha = 1 - self.firstLabel.alpha = 0 - }, completion: { _ in - self.labels = self.labels.reversed() - }) - } - - override public func layoutSubviews() { - super.layoutSubviews() - subviews.forEach { subview in - subview.frame = bounds + + private func configure() { + addSubview(dateLabel) + dateLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + dateLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10), + dateLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5), + dateLabel.heightAnchor.constraint(equalToConstant: 17) + ]) + updateStyle(style) } - } - - // MARK: DayViewStateUpdating - - public func move(from oldDate: Date, to newDate: Date) { - guard newDate != oldDate - else { return } - labels.last!.text = formattedDate(date: newDate) - var direction: AnimationDirection = newDate > oldDate ? .Forward : .Backward + public func updateStyle(_ newStyle: SwipeLabelStyle) { + style = newStyle + dateLabel.textColor = style.textColor + dateLabel.font = style.font + } - let rightToLeft = UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft - if rightToLeft { direction.flip() } + // MARK: - DayViewStateUpdating + public func move(from oldDate: Date, to newDate: Date) { + guard newDate != oldDate + else { return } + dateLabel.text = formattedDate(date: newDate) + } - animate(direction) - } - - private func formattedDate(date: Date) -> String { - let timezone = calendar.timeZone - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeStyle = .none - formatter.timeZone = timezone - formatter.locale = Locale.init(identifier: Locale.preferredLanguages[0]) - return formatter.string(from: date) - } + private func formattedDate(date: Date) -> String { + let timezone = calendar.timeZone + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + formatter.timeZone = timezone + formatter.locale = Locale.init(identifier: Locale.preferredLanguages[0]) + return formatter.string(from: date) + } } From 919f8f1758220b7ba5d9c89f31a7e91b3ecefa1f Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 6 Oct 2022 15:51:12 +0300 Subject: [PATCH 17/38] Added date label for dayMode (implemented layout) --- Sources/DayView.swift | 39 ++++++++++++++++++------------ Sources/Header/DayHeaderView.swift | 9 +++++++ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 08b4ac16..889ff70c 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -46,7 +46,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { configureLayout() } } - + public var horizontalSpacing: CGFloat = 0 { didSet { layoutTableView() @@ -57,8 +57,12 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public var timelineScrollOffset: CGPoint { timelinePagerView.timelineScrollOffset } + + var agendaHeightConstraint: NSLayoutConstraint! + var dayHeightConstraint: NSLayoutConstraint! + - private static let headerVisibleHeight: CGFloat = 88 // swipe view height 20 + private static let headerVisibleHeight: CGFloat = 68 // swipe view height 20 public var headerHeight: CGFloat = headerVisibleHeight public var autoScrollToFirstEvent: Bool { @@ -129,6 +133,9 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { addSubview(timelinePagerView) addSubview(dayHeaderView) + agendaHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 68) + dayHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 88) + configureLayout() timelinePagerView.delegate = self dayHeaderView.delegate = self @@ -145,29 +152,31 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { } private func switchModeTo(calendarMode: CalendarMode) { + dayHeaderView.switchModeTo(calendarMode: calendarMode) switch calendarMode { case .agenda: tableView?.isHidden = false timelinePagerView.isHidden = true + NSLayoutConstraint.deactivate([dayHeightConstraint]) + NSLayoutConstraint.activate([agendaHeightConstraint]) case .day: tableView?.isHidden = true timelinePagerView.isHidden = false + NSLayoutConstraint.deactivate([agendaHeightConstraint]) + NSLayoutConstraint.activate([dayHeightConstraint]) } } - private func configureLayout() { - dayHeaderView.translatesAutoresizingMaskIntoConstraints = false - - let heightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: headerHeight) - heightConstraint.priority = .defaultLow - - NSLayoutConstraint.activate([ - dayHeaderView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - dayHeaderView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), - dayHeaderView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - heightConstraint - ]) - } + private func configureLayout() { + dayHeaderView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + dayHeaderView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + dayHeaderView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + dayHeaderView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + agendaHeightConstraint + ]) + } // TODO: Create global constaints for tableView and set here private func layoutTableView() { diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index 63b572a8..2d1be8ca 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -56,6 +56,15 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + public func switchModeTo(calendarMode: CalendarMode) { + switch calendarMode { + case .agenda: + swipeLabelView.isHidden = true + case .day: + swipeLabelView.isHidden = false + } + } private func configure() { [daySymbolsView, swipeLabelView, separator].forEach(addSubview) From 20168b9f990e7281105f753b3ee4e5f31d5a105c Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 6 Oct 2022 17:05:54 +0300 Subject: [PATCH 18/38] Added date formatter for date view --- Sources/Header/SwipeLabelView.swift | 89 ++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/Sources/Header/SwipeLabelView.swift b/Sources/Header/SwipeLabelView.swift index 079aa11a..bb7e1f4a 100644 --- a/Sources/Header/SwipeLabelView.swift +++ b/Sources/Header/SwipeLabelView.swift @@ -14,12 +14,27 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { } didSet { state?.subscribe(client: self) - updateLabelText() + updateLabelTextWith(date: state!.selectedDate) } } - private func updateLabelText() { - dateLabel.text = formattedDate(date: state!.selectedDate) + private func isToday(date: Date) -> Bool { + calendar.isDateInToday(date) + } + + private func updateLabelTextWith(date: Date) { + var textColor: UIColor? + if isToday(date: date) { + textColor = .systemBlue + } else if isDateInWeekend(date: date) { + textColor = UIColor.secondaryLabel + } else { + textColor = UIColor.label + } + + dateLabel.textColor = textColor + dateLabel.font = UIFont.systemFont(ofSize: 16, weight: isToday(date: date) ? .bold : .medium) + dateLabel.text = calendarDateStringWithoutCurrentYear(from: date) } private var style = SwipeLabelStyle() @@ -43,34 +58,66 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { private func configure() { addSubview(dateLabel) dateLabel.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - dateLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10), - dateLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5), - dateLabel.heightAnchor.constraint(equalToConstant: 17) - ]) + dateLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true + dateLabel.heightAnchor.constraint(equalToConstant: 17).isActive = true + + if isIPad() { + dateLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true + } else { + dateLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -11).isActive = true + } + updateStyle(style) } - public func updateStyle(_ newStyle: SwipeLabelStyle) { - style = newStyle - dateLabel.textColor = style.textColor - dateLabel.font = style.font - } + public func updateStyle(_ newStyle: SwipeLabelStyle) {} // MARK: - DayViewStateUpdating public func move(from oldDate: Date, to newDate: Date) { guard newDate != oldDate else { return } - dateLabel.text = formattedDate(date: newDate) + + updateLabelTextWith(date: newDate) } - private func formattedDate(date: Date) -> String { - let timezone = calendar.timeZone + // MARK: - DateFormatter + private let calendarDateFormatWithoutYear = "EEE, dd MMM" + + private func calendarDateStringWithoutCurrentYear(from date: Date) -> String? { + var dateFormatter = calendarDateFormatter() + if Date().currentYear() == date.year() { + dateFormatter = calendarDateFormatter(calendarDateFormatWithoutYear) + } + return dateFormatter.string(from: date) + } + + private func calendarDateFormatter(_ template: String = calendarDateFormat) -> DateFormatter { + let langCode = Locale.preferredLanguages.first + let locale = (langCode == nil) ? Locale.current : Locale(identifier: langCode!) let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeStyle = .none - formatter.timeZone = timezone - formatter.locale = Locale.init(identifier: Locale.preferredLanguages[0]) - return formatter.string(from: date) + formatter.timeZone = .current + formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: locale) + return formatter + } + + private func isDateInWeekend(date: Date) -> Bool { + let calendar = Calendar.current + return calendar.isDateInWeekend(date) + } +} + +public func isIPad() -> Bool { + return UIDevice.current.userInterfaceIdiom == .pad +} + +fileprivate let calendarDateFormat = "EEE, dd MMM yyyy" + +private extension Date { + func currentYear() -> Int { + return Calendar.current.component(.year, from: Date()) + } + + func year() -> Int { + return Calendar.current.component(.year, from: self) } } From f5aeeaaaebe5bddb283ffc7a9c8f41eb234e908c Mon Sep 17 00:00:00 2001 From: igover Date: Fri, 7 Oct 2022 13:20:14 +0300 Subject: [PATCH 19/38] Add separator for swipeLabelView --- Sources/DayView.swift | 2 +- Sources/Header/DayHeaderView.swift | 4 ++-- Sources/Header/SwipeLabelView.swift | 25 +++++++++++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 889ff70c..a314bdfd 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -134,7 +134,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { addSubview(dayHeaderView) agendaHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 68) - dayHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 88) + dayHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 100) configureLayout() timelinePagerView.delegate = self diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index 2d1be8ca..a2f99a41 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -29,7 +29,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat private var daySymbolsViewHeight: CGFloat = 20 private var pagingScrollViewHeight: CGFloat = 40 - private var swipeLabelViewHeight: CGFloat = 20 + private var swipeLabelViewHeight: CGFloat = 32 private let daySymbolsView: DaySymbolsView private var pagingViewController = UIPageViewController(transitionStyle: .scroll, @@ -126,7 +126,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat pagingViewController.view?.frame = CGRect(origin: CGPoint(x: 0, y: daySymbolsViewHeight), size: CGSize(width: bounds.width, height: pagingScrollViewHeight)) - swipeLabelView.frame = CGRect(origin: CGPoint(x: 0, y: bounds.height - 10 - swipeLabelViewHeight), + swipeLabelView.frame = CGRect(origin: CGPoint(x: 0, y: bounds.height - swipeLabelViewHeight), size: CGSize(width: bounds.width, height: swipeLabelViewHeight)) let separatorHeight = 1 / UIScreen.main.scale diff --git a/Sources/Header/SwipeLabelView.swift b/Sources/Header/SwipeLabelView.swift index bb7e1f4a..7e6bda27 100644 --- a/Sources/Header/SwipeLabelView.swift +++ b/Sources/Header/SwipeLabelView.swift @@ -4,9 +4,16 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { private lazy var dateLabel: UILabel = { let label = UILabel() + label.backgroundColor = .clear return label }() + private lazy var separator: UIView = { + let separator = UIView() + separator.backgroundColor = SystemColors.systemSeparator + return separator + }() + public private(set) var calendar = Calendar.autoupdatingCurrent public weak var state: DayViewState? { willSet(newValue) { @@ -57,20 +64,30 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { private func configure() { addSubview(dateLabel) + addSubview(separator) + + separator.translatesAutoresizingMaskIntoConstraints = false + separator.topAnchor.constraint(equalTo: topAnchor).isActive = true + separator.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + separator.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + separator.heightAnchor.constraint(equalToConstant: 0.5).isActive = true + dateLabel.translatesAutoresizingMaskIntoConstraints = false - dateLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true - dateLabel.heightAnchor.constraint(equalToConstant: 17).isActive = true + dateLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8).isActive = true + dateLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true if isIPad() { dateLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true } else { - dateLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -11).isActive = true + dateLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12).isActive = true } updateStyle(style) } - public func updateStyle(_ newStyle: SwipeLabelStyle) {} + public func updateStyle(_ newStyle: SwipeLabelStyle) { + backgroundColor = .tertiarySystemBackground + } // MARK: - DayViewStateUpdating public func move(from oldDate: Date, to newDate: Date) { From b4249600370dcf8c4eb2a36dda918df3c14b732d Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 10 Oct 2022 11:25:50 +0300 Subject: [PATCH 20/38] Code cleaning --- Sources/Extensions/Date+DateOnly.swift | 8 ++ Sources/Extensions/UIColor+SystemColors.swift | 92 +++++++------------ Sources/Header/SwipeLabelView.swift | 18 +--- 3 files changed, 47 insertions(+), 71 deletions(-) diff --git a/Sources/Extensions/Date+DateOnly.swift b/Sources/Extensions/Date+DateOnly.swift index 7f401eb4..7c042d5a 100644 --- a/Sources/Extensions/Date+DateOnly.swift +++ b/Sources/Extensions/Date+DateOnly.swift @@ -14,4 +14,12 @@ extension Date { let returnValue = calendar.date(from: newComponents) return returnValue! } + + func currentYear() -> Int { + return Calendar.current.component(.year, from: Date()) + } + + func year() -> Int { + return Calendar.current.component(.year, from: self) + } } diff --git a/Sources/Extensions/UIColor+SystemColors.swift b/Sources/Extensions/UIColor+SystemColors.swift index bbe90da0..0a03d645 100644 --- a/Sources/Extensions/UIColor+SystemColors.swift +++ b/Sources/Extensions/UIColor+SystemColors.swift @@ -1,62 +1,40 @@ import UIKit public enum SystemColors { - public static var label: UIColor { - if #available(iOS 13, *) { - return .label - } - return .black - } - public static var secondaryLabel: UIColor { - if #available(iOS 13, *) { - return .secondaryLabel - } - return .lightGray - } - public static var systemBackground: UIColor { - if #available(iOS 13, *) { - return .systemBackground - } - return .white - } - public static var secondarySystemBackground: UIColor { - if #available(iOS 13, *) { - return .secondarySystemBackground - } - return UIColor(white: 247/255, alpha: 1) - } + public static var label: UIColor { + return .label + } + + public static var secondaryLabel: UIColor { + return .secondaryLabel + } + + public static var systemBackground: UIColor { + return .systemBackground + } + + public static var secondarySystemBackground: UIColor { + return .secondarySystemBackground + } + public static var tertiarySystemBackground: UIColor { - if #available(iOS 13, *) { - return .tertiarySystemBackground - } - return .white - } - public static var systemRed: UIColor { - if #available(iOS 13, *) { - return .systemRed - } - return .red - } - public static var systemBlue: UIColor { - if #available(iOS 13, *) { - return .systemBlue - } - return .blue - } - public static var systemGray4: UIColor { - if #available(iOS 13, *) { - return .systemGray4 - } - return UIColor(red: 209/255, - green: 209/255, - blue: 213/255, alpha: 1) - } - public static var systemSeparator: UIColor { - if #available(iOS 13, *) { - return .opaqueSeparator - } - return UIColor(red: 198/255, - green: 198/255, - blue: 200/255, alpha: 1) - } + return .tertiarySystemBackground + } + + public static var systemRed: UIColor { + return .systemRed + } + + public static var systemBlue: UIColor { + return .systemBlue + } + + public static var systemGray4: UIColor { + return .systemGray4 + } + + public static var systemSeparator: UIColor { + return .opaqueSeparator + } + } diff --git a/Sources/Header/SwipeLabelView.swift b/Sources/Header/SwipeLabelView.swift index 7e6bda27..0fc98606 100644 --- a/Sources/Header/SwipeLabelView.swift +++ b/Sources/Header/SwipeLabelView.swift @@ -25,10 +25,6 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { } } - private func isToday(date: Date) -> Bool { - calendar.isDateInToday(date) - } - private func updateLabelTextWith(date: Date) { var textColor: UIColor? if isToday(date: date) { @@ -118,9 +114,12 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { } private func isDateInWeekend(date: Date) -> Bool { - let calendar = Calendar.current return calendar.isDateInWeekend(date) } + + private func isToday(date: Date) -> Bool { + calendar.isDateInToday(date) + } } public func isIPad() -> Bool { @@ -129,12 +128,3 @@ public func isIPad() -> Bool { fileprivate let calendarDateFormat = "EEE, dd MMM yyyy" -private extension Date { - func currentYear() -> Int { - return Calendar.current.component(.year, from: Date()) - } - - func year() -> Int { - return Calendar.current.component(.year, from: self) - } -} From f9e3ed08e7514facd98501621faae01b81755694 Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 10 Oct 2022 12:22:33 +0300 Subject: [PATCH 21/38] Fix header size --- Sources/DayView.swift | 3 +-- Sources/Header/DayHeaderView.swift | 2 +- Sources/Header/SwipeLabelView.swift | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index a314bdfd..240909c4 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -134,7 +134,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { addSubview(dayHeaderView) agendaHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 68) - dayHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 100) + dayHeightConstraint = dayHeaderView.heightAnchor.constraint(equalToConstant: 98) configureLayout() timelinePagerView.delegate = self @@ -178,7 +178,6 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { ]) } - // TODO: Create global constaints for tableView and set here private func layoutTableView() { guard let tableView = tableView else { return } tableView.translatesAutoresizingMaskIntoConstraints = false diff --git a/Sources/Header/DayHeaderView.swift b/Sources/Header/DayHeaderView.swift index a2f99a41..ece2985e 100644 --- a/Sources/Header/DayHeaderView.swift +++ b/Sources/Header/DayHeaderView.swift @@ -29,7 +29,7 @@ public final class DayHeaderView: UIView, DaySelectorDelegate, DayViewStateUpdat private var daySymbolsViewHeight: CGFloat = 20 private var pagingScrollViewHeight: CGFloat = 40 - private var swipeLabelViewHeight: CGFloat = 32 + private var swipeLabelViewHeight: CGFloat = 30 private let daySymbolsView: DaySymbolsView private var pagingViewController = UIPageViewController(transitionStyle: .scroll, diff --git a/Sources/Header/SwipeLabelView.swift b/Sources/Header/SwipeLabelView.swift index 0fc98606..b505a68b 100644 --- a/Sources/Header/SwipeLabelView.swift +++ b/Sources/Header/SwipeLabelView.swift @@ -69,7 +69,7 @@ public final class SwipeLabelView: UIView, DayViewStateUpdating { separator.heightAnchor.constraint(equalToConstant: 0.5).isActive = true dateLabel.translatesAutoresizingMaskIntoConstraints = false - dateLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8).isActive = true + dateLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6).isActive = true dateLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true if isIPad() { From 9d4a0f42f7e9577d4b3f191d26cb96fccfd4c5c0 Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 10 Oct 2022 14:45:30 +0300 Subject: [PATCH 22/38] Save calendar mode if needed --- Sources/DayView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 240909c4..b288e444 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -51,6 +51,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { didSet { layoutTableView() layoutTimelinePagerView() + switchModeTo(calendarMode: calendarMode ?? .agenda) } } From f0edfd7d8cf6208489678ce180a00812d1a386cb Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 10 Oct 2022 15:33:44 +0300 Subject: [PATCH 23/38] Change alpha for background AppointmentView --- Sources/Timeline/Event.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Timeline/Event.swift b/Sources/Timeline/Event.swift index 18b953d3..57b7cd4e 100644 --- a/Sources/Timeline/Event.swift +++ b/Sources/Timeline/Event.swift @@ -12,7 +12,7 @@ public final class Event: EventDescriptor { updateColors() } } - public var backgroundColor = SystemColors.systemBlue.withAlphaComponent(0.3) + public var backgroundColor = SystemColors.systemBlue.withAlphaComponent(0.4) public var textColor = SystemColors.label public var font = UIFont.boldSystemFont(ofSize: 12) public var userInfo: Any? From 5641f7cd6e91e8c0eab3c1e8d5976002b097708d Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 24 Oct 2022 16:13:20 +0300 Subject: [PATCH 24/38] Save current time position after swipe to next/previous day --- Sources/DayView.swift | 5 ++ Sources/Timeline/TimelineContainer.swift | 32 +++++++++---- .../TimelineContainerController.swift | 13 ++++- Sources/Timeline/TimelinePagerView.swift | 47 +++++++++++++++++-- Sources/Timeline/TimelineView.swift | 12 +++++ 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index b288e444..27c751d3 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -219,6 +219,10 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { public func scrollToFirstEventIfNeeded(animated: Bool = true) { timelinePagerView.scrollToFirstEventIfNeeded(animated: animated) } + + public func scrollToCurrentTime(animated: Bool = true) { + timelinePagerView.scrollToCurrentTime(animated: animated) + } public func reloadData() { timelinePagerView.reloadData() @@ -278,6 +282,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { // MARK: - DayViewDelegate public func didTapOpDate(date: Date) { delegate?.didTapOnDate(date: date) +// timelinePagerView.scrollToUserOffSet() } public func didMoveHeaderViewToDate(date: Date) { diff --git a/Sources/Timeline/TimelineContainer.swift b/Sources/Timeline/TimelineContainer.swift index 5f98e927..375362ee 100644 --- a/Sources/Timeline/TimelineContainer.swift +++ b/Sources/Timeline/TimelineContainer.swift @@ -12,21 +12,25 @@ public final class TimelineContainer: UIScrollView { required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + + public override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) { + super.setContentOffset(contentOffset, animated: animated) + print("___ self.contentOffset for", contentOffset) + } + override public func layoutSubviews() { super.layoutSubviews() timeline.frame = CGRect(x: 0, y: 0, width: bounds.width, height: timeline.fullHeight) + print("___ from layoutSubviews", contentOffset.y) + if contentOffset.y < 5 { + print() + } timeline.offsetAllDayView(by: contentOffset.y) - //adjust the scroll insets let allDayViewHeight = timeline.allDayViewHeight - let bottomSafeInset: CGFloat - if #available(iOS 11.0, *) { - bottomSafeInset = window?.safeAreaInsets.bottom ?? 0 - } else { - bottomSafeInset = 0 - } + let bottomSafeInset = window?.safeAreaInsets.bottom ?? 0 + scrollIndicatorInsets = UIEdgeInsets(top: allDayViewHeight, left: 0, bottom: bottomSafeInset, right: 0) contentInset = UIEdgeInsets(top: allDayViewHeight, left: 0, bottom: bottomSafeInset, right: 0) } @@ -42,6 +46,14 @@ public final class TimelineContainer: UIScrollView { setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll - padding), animated: animated) } } + + public func scroollToCurrentTime(animated: Bool) { + let allDayViewHeight = timeline.allDayViewHeight + let padding = allDayViewHeight + 200 + if let yToScroll = timeline.currentTimeYPosition { + setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll - padding), animated: animated) + } + } public func scrollTo(hour24: Float, animated: Bool = true) { let percentToScroll = CGFloat(hour24 / 24) @@ -49,6 +61,10 @@ public final class TimelineContainer: UIScrollView { let padding: CGFloat = 8 setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll - padding), animated: animated) } + + public func scrollTo(offSet: CGPoint, animated: Bool = false) { + setTimelineOffset(offSet, animated: animated) + } private func setTimelineOffset(_ offset: CGPoint, animated: Bool) { let yToScroll = offset.y diff --git a/Sources/Timeline/TimelineContainerController.swift b/Sources/Timeline/TimelineContainerController.swift index c1eb2fc7..8fffd85c 100644 --- a/Sources/Timeline/TimelineContainerController.swift +++ b/Sources/Timeline/TimelineContainerController.swift @@ -2,7 +2,7 @@ import UIKit public final class TimelineContainerController: UIViewController { /// Content Offset to be set once the view size has been calculated - public var pendingContentOffset: CGPoint? + public var pendingContentOffset: CGPoint? public private(set) lazy var timeline = TimelineView() public private(set) lazy var container: TimelineContainer = { @@ -13,12 +13,21 @@ public final class TimelineContainerController: UIViewController { public override func loadView() { view = container +// container.delegate = self } +// public func scrollViewDidScroll(_ scrollView: UIScrollView) { +// self.parent?.children.forEach({ vc in +// if let vc = vc as? TimelineContainerController, vc !== self { +// vc.container.setContentOffset(scrollView.contentOffset, animated: false) +// } +// }) +// } + public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() container.contentSize = timeline.frame.size - if let newOffset = pendingContentOffset { + if let newOffset = self.pendingContentOffset { // Apply new offset only once the size has been determined if view.bounds != .zero { container.setContentOffset(newOffset, animated: false) diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index a13d7b0b..e8d18b28 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -143,6 +143,12 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr controller.container.scrollTo(hour24: hour24, animated: animated) } } + + public func scrollToUserOffSet(animated: Bool = false) { + if let controller = currentTimeline { + controller.container.scrollTo(offSet: userContentOffset!, animated: animated) + } + } private func configureTimelineController(date: Date) -> TimelineContainerController { let controller = TimelineContainerController() @@ -205,6 +211,12 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } } } + + public func scrollToCurrentTime(animated: Bool) { + if let controller = currentTimeline { + controller.container.scroollToCurrentTime(animated: animated) + } + } /// Event view with editing mode active. Can be either edited or newly created event private var editedEventView: AppointmentView? @@ -466,21 +478,43 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } // MARK: UIPageViewControllerDataSource - + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { +// setContentOffsetForChildren(offset: scrollView.contentOffset) + } + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { +// setContentOffsetForChildren(offset: scrollView.contentOffset) + } + + private func setContentOffsetForChildren(offset: CGPoint) { + pagingViewController.children.forEach({ vc in + if let vc = vc as? TimelineContainerController { + vc.container.setContentOffset(offset, animated: false) + vc.container.setNeedsLayout() + print("___ . pendingOffSet did set for vc - ", offset) + } + }) + } + + public var userContentOffset: CGPoint? + public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { - guard let containerController = viewController as? TimelineContainerController else {return nil} + guard let containerController = viewController as? TimelineContainerController else { return nil } let previousDate = calendar.date(byAdding: .day, value: -1, to: containerController.timeline.date)! let timelineContainerController = configureTimelineController(date: previousDate) - let offset = (pageViewController.viewControllers?.first as? TimelineContainerController)?.container.contentOffset + let offset = containerController.container.contentOffset timelineContainerController.pendingContentOffset = offset return timelineContainerController } public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { - guard let containerController = viewController as? TimelineContainerController else {return nil} + guard let containerController = viewController as? TimelineContainerController else { return nil } let nextDate = calendar.date(byAdding: .day, value: 1, to: containerController.timeline.date)! let timelineContainerController = configureTimelineController(date: nextDate) - let offset = (pageViewController.viewControllers?.first as? TimelineContainerController)?.container.contentOffset + let offset = containerController.container.contentOffset timelineContainerController.pendingContentOffset = offset return timelineContainerController } @@ -494,6 +528,9 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } if let timelineContainerController = pageViewController.viewControllers?.first as? TimelineContainerController { let selectedDate = timelineContainerController.timeline.date +// if let userContentOffset = userContentOffset { +// scrollToUserOffSet() +// } delegate?.timelinePager(timelinePager: self, willMoveTo: selectedDate) state?.client(client: self, didMoveTo: selectedDate) scrollToFirstEventIfNeeded(animated: true) diff --git a/Sources/Timeline/TimelineView.swift b/Sources/Timeline/TimelineView.swift index a66d8b58..db8d340c 100644 --- a/Sources/Timeline/TimelineView.swift +++ b/Sources/Timeline/TimelineView.swift @@ -60,6 +60,15 @@ public final class TimelineView: UIView { let beginningOfDayPosition = dateToY(date) return max(firstEventPosition, beginningOfDayPosition) } + + public var currentTimeYPosition: CGFloat? { + if isToday { + let yPosition = nowLine.frame.origin.y + return yPosition + } else { + return nil + } + } private lazy var nowLine: CurrentTimeIndicator = CurrentTimeIndicator() @@ -160,7 +169,9 @@ public final class TimelineView: UIView { contentScaleFactor = 1 layer.contentsScale = 1 contentMode = .redraw +// let colors: [UIColor] = [.red, .blue, .link, .lightGray, .white, .green, .green, .brown, .magenta] backgroundColor = .white +// backgroundColor = colors.randomElement() addSubview(nowLine) // Add long press gesture recognizer @@ -428,6 +439,7 @@ public final class TimelineView: UIView { public func offsetAllDayView(by yValue: CGFloat) { if let topConstraint = self.allDayViewTopConstraint { topConstraint.constant = yValue + print("___ yValue", yValue) layoutIfNeeded() } } From c5de699788299500908d1073038e6484f01f0401 Mon Sep 17 00:00:00 2001 From: Igor Verebei Date: Mon, 24 Oct 2022 22:53:17 +0300 Subject: [PATCH 25/38] Save current time position (intermediate commit) --- Sources/Timeline/TimelineContainer.swift | 19 ++++++++++-- .../TimelineContainerController.swift | 23 +++++++++------ Sources/Timeline/TimelinePagerView.swift | 29 ++++++++++++------- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Sources/Timeline/TimelineContainer.swift b/Sources/Timeline/TimelineContainer.swift index 375362ee..3d5d71f5 100644 --- a/Sources/Timeline/TimelineContainer.swift +++ b/Sources/Timeline/TimelineContainer.swift @@ -2,6 +2,7 @@ import UIKit public final class TimelineContainer: UIScrollView { public let timeline: TimelineView + weak var parent: UIViewController? public init(_ timeline: TimelineView) { self.timeline = timeline @@ -14,8 +15,22 @@ public final class TimelineContainer: UIScrollView { } public override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) { - super.setContentOffset(contentOffset, animated: animated) - print("___ self.contentOffset for", contentOffset) + + guard let parent = parent?.parent as? CKPageViewController else { return } + + if let offset = parent.commonOffset { + super.setContentOffset(offset, animated: animated) + + } + + if parent.children.count == 1 { + super.setContentOffset(contentOffset, animated: animated) + } + + if parent.children.count != 1, + parent.commonOffset == nil { + super.setContentOffset(contentOffset, animated: animated) + } } override public func layoutSubviews() { diff --git a/Sources/Timeline/TimelineContainerController.swift b/Sources/Timeline/TimelineContainerController.swift index 8fffd85c..accd128b 100644 --- a/Sources/Timeline/TimelineContainerController.swift +++ b/Sources/Timeline/TimelineContainerController.swift @@ -7,22 +7,27 @@ public final class TimelineContainerController: UIViewController { public private(set) lazy var timeline = TimelineView() public private(set) lazy var container: TimelineContainer = { let view = TimelineContainer(timeline) + view.parent = self view.addSubview(timeline) return view }() + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + guard let parent = self.parent as? CKPageViewController else { return } + + if let offset = parent.commonOffset { + container.setContentOffset(offset, animated: false) + } else { + if let pendingOffset = self.pendingContentOffset { + container.setContentOffset(pendingOffset, animated: false) + } + } + } + public override func loadView() { view = container -// container.delegate = self } - -// public func scrollViewDidScroll(_ scrollView: UIScrollView) { -// self.parent?.children.forEach({ vc in -// if let vc = vc as? TimelineContainerController, vc !== self { -// vc.container.setContentOffset(scrollView.contentOffset, animated: false) -// } -// }) -// } public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index e8d18b28..b6fd7200 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -14,11 +14,19 @@ public protocol TimelinePagerViewDelegate: AnyObject { func timelinePager(timelinePager: TimelinePagerView, didUpdate event: EventDescriptor) } +class CKPageViewController: UIPageViewController { + + var commonOffset: CGPoint? + +} + public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScrollViewDelegate, DayViewStateUpdating, UIPageViewControllerDataSource, UIPageViewControllerDelegate, TimelineViewDelegate { public weak var dataSource: EventDataSource? public weak var delegate: TimelinePagerViewDelegate? + weak var currentPage: UIViewController? + public private(set) var calendar: Calendar = Calendar.autoupdatingCurrent public var eventEditingSnappingBehavior: EventEditingSnappingBehavior { didSet { @@ -38,7 +46,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr public var autoScrollToFirstEvent = false - private var pagingViewController = UIPageViewController(transitionStyle: .scroll, + private var pagingViewController = CKPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private var style = TimelineStyle() @@ -481,22 +489,21 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { -// setContentOffsetForChildren(offset: scrollView.contentOffset) + setContentOffsetForChildren(offset: scrollView.contentOffset) } } public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { -// setContentOffsetForChildren(offset: scrollView.contentOffset) + setContentOffsetForChildren(offset: scrollView.contentOffset) } private func setContentOffsetForChildren(offset: CGPoint) { - pagingViewController.children.forEach({ vc in - if let vc = vc as? TimelineContainerController { - vc.container.setContentOffset(offset, animated: false) - vc.container.setNeedsLayout() - print("___ . pendingOffSet did set for vc - ", offset) - } - }) +// if let first = currentPage as? TimelineContainerController{ + self.pagingViewController.commonOffset = offset +// } + if self.pagingViewController.children.count == 1 { + self.pagingViewController.commonOffset = offset + } } public var userContentOffset: CGPoint? @@ -526,6 +533,8 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr delegate?.timelinePagerDidTransitionCancel(timelinePager: self) return } + self.currentPage = pageViewController.viewControllers?.first + if let timelineContainerController = pageViewController.viewControllers?.first as? TimelineContainerController { let selectedDate = timelineContainerController.timeline.date // if let userContentOffset = userContentOffset { From 2aad6ab776b404b1d08fbbf36099ae8332d46b20 Mon Sep 17 00:00:00 2001 From: igover Date: Tue, 25 Oct 2022 15:56:48 +0300 Subject: [PATCH 26/38] Save current time for next/previous day (implemented) --- Sources/DayView.swift | 4 +-- Sources/Timeline/TimelineContainer.swift | 14 +++------- .../TimelineContainerController.swift | 14 +++++----- Sources/Timeline/TimelinePagerView.swift | 26 +++++-------------- Sources/Timeline/TimelineView.swift | 3 --- 5 files changed, 17 insertions(+), 44 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index 27c751d3..bea3d0a6 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -62,8 +62,7 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { var agendaHeightConstraint: NSLayoutConstraint! var dayHeightConstraint: NSLayoutConstraint! - - private static let headerVisibleHeight: CGFloat = 68 // swipe view height 20 + private static let headerVisibleHeight: CGFloat = 68 public var headerHeight: CGFloat = headerVisibleHeight public var autoScrollToFirstEvent: Bool { @@ -282,7 +281,6 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { // MARK: - DayViewDelegate public func didTapOpDate(date: Date) { delegate?.didTapOnDate(date: date) -// timelinePagerView.scrollToUserOffSet() } public func didMoveHeaderViewToDate(date: Date) { diff --git a/Sources/Timeline/TimelineContainer.swift b/Sources/Timeline/TimelineContainer.swift index 3d5d71f5..cd7e23ef 100644 --- a/Sources/Timeline/TimelineContainer.swift +++ b/Sources/Timeline/TimelineContainer.swift @@ -2,7 +2,7 @@ import UIKit public final class TimelineContainer: UIScrollView { public let timeline: TimelineView - weak var parent: UIViewController? + unowned var parent: UIViewController? public init(_ timeline: TimelineView) { self.timeline = timeline @@ -15,15 +15,14 @@ public final class TimelineContainer: UIScrollView { } public override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) { - guard let parent = parent?.parent as? CKPageViewController else { return } if let offset = parent.commonOffset { super.setContentOffset(offset, animated: animated) - } if parent.children.count == 1 { + parent.commonOffset = contentOffset super.setContentOffset(contentOffset, animated: animated) } @@ -36,10 +35,7 @@ public final class TimelineContainer: UIScrollView { override public func layoutSubviews() { super.layoutSubviews() timeline.frame = CGRect(x: 0, y: 0, width: bounds.width, height: timeline.fullHeight) - print("___ from layoutSubviews", contentOffset.y) - if contentOffset.y < 5 { - print() - } + timeline.offsetAllDayView(by: contentOffset.y) //adjust the scroll insets @@ -76,10 +72,6 @@ public final class TimelineContainer: UIScrollView { let padding: CGFloat = 8 setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll - padding), animated: animated) } - - public func scrollTo(offSet: CGPoint, animated: Bool = false) { - setTimelineOffset(offSet, animated: animated) - } private func setTimelineOffset(_ offset: CGPoint, animated: Bool) { let yToScroll = offset.y diff --git a/Sources/Timeline/TimelineContainerController.swift b/Sources/Timeline/TimelineContainerController.swift index accd128b..eda0c111 100644 --- a/Sources/Timeline/TimelineContainerController.swift +++ b/Sources/Timeline/TimelineContainerController.swift @@ -2,20 +2,24 @@ import UIKit public final class TimelineContainerController: UIViewController { /// Content Offset to be set once the view size has been calculated - public var pendingContentOffset: CGPoint? + public var pendingContentOffset: CGPoint? public private(set) lazy var timeline = TimelineView() public private(set) lazy var container: TimelineContainer = { let view = TimelineContainer(timeline) - view.parent = self + view.parent = self view.addSubview(timeline) return view }() + public override func loadView() { + view = container + } + public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) guard let parent = self.parent as? CKPageViewController else { return } - + if let offset = parent.commonOffset { container.setContentOffset(offset, animated: false) } else { @@ -25,10 +29,6 @@ public final class TimelineContainerController: UIViewController { } } - public override func loadView() { - view = container - } - public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() container.contentSize = timeline.frame.size diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index b6fd7200..b6e58279 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -15,9 +15,7 @@ public protocol TimelinePagerViewDelegate: AnyObject { } class CKPageViewController: UIPageViewController { - var commonOffset: CGPoint? - } public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScrollViewDelegate, DayViewStateUpdating, UIPageViewControllerDataSource, UIPageViewControllerDelegate, TimelineViewDelegate { @@ -47,8 +45,8 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr public var autoScrollToFirstEvent = false private var pagingViewController = CKPageViewController(transitionStyle: .scroll, - navigationOrientation: .horizontal, - options: nil) + navigationOrientation: .horizontal, + options: nil) private var style = TimelineStyle() private lazy var panGestureRecoognizer = UIPanGestureRecognizer(target: self, @@ -151,12 +149,6 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr controller.container.scrollTo(hour24: hour24, animated: animated) } } - - public func scrollToUserOffSet(animated: Bool = false) { - if let controller = currentTimeline { - controller.container.scrollTo(offSet: userContentOffset!, animated: animated) - } - } private func configureTimelineController(date: Date) -> TimelineContainerController { let controller = TimelineContainerController() @@ -447,7 +439,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr let oldDate = oldDate.dateOnly(calendar: calendar) let newDate = newDate.dateOnly(calendar: calendar) let newController = configureTimelineController(date: newDate) - + newController.pendingContentOffset = pagingViewController.commonOffset delegate?.timelinePager(timelinePager: self, willMoveTo: newDate) func completionHandler(_ completion: Bool) { @@ -498,16 +490,13 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } private func setContentOffsetForChildren(offset: CGPoint) { -// if let first = currentPage as? TimelineContainerController{ - self.pagingViewController.commonOffset = offset -// } + self.pagingViewController.commonOffset = offset + if self.pagingViewController.children.count == 1 { self.pagingViewController.commonOffset = offset } } - - public var userContentOffset: CGPoint? - + public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let containerController = viewController as? TimelineContainerController else { return nil } let previousDate = calendar.date(byAdding: .day, value: -1, to: containerController.timeline.date)! @@ -537,9 +526,6 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr if let timelineContainerController = pageViewController.viewControllers?.first as? TimelineContainerController { let selectedDate = timelineContainerController.timeline.date -// if let userContentOffset = userContentOffset { -// scrollToUserOffSet() -// } delegate?.timelinePager(timelinePager: self, willMoveTo: selectedDate) state?.client(client: self, didMoveTo: selectedDate) scrollToFirstEventIfNeeded(animated: true) diff --git a/Sources/Timeline/TimelineView.swift b/Sources/Timeline/TimelineView.swift index db8d340c..24ae61b0 100644 --- a/Sources/Timeline/TimelineView.swift +++ b/Sources/Timeline/TimelineView.swift @@ -169,9 +169,7 @@ public final class TimelineView: UIView { contentScaleFactor = 1 layer.contentsScale = 1 contentMode = .redraw -// let colors: [UIColor] = [.red, .blue, .link, .lightGray, .white, .green, .green, .brown, .magenta] backgroundColor = .white -// backgroundColor = colors.randomElement() addSubview(nowLine) // Add long press gesture recognizer @@ -439,7 +437,6 @@ public final class TimelineView: UIView { public func offsetAllDayView(by yValue: CGFloat) { if let topConstraint = self.allDayViewTopConstraint { topConstraint.constant = yValue - print("___ yValue", yValue) layoutIfNeeded() } } From ae0d17e9b39d95ea5dc423e9f8c139f0221bf89d Mon Sep 17 00:00:00 2001 From: igover Date: Wed, 26 Oct 2022 11:00:02 +0300 Subject: [PATCH 27/38] Layout optimization fix --- Sources/Timeline/TimelineContainer.swift | 1 + Sources/Timeline/TimelineContainerController.swift | 6 ++---- Sources/Timeline/TimelinePagerView.swift | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Sources/Timeline/TimelineContainer.swift b/Sources/Timeline/TimelineContainer.swift index cd7e23ef..bb07d596 100644 --- a/Sources/Timeline/TimelineContainer.swift +++ b/Sources/Timeline/TimelineContainer.swift @@ -19,6 +19,7 @@ public final class TimelineContainer: UIScrollView { if let offset = parent.commonOffset { super.setContentOffset(offset, animated: animated) + return } if parent.children.count == 1 { diff --git a/Sources/Timeline/TimelineContainerController.swift b/Sources/Timeline/TimelineContainerController.swift index eda0c111..316a9498 100644 --- a/Sources/Timeline/TimelineContainerController.swift +++ b/Sources/Timeline/TimelineContainerController.swift @@ -22,10 +22,8 @@ public final class TimelineContainerController: UIViewController { if let offset = parent.commonOffset { container.setContentOffset(offset, animated: false) - } else { - if let pendingOffset = self.pendingContentOffset { - container.setContentOffset(pendingOffset, animated: false) - } + } else if let pendingOffset = self.pendingContentOffset { + container.setContentOffset(pendingOffset, animated: false) } } diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index b6e58279..561b9bfd 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -14,7 +14,7 @@ public protocol TimelinePagerViewDelegate: AnyObject { func timelinePager(timelinePager: TimelinePagerView, didUpdate event: EventDescriptor) } -class CKPageViewController: UIPageViewController { +final class CKPageViewController: UIPageViewController { var commonOffset: CGPoint? } From c6442c033bc21f1be9a5b8f2c5c87725ebaa461d Mon Sep 17 00:00:00 2001 From: igover <75737505+igover@users.noreply.github.com> Date: Mon, 31 Oct 2022 19:39:59 +0300 Subject: [PATCH 28/38] Scroll to current time minus 4 hours --- Sources/Extensions/Date+DateOnly.swift | 11 +++++++++++ Sources/Timeline/TimelineContainer.swift | 12 ++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Sources/Extensions/Date+DateOnly.swift b/Sources/Extensions/Date+DateOnly.swift index 7c042d5a..c63770d7 100644 --- a/Sources/Extensions/Date+DateOnly.swift +++ b/Sources/Extensions/Date+DateOnly.swift @@ -22,4 +22,15 @@ extension Date { func year() -> Int { return Calendar.current.component(.year, from: self) } + + func timeOnly() -> Float { + let hour = Double(Calendar.current.component(.hour, from: Date())) + let minute = Double(Calendar.current.component(.minute, from: Date())) + var time = hour + let minutesInPercent = minute / 60.0 + if minutesInPercent < 1 { + time = hour + minutesInPercent + } + return Float(time) + } } diff --git a/Sources/Timeline/TimelineContainer.swift b/Sources/Timeline/TimelineContainer.swift index bb07d596..dbe5b65a 100644 --- a/Sources/Timeline/TimelineContainer.swift +++ b/Sources/Timeline/TimelineContainer.swift @@ -60,23 +60,19 @@ public final class TimelineContainer: UIScrollView { } public func scroollToCurrentTime(animated: Bool) { - let allDayViewHeight = timeline.allDayViewHeight - let padding = allDayViewHeight + 200 - if let yToScroll = timeline.currentTimeYPosition { - setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll - padding), animated: animated) - } + let timeToScroll = Date().timeOnly() - 4 + scrollTo(hour24: timeToScroll) } public func scrollTo(hour24: Float, animated: Bool = true) { let percentToScroll = CGFloat(hour24 / 24) let yToScroll = contentSize.height * percentToScroll - let padding: CGFloat = 8 - setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll - padding), animated: animated) + setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll), animated: animated) } private func setTimelineOffset(_ offset: CGPoint, animated: Bool) { let yToScroll = offset.y - let bottomOfScrollView = contentSize.height - bounds.size.height + let bottomOfScrollView = contentSize.height - bounds.size.height + 30 let newContentY = (yToScroll < bottomOfScrollView) ? yToScroll : bottomOfScrollView setContentOffset(CGPoint(x: offset.x, y: newContentY), animated: animated) } From 91eca481a574a5a2f451a78ad8ff6f7ce846c925 Mon Sep 17 00:00:00 2001 From: igover Date: Tue, 1 Nov 2022 13:06:08 +0300 Subject: [PATCH 29/38] Scroll to current time minus 4 hours (implemented) --- Sources/DayView.swift | 4 ++-- Sources/Timeline/TimelineContainer.swift | 5 ++++- Sources/Timeline/TimelinePagerView.swift | 9 ++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index bea3d0a6..a7b4b6a4 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -219,8 +219,8 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { timelinePagerView.scrollToFirstEventIfNeeded(animated: animated) } - public func scrollToCurrentTime(animated: Bool = true) { - timelinePagerView.scrollToCurrentTime(animated: animated) + public func scrollToCurrentTimeIfNeeded(needToScroll: Bool, animated: Bool = true) { + timelinePagerView.scrollToCurrentTimeIfNeeded(needToScroll: needToScroll, animated: animated) } public func reloadData() { diff --git a/Sources/Timeline/TimelineContainer.swift b/Sources/Timeline/TimelineContainer.swift index dbe5b65a..1ec127af 100644 --- a/Sources/Timeline/TimelineContainer.swift +++ b/Sources/Timeline/TimelineContainer.swift @@ -61,12 +61,15 @@ public final class TimelineContainer: UIScrollView { public func scroollToCurrentTime(animated: Bool) { let timeToScroll = Date().timeOnly() - 4 - scrollTo(hour24: timeToScroll) + scrollTo(hour24: timeToScroll, animated: animated) } public func scrollTo(hour24: Float, animated: Bool = true) { let percentToScroll = CGFloat(hour24 / 24) let yToScroll = contentSize.height * percentToScroll + if let parent = parent?.parent as? CKPageViewController { + parent.commonOffset?.y = yToScroll + } setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll), animated: animated) } diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index 561b9bfd..e310fa0a 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -212,9 +212,11 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } } - public func scrollToCurrentTime(animated: Bool) { - if let controller = currentTimeline { - controller.container.scroollToCurrentTime(animated: animated) + public func scrollToCurrentTimeIfNeeded(needToScroll: Bool, animated: Bool) { + if needToScroll { + if let controller = currentTimeline { + controller.container.scroollToCurrentTime(animated: animated) + } } } @@ -456,6 +458,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr self.pagingViewController.viewControllers?.first?.view.setNeedsLayout() self.scrollToFirstEventIfNeeded(animated: true) + self.scrollToCurrentTimeIfNeeded(needToScroll: false, animated: true) self.delegate?.timelinePager(timelinePager: self, didMoveTo: newDate) } } From f710bbd7715c088411a785ed86fc9c792f28ca70 Mon Sep 17 00:00:00 2001 From: igover Date: Tue, 1 Nov 2022 18:11:45 +0300 Subject: [PATCH 30/38] Change events gap (in case if second event starts in the end time of previous event) --- Sources/CalendarStyle.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CalendarStyle.swift b/Sources/CalendarStyle.swift index c764cf7e..741a77de 100644 --- a/Sources/CalendarStyle.swift +++ b/Sources/CalendarStyle.swift @@ -73,7 +73,7 @@ public struct TimelineStyle { public var verticalDiff: CGFloat = 50 public var verticalInset: CGFloat = 10 public var leadingInset: CGFloat = 53 - public var eventGap: CGFloat = 0 + public var eventGap: CGFloat = 1 public init() {} } From 1dcac2c53ac495b8cd9d4da6b32fb62fb8cc6521 Mon Sep 17 00:00:00 2001 From: igover Date: Wed, 2 Nov 2022 13:00:32 +0300 Subject: [PATCH 31/38] Added min date for Public API --- Sources/DayView.swift | 6 ++++++ Sources/Timeline/TimelinePagerView.swift | 3 +++ 2 files changed, 9 insertions(+) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index a7b4b6a4..da1285b5 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -47,6 +47,12 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { } } + public var minDate: Date? { + didSet { + timelinePagerView.minDate = minDate + } + } + public var horizontalSpacing: CGFloat = 0 { didSet { layoutTableView() diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index e310fa0a..45fc37bf 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -43,6 +43,8 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } public var autoScrollToFirstEvent = false + + public var minDate: Date? private var pagingViewController = CKPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, @@ -503,6 +505,7 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let containerController = viewController as? TimelineContainerController else { return nil } let previousDate = calendar.date(byAdding: .day, value: -1, to: containerController.timeline.date)! + guard previousDate >= minDate ?? Date() else { return nil } let timelineContainerController = configureTimelineController(date: previousDate) let offset = containerController.container.contentOffset timelineContainerController.pendingContentOffset = offset From 5af918af685854f09c5587793c032206be53fe00 Mon Sep 17 00:00:00 2001 From: igover Date: Wed, 2 Nov 2022 13:17:51 +0300 Subject: [PATCH 32/38] Fix logic for min date --- Sources/Timeline/TimelinePagerView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index 45fc37bf..ac76c8f3 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -505,7 +505,9 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let containerController = viewController as? TimelineContainerController else { return nil } let previousDate = calendar.date(byAdding: .day, value: -1, to: containerController.timeline.date)! - guard previousDate >= minDate ?? Date() else { return nil } + if let minDate = minDate { + guard previousDate >= minDate else { return nil } + } let timelineContainerController = configureTimelineController(date: previousDate) let offset = containerController.container.contentOffset timelineContainerController.pendingContentOffset = offset From 4d4c9239b12c8b55918dca9757c0f8b0da3aa6d6 Mon Sep 17 00:00:00 2001 From: igover Date: Thu, 3 Nov 2022 13:14:54 +0300 Subject: [PATCH 33/38] Fix scroll to current time method --- Sources/DayView.swift | 13 +++++-- Sources/Extensions/Date+DateOnly.swift | 14 +++++--- Sources/Timeline/TimelineContainer.swift | 34 +++++++++++++------ .../TimelineContainerController.swift | 7 ++++ Sources/Timeline/TimelinePagerView.swift | 20 +++++++---- 5 files changed, 64 insertions(+), 24 deletions(-) diff --git a/Sources/DayView.swift b/Sources/DayView.swift index da1285b5..3a9c8452 100644 --- a/Sources/DayView.swift +++ b/Sources/DayView.swift @@ -79,6 +79,15 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { timelinePagerView.autoScrollToFirstEvent = value } } + + public var timeIntervalBefore: Float { + get { + timelinePagerView.timeIntervalBefore + } + set (value) { + timelinePagerView.timeIntervalBefore = value + } + } public let dayHeaderView: DayHeaderView public let timelinePagerView: TimelinePagerView @@ -225,8 +234,8 @@ public class DayView: UIView, TimelinePagerViewDelegate, DayHeaderViewDelegate { timelinePagerView.scrollToFirstEventIfNeeded(animated: animated) } - public func scrollToCurrentTimeIfNeeded(needToScroll: Bool, animated: Bool = true) { - timelinePagerView.scrollToCurrentTimeIfNeeded(needToScroll: needToScroll, animated: animated) + public func scrollToCurrentTime(animated: Bool = true) { + timelinePagerView.scrollToCurrentTime(animated: animated) } public func reloadData() { diff --git a/Sources/Extensions/Date+DateOnly.swift b/Sources/Extensions/Date+DateOnly.swift index c63770d7..27dc0958 100644 --- a/Sources/Extensions/Date+DateOnly.swift +++ b/Sources/Extensions/Date+DateOnly.swift @@ -23,13 +23,17 @@ extension Date { return Calendar.current.component(.year, from: self) } + /* Returns current time in Float + - before point - hours + - after point - percent of minutes in hour + */ func timeOnly() -> Float { - let hour = Double(Calendar.current.component(.hour, from: Date())) - let minute = Double(Calendar.current.component(.minute, from: Date())) - var time = hour - let minutesInPercent = minute / 60.0 + let hours = Double(Calendar.current.component(.hour, from: Date())) + let minutes = Double(Calendar.current.component(.minute, from: Date())) + var time = hours + let minutesInPercent = minutes / 60.0 if minutesInPercent < 1 { - time = hour + minutesInPercent + time = hours + minutesInPercent } return Float(time) } diff --git a/Sources/Timeline/TimelineContainer.swift b/Sources/Timeline/TimelineContainer.swift index 1ec127af..e6394c52 100644 --- a/Sources/Timeline/TimelineContainer.swift +++ b/Sources/Timeline/TimelineContainer.swift @@ -3,6 +3,13 @@ import UIKit public final class TimelineContainer: UIScrollView { public let timeline: TimelineView unowned var parent: UIViewController? + + var timeIntervalBefore: Float { + if let parent = parent?.parent as? CKPageViewController { + return parent.timeIntervalBefore + } + return .zero + } public init(_ timeline: TimelineView) { self.timeline = timeline @@ -60,23 +67,30 @@ public final class TimelineContainer: UIScrollView { } public func scroollToCurrentTime(animated: Bool) { - let timeToScroll = Date().timeOnly() - 4 + let timeToScroll = Date().timeOnly() - timeIntervalBefore scrollTo(hour24: timeToScroll, animated: animated) } public func scrollTo(hour24: Float, animated: Bool = true) { let percentToScroll = CGFloat(hour24 / 24) let yToScroll = contentSize.height * percentToScroll - if let parent = parent?.parent as? CKPageViewController { - parent.commonOffset?.y = yToScroll - } setTimelineOffset(CGPoint(x: contentOffset.x, y: yToScroll), animated: animated) } - private func setTimelineOffset(_ offset: CGPoint, animated: Bool) { - let yToScroll = offset.y - let bottomOfScrollView = contentSize.height - bounds.size.height + 30 - let newContentY = (yToScroll < bottomOfScrollView) ? yToScroll : bottomOfScrollView - setContentOffset(CGPoint(x: offset.x, y: newContentY), animated: animated) - } + private func setTimelineOffset(_ offset: CGPoint, animated: Bool) { + let yToScroll = offset.y + let bottomOfScrollView = contentSize.height - bounds.size.height + + var newContentY: CGFloat = yToScroll + if yToScroll < bottomOfScrollView { + newContentY = (yToScroll < frame.minY) ? frame.minY : yToScroll + } else { + newContentY = bottomOfScrollView + } + + if let parent = parent?.parent as? CKPageViewController { + parent.commonOffset?.y = newContentY + } + setContentOffset(CGPoint(x: offset.x, y: newContentY), animated: animated) + } } diff --git a/Sources/Timeline/TimelineContainerController.swift b/Sources/Timeline/TimelineContainerController.swift index 316a9498..4a255999 100644 --- a/Sources/Timeline/TimelineContainerController.swift +++ b/Sources/Timeline/TimelineContainerController.swift @@ -30,6 +30,13 @@ public final class TimelineContainerController: UIViewController { public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() container.contentSize = timeline.frame.size + if let parent = self.parent as? CKPageViewController { + if parent.isFirstLaunch { + container.scroollToCurrentTime(animated: true) + parent.isFirstLaunch = false + } + } + if let newOffset = self.pendingContentOffset { // Apply new offset only once the size has been determined if view.bounds != .zero { diff --git a/Sources/Timeline/TimelinePagerView.swift b/Sources/Timeline/TimelinePagerView.swift index ac76c8f3..11855475 100644 --- a/Sources/Timeline/TimelinePagerView.swift +++ b/Sources/Timeline/TimelinePagerView.swift @@ -16,6 +16,8 @@ public protocol TimelinePagerViewDelegate: AnyObject { final class CKPageViewController: UIPageViewController { var commonOffset: CGPoint? + var isFirstLaunch = true + var timeIntervalBefore: Float = .zero } public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScrollViewDelegate, DayViewStateUpdating, UIPageViewControllerDataSource, UIPageViewControllerDelegate, TimelineViewDelegate { @@ -44,7 +46,15 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr public var autoScrollToFirstEvent = false + /// Min date in calendar for swipe public var minDate: Date? + + /// Time interval before current time + public var timeIntervalBefore: Float = .zero { + didSet { + pagingViewController.timeIntervalBefore = timeIntervalBefore + } + } private var pagingViewController = CKPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, @@ -214,11 +224,9 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr } } - public func scrollToCurrentTimeIfNeeded(needToScroll: Bool, animated: Bool) { - if needToScroll { - if let controller = currentTimeline { - controller.container.scroollToCurrentTime(animated: animated) - } + public func scrollToCurrentTime(animated: Bool) { + if let controller = currentTimeline { + controller.container.scroollToCurrentTime(animated: animated) } } @@ -459,8 +467,6 @@ public final class TimelinePagerView: UIView, UIGestureRecognizerDelegate, UIScr completion: nil) self.pagingViewController.viewControllers?.first?.view.setNeedsLayout() - self.scrollToFirstEventIfNeeded(animated: true) - self.scrollToCurrentTimeIfNeeded(needToScroll: false, animated: true) self.delegate?.timelinePager(timelinePager: self, didMoveTo: newDate) } } From b842cf27003d0502d878f0fd2aff714c17c553a2 Mon Sep 17 00:00:00 2001 From: igover Date: Mon, 7 Nov 2022 17:53:34 +0300 Subject: [PATCH 34/38] Fix layout for subject and location --- Sources/Timeline/AppointmentView.swift | 74 +++++++++++++++----------- Sources/Timeline/Event.swift | 2 +- Sources/Timeline/EventDescriptor.swift | 2 +- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/Sources/Timeline/AppointmentView.swift b/Sources/Timeline/AppointmentView.swift index de9cad6d..a70bd3a9 100644 --- a/Sources/Timeline/AppointmentView.swift +++ b/Sources/Timeline/AppointmentView.swift @@ -7,6 +7,28 @@ open class AppointmentView: UIView { public var contentHeight: CGFloat { stackView.frame.height } + + private typealias Attributes = ([NSAttributedString.Key : NSObject]) + private let separator = NSAttributedString(string: "\n") + + private lazy var subjectAttributes: Attributes = { + let style = NSMutableParagraphStyle() + style.lineBreakMode = .byTruncatingTail + let attributes = [NSAttributedString.Key.foregroundColor: UIColor.label, + NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .medium), + NSAttributedString.Key.paragraphStyle: style] + return attributes + }() + + private lazy var locationAttributes: Attributes = { + let style = NSMutableParagraphStyle() + style.paragraphSpacingBefore = 5 + style.lineBreakMode = .byTruncatingTail + let attributes = [NSAttributedString.Key.foregroundColor: UIColor.secondaryLabel, + NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15, weight: .medium), + NSAttributedString.Key.paragraphStyle: style] + return attributes + }() public private(set) lazy var stackView: UIStackView = { let view = UIStackView() @@ -15,39 +37,22 @@ open class AppointmentView: UIView { view.isUserInteractionEnabled = false view.backgroundColor = .clear view.axis = .vertical - view.spacing = 5 + view.spacing = .zero return view }() - public private(set) lazy var subjectLabel: UILabel = { + public private(set) lazy var textLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 16, weight: .medium) - label.lineBreakMode = .byTruncatingTail label.textColor = .label label.numberOfLines = 0 return label }() - public private(set) lazy var locationLabel: UITextView = { - let label = UITextView() - label.font = UIFont.systemFont(ofSize: 15, weight: .medium) - label.textContainer.lineBreakMode = .byTruncatingTail - label.textContainer.lineFragmentPadding = 0.0 - label.textContainer.maximumNumberOfLines = 1 - label.textContainerInset = .zero - label.backgroundColor = .clear - label.textColor = .secondaryLabel - return label + public private(set) lazy var dummyView: UILabel = { + return UILabel() }() - public private(set) lazy var textView: UITextView = { - let view = UITextView() - view.isUserInteractionEnabled = false - view.backgroundColor = .clear - view.isScrollEnabled = false - return view - }() - /// 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 private(set) lazy var eventResizeHandles = [EventResizeHandleView(), EventResizeHandleView()] @@ -65,8 +70,8 @@ open class AppointmentView: UIView { private func configure() { clipsToBounds = false color = tintColor - stackView.addArrangedSubview(subjectLabel) - stackView.addArrangedSubview(locationLabel) + stackView.addArrangedSubview(textLabel) + stackView.addArrangedSubview(dummyView) addSubview(stackView) for (idx, handle) in eventResizeHandles.enumerated() { @@ -76,15 +81,18 @@ open class AppointmentView: UIView { } public func updateWithDescriptor(event: EventDescriptor) { - if let attributedText = event.attributedText { - textView.attributedText = attributedText - } else { - subjectLabel.text = event.text - locationLabel.text = event.location - } - if let lineBreakMode = event.lineBreakMode { - textView.textContainer.lineBreakMode = lineBreakMode - } + let attributedText = NSMutableAttributedString() + let attributedSubject = NSAttributedString(string: event.text,attributes: subjectAttributes) + attributedText.append(attributedSubject) + + if let location = event.location { + let attributedLocation = NSAttributedString(string: location, attributes: locationAttributes) + attributedText.append(separator) + attributedText.append(attributedLocation) + } + + textLabel.attributedText = attributedText + descriptor = event backgroundColor = event.backgroundColor color = event.color @@ -159,12 +167,14 @@ open class AppointmentView: UIView { return CGRect(x: bounds.minX + 3, y: bounds.minY, width: bounds.width - 3, height: bounds.height) } }() + if frame.minY < 0 { var textFrame = stackView.frame; textFrame.origin.y = frame.minY * -1; textFrame.size.height += frame.minY; stackView.frame = textFrame; } + let first = eventResizeHandles.first let last = eventResizeHandles.last let radius: CGFloat = 40 diff --git a/Sources/Timeline/Event.swift b/Sources/Timeline/Event.swift index 57b7cd4e..718b49e2 100644 --- a/Sources/Timeline/Event.swift +++ b/Sources/Timeline/Event.swift @@ -4,7 +4,7 @@ public final class Event: EventDescriptor { public var dateInterval = DateInterval() public var isAllDay = false public var text = "" - public var location = "" + public var location: String? public var attributedText: NSAttributedString? public var lineBreakMode: NSLineBreakMode? public var color = SystemColors.systemBlue { diff --git a/Sources/Timeline/EventDescriptor.swift b/Sources/Timeline/EventDescriptor.swift index 36ba47cc..ab2b77cd 100644 --- a/Sources/Timeline/EventDescriptor.swift +++ b/Sources/Timeline/EventDescriptor.swift @@ -5,7 +5,7 @@ public protocol EventDescriptor: AnyObject { var dateInterval: DateInterval {get set} var isAllDay: Bool {get} var text: String {get} - var location: String {get} + var location: String? {get} var attributedText: NSAttributedString? {get} var lineBreakMode: NSLineBreakMode? {get} var font : UIFont {get} From be69b9f7910ddc24c7c4a0b36e5558c144567cf2 Mon Sep 17 00:00:00 2001 From: Igor Verebei Date: Wed, 15 Mar 2023 13:21:58 +0300 Subject: [PATCH 35/38] Appointment view. Alpha Color fix --- Sources/Timeline/Event.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/Timeline/Event.swift b/Sources/Timeline/Event.swift index 718b49e2..f78ee864 100644 --- a/Sources/Timeline/Event.swift +++ b/Sources/Timeline/Event.swift @@ -80,14 +80,16 @@ public final class Event: EventDescriptor { return UIColor(hue: h, saturation: s, brightness: b * 0.4, alpha: a) } - private func backgroundColorForLightTheme(baseColor: UIColor) -> UIColor { - baseColor.withAlphaComponent(0.3) - } + private func backgroundColorForLightTheme(baseColor: UIColor) -> UIColor { + var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + color.getHue(&h, saturation: &s, brightness: &b, alpha: &a) + return UIColor(hue: h, saturation: s * 0.3, brightness: b * 1.1, alpha: 1) + } private func backgroundColorForDarkTheme(baseColor: UIColor) -> UIColor { var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 color.getHue(&h, saturation: &s, brightness: &b, alpha: &a) - return UIColor(hue: h, saturation: s, brightness: b * 0.4, alpha: a * 0.8) + return UIColor(hue: h, saturation: s, brightness: b * 0.4, alpha: 1) } private func dynamicColor(light: UIColor, dark: UIColor) -> UIColor { From 53e2f0365301bec6d4b5d8eced20ca946475e1dc Mon Sep 17 00:00:00 2001 From: Igor Verebei Date: Wed, 24 May 2023 17:22:29 +0300 Subject: [PATCH 36/38] Add icon for private events. Fix layout for events less then 15 min --- Sources/Timeline/AppointmentView.swift | 39 ++++++++++++++++++++++---- Sources/Timeline/Event.swift | 2 ++ Sources/Timeline/EventDescriptor.swift | 1 + Sources/Timeline/TimelineView.swift | 7 ++++- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Sources/Timeline/AppointmentView.swift b/Sources/Timeline/AppointmentView.swift index a70bd3a9..527f49f9 100644 --- a/Sources/Timeline/AppointmentView.swift +++ b/Sources/Timeline/AppointmentView.swift @@ -10,12 +10,13 @@ open class AppointmentView: UIView { private typealias Attributes = ([NSAttributedString.Key : NSObject]) private let separator = NSAttributedString(string: "\n") - + private let lockImageSize: CGFloat = 12 + private lazy var subjectAttributes: Attributes = { let style = NSMutableParagraphStyle() style.lineBreakMode = .byTruncatingTail let attributes = [NSAttributedString.Key.foregroundColor: UIColor.label, - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .medium), + NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14, weight: .medium), NSAttributedString.Key.paragraphStyle: style] return attributes }() @@ -25,14 +26,14 @@ open class AppointmentView: UIView { style.paragraphSpacingBefore = 5 style.lineBreakMode = .byTruncatingTail let attributes = [NSAttributedString.Key.foregroundColor: UIColor.secondaryLabel, - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15, weight: .medium), + NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .medium), NSAttributedString.Key.paragraphStyle: style] return attributes }() public private(set) lazy var stackView: UIStackView = { let view = UIStackView() - view.layoutMargins = UIEdgeInsets(top: 2, left: 1, bottom: 2, right: 0) + view.layoutMargins = UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 0) view.isLayoutMarginsRelativeArrangement = true view.isUserInteractionEnabled = false view.backgroundColor = .clear @@ -52,6 +53,13 @@ open class AppointmentView: UIView { public private(set) lazy var dummyView: UILabel = { return UILabel() }() + + private lazy var lockImageView: UIImageView = { + let lockImage = UIImage(systemName: "lock.fill") + let imageView = UIImageView(image: lockImage) + imageView.tintColor = .label + return imageView + }() /// 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` @@ -73,6 +81,7 @@ open class AppointmentView: UIView { stackView.addArrangedSubview(textLabel) stackView.addArrangedSubview(dummyView) addSubview(stackView) + addSubview(lockImageView) for (idx, handle) in eventResizeHandles.enumerated() { handle.tag = idx @@ -162,9 +171,9 @@ open class AppointmentView: UIView { super.layoutSubviews() stackView.frame = { if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft { - return CGRect(x: bounds.minX, y: bounds.minY, width: bounds.width - 3, height: bounds.height) + return CGRect(x: bounds.minX, y: bounds.minY, width: bounds.width - lockImageSize - 5, height: bounds.height) } else { - return CGRect(x: bounds.minX + 3, y: bounds.minY, width: bounds.width - 3, height: bounds.height) + return CGRect(x: bounds.minX + 3, y: bounds.minY, width: bounds.width - lockImageSize - 5, height: bounds.height) } }() @@ -175,6 +184,24 @@ open class AppointmentView: UIView { stackView.frame = textFrame; } + lockImageView.frame = { + if stackView.bounds.height <= lockImageSize { + return CGRect(x: bounds.maxX - lockImageSize - 3, + y: bounds.maxY - lockImageSize, + width: lockImageSize, + height: lockImageSize) + } else { + return CGRect(x: bounds.maxX - lockImageSize - 3, + y: bounds.maxY - lockImageSize - 3, + width: lockImageSize, + height: lockImageSize) + } + }() + + guard let descriptor else { return } + // Не показывать иконку замка для частных событий длительностью менее 15 мин (900 сек) + lockImageView.isHidden = ((descriptor.dateInterval.duration) < TimeInterval(900.0)) || !(descriptor.isPrivate) + let first = eventResizeHandles.first let last = eventResizeHandles.last let radius: CGFloat = 40 diff --git a/Sources/Timeline/Event.swift b/Sources/Timeline/Event.swift index f78ee864..fae8c07a 100644 --- a/Sources/Timeline/Event.swift +++ b/Sources/Timeline/Event.swift @@ -3,6 +3,7 @@ import UIKit public final class Event: EventDescriptor { public var dateInterval = DateInterval() public var isAllDay = false + public var isPrivate = false public var text = "" public var location: String? public var attributedText: NSAttributedString? @@ -28,6 +29,7 @@ public final class Event: EventDescriptor { let cloned = Event() cloned.dateInterval = dateInterval cloned.isAllDay = isAllDay + cloned.isPrivate = isPrivate cloned.text = text cloned.location = location cloned.attributedText = attributedText diff --git a/Sources/Timeline/EventDescriptor.swift b/Sources/Timeline/EventDescriptor.swift index ab2b77cd..b777d0ee 100644 --- a/Sources/Timeline/EventDescriptor.swift +++ b/Sources/Timeline/EventDescriptor.swift @@ -4,6 +4,7 @@ import UIKit public protocol EventDescriptor: AnyObject { var dateInterval: DateInterval {get set} var isAllDay: Bool {get} + var isPrivate: Bool {get} var text: String {get} var location: String? {get} var attributedText: NSAttributedString? {get} diff --git a/Sources/Timeline/TimelineView.swift b/Sources/Timeline/TimelineView.swift index 24ae61b0..c245ae25 100644 --- a/Sources/Timeline/TimelineView.swift +++ b/Sources/Timeline/TimelineView.swift @@ -497,9 +497,14 @@ public final class TimelineView: UIView { let startY = dateToY(event.descriptor.dateInterval.start) let endY = dateToY(event.descriptor.dateInterval.end) let floatIndex = CGFloat(index) + var height = endY - startY + // если событие менее 15 минут - отображать высоту view как для 15 минут + if height < style.verticalDiff / 4 { + height = style.verticalDiff / 4 + } let x = style.leadingInset + floatIndex / totalCount * calendarWidth let equalWidth = calendarWidth / totalCount - event.frame = CGRect(x: x, y: startY, width: equalWidth, height: endY - startY) + event.frame = CGRect(x: x, y: startY, width: equalWidth, height: height) } } } From 1508c16b2208c114fdfe691aebe0e478a2c15f5f Mon Sep 17 00:00:00 2001 From: igover <75737505+igover@users.noreply.github.com> Date: Fri, 26 May 2023 16:41:54 +0300 Subject: [PATCH 37/38] Added display event with zero duration. --- Sources/CalendarStyle.swift | 2 +- Sources/Timeline/AppointmentView.swift | 104 +++++++++++++++++++------ Sources/Timeline/TimelineView.swift | 8 +- 3 files changed, 90 insertions(+), 24 deletions(-) diff --git a/Sources/CalendarStyle.swift b/Sources/CalendarStyle.swift index 741a77de..1b5e3cd3 100644 --- a/Sources/CalendarStyle.swift +++ b/Sources/CalendarStyle.swift @@ -70,7 +70,7 @@ public struct TimelineStyle { public var eventsWillOverlap: Bool = false public var minimumEventDurationInMinutesWhileEditing: Int = 30 public var splitMinuteInterval: Int = 15 - public var verticalDiff: CGFloat = 50 + public var verticalDiff: CGFloat = 60 public var verticalInset: CGFloat = 10 public var leadingInset: CGFloat = 53 public var eventGap: CGFloat = 1 diff --git a/Sources/Timeline/AppointmentView.swift b/Sources/Timeline/AppointmentView.swift index 527f49f9..7591501b 100644 --- a/Sources/Timeline/AppointmentView.swift +++ b/Sources/Timeline/AppointmentView.swift @@ -4,19 +4,20 @@ open class AppointmentView: UIView { public var descriptor: EventDescriptor? public var color = SystemColors.label - public var contentHeight: CGFloat { - stackView.frame.height - } + private var isZeroDuration: Bool { + return descriptor?.dateInterval.duration == Double(0.0) + } private typealias Attributes = ([NSAttributedString.Key : NSObject]) private let separator = NSAttributedString(string: "\n") - private let lockImageSize: CGFloat = 12 + private let lockImageSize: CGFloat = 14 + private let pointSize: CGFloat = 10 private lazy var subjectAttributes: Attributes = { let style = NSMutableParagraphStyle() style.lineBreakMode = .byTruncatingTail let attributes = [NSAttributedString.Key.foregroundColor: UIColor.label, - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14, weight: .medium), + NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .medium), NSAttributedString.Key.paragraphStyle: style] return attributes }() @@ -26,12 +27,12 @@ open class AppointmentView: UIView { style.paragraphSpacingBefore = 5 style.lineBreakMode = .byTruncatingTail let attributes = [NSAttributedString.Key.foregroundColor: UIColor.secondaryLabel, - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 13, weight: .medium), + NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium), NSAttributedString.Key.paragraphStyle: style] return attributes }() - public private(set) lazy var stackView: UIStackView = { + private lazy var stackView: UIStackView = { let view = UIStackView() view.layoutMargins = UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 0) view.isLayoutMarginsRelativeArrangement = true @@ -42,15 +43,15 @@ open class AppointmentView: UIView { return view }() - public private(set) lazy var textLabel: UILabel = { + private lazy var stactTextLabel: UILabel = { let label = UILabel() - label.font = UIFont.systemFont(ofSize: 16, weight: .medium) + label.font = UIFont.systemFont(ofSize: 13, weight: .medium) label.textColor = .label label.numberOfLines = 0 return label }() - public private(set) lazy var dummyView: UILabel = { + private lazy var dummyView: UILabel = { return UILabel() }() @@ -60,7 +61,27 @@ open class AppointmentView: UIView { imageView.tintColor = .label return imageView }() - + + // MARK: - Props for zero duration view + private lazy var containerView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() + + private lazy var containerTextLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 13, weight: .medium) + label.textColor = .label + label.lineBreakMode = .byTruncatingTail + return label + }() + + private lazy var pointView: UIView = { + let view = UIView() + return view + }() + /// 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 private(set) lazy var eventResizeHandles = [EventResizeHandleView(), EventResizeHandleView()] @@ -78,11 +99,15 @@ open class AppointmentView: UIView { private func configure() { clipsToBounds = false color = tintColor - stackView.addArrangedSubview(textLabel) + stackView.addArrangedSubview(stactTextLabel) stackView.addArrangedSubview(dummyView) addSubview(stackView) addSubview(lockImageView) - + + containerView.addSubview(pointView) + containerView.addSubview(containerTextLabel) + self.addSubview(containerView) + for (idx, handle) in eventResizeHandles.enumerated() { handle.tag = idx addSubview(handle) @@ -100,11 +125,14 @@ open class AppointmentView: UIView { attributedText.append(attributedLocation) } - textLabel.attributedText = attributedText + stactTextLabel.attributedText = attributedText + containerTextLabel.text = event.text + descriptor = event + backgroundColor = isZeroDuration ? .clear : event.backgroundColor + pointView.backgroundColor = event.color + + color = isZeroDuration ? .clear : event.color - descriptor = event - backgroundColor = event.backgroundColor - color = event.color eventResizeHandles.forEach{ $0.borderColor = event.color $0.isHidden = event.editedEvent == nil @@ -166,10 +194,42 @@ open class AppointmentView: UIView { } private var drawsShadow = false - - override open func layoutSubviews() { - super.layoutSubviews() - stackView.frame = { + + override open func layoutSubviews() { + super.layoutSubviews() + containerView.isHidden = true + pointView.isHidden = true + containerTextLabel.isHidden = true + stackView.isHidden = false + + // Не показывать stackView для событий с длительностью 0. Вместо него использовать containerView + if isZeroDuration { + stackView.isHidden = true + containerView.isHidden = false + pointView.isHidden = false + containerTextLabel.isHidden = false + + containerView.frame = CGRect(x: bounds.minX, + y: bounds.minY, + width: bounds.width, + height: bounds.height) + + pointView.frame = CGRect(x: containerView.frame.minX + 3, + y: containerView.frame.height/2 - pointSize/2, + width: pointSize, + height: pointSize) + + pointView.layer.cornerRadius = pointSize/2 + + containerTextLabel.frame = CGRect(x: pointView.frame.maxX + 5, + y: 0, + width: containerView.bounds.width - pointSize * 2, + height: containerView.frame.height) + + } + + + stackView.frame = { if UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft { return CGRect(x: bounds.minX, y: bounds.minY, width: bounds.width - lockImageSize - 5, height: bounds.height) } else { @@ -197,7 +257,7 @@ open class AppointmentView: UIView { height: lockImageSize) } }() - + guard let descriptor else { return } // Не показывать иконку замка для частных событий длительностью менее 15 мин (900 сек) lockImageView.isHidden = ((descriptor.dateInterval.duration) < TimeInterval(900.0)) || !(descriptor.isPrivate) diff --git a/Sources/Timeline/TimelineView.swift b/Sources/Timeline/TimelineView.swift index c245ae25..bc100b43 100644 --- a/Sources/Timeline/TimelineView.swift +++ b/Sources/Timeline/TimelineView.swift @@ -494,7 +494,7 @@ public final class TimelineView: UIView { for overlappingEvents in groupsOfEvents { let totalCount = CGFloat(overlappingEvents.count) for (index, event) in overlappingEvents.enumerated() { - let startY = dateToY(event.descriptor.dateInterval.start) + var startY = dateToY(event.descriptor.dateInterval.start) let endY = dateToY(event.descriptor.dateInterval.end) let floatIndex = CGFloat(index) var height = endY - startY @@ -502,6 +502,12 @@ public final class TimelineView: UIView { if height < style.verticalDiff / 4 { height = style.verticalDiff / 4 } + + // если событие длительностью 0 минут - смещаем начало события на половину высоты appointmentZeroView + if event.descriptor.dateInterval.duration == 0 { + startY = (startY - height / 2) + style.eventGap + } + let x = style.leadingInset + floatIndex / totalCount * calendarWidth let equalWidth = calendarWidth / totalCount event.frame = CGRect(x: x, y: startY, width: equalWidth, height: height) From 043eae0f075d26290ad413d7e0d5ecc50324a05d Mon Sep 17 00:00:00 2001 From: Kalabishka Ivan Date: Fri, 9 Jun 2023 22:45:54 +0300 Subject: [PATCH 38/38] Feature #11135 - Displaying events --- Sources/Timeline/AppointmentView.swift | 171 ++++++++++++++++++++----- Sources/Timeline/Event.swift | 3 + Sources/Timeline/EventDescriptor.swift | 11 ++ 3 files changed, 150 insertions(+), 35 deletions(-) diff --git a/Sources/Timeline/AppointmentView.swift b/Sources/Timeline/AppointmentView.swift index 7591501b..e561db88 100644 --- a/Sources/Timeline/AppointmentView.swift +++ b/Sources/Timeline/AppointmentView.swift @@ -10,7 +10,7 @@ open class AppointmentView: UIView { private typealias Attributes = ([NSAttributedString.Key : NSObject]) private let separator = NSAttributedString(string: "\n") - private let lockImageSize: CGFloat = 14 + private let lockImageSize: CGFloat = 14 private let pointSize: CGFloat = 10 private lazy var subjectAttributes: Attributes = { @@ -74,7 +74,7 @@ open class AppointmentView: UIView { label.font = UIFont.systemFont(ofSize: 13, weight: .medium) label.textColor = .label label.lineBreakMode = .byTruncatingTail - return label + return label }() private lazy var pointView: UIView = { @@ -114,34 +114,44 @@ open class AppointmentView: UIView { } } - public func updateWithDescriptor(event: EventDescriptor) { - let attributedText = NSMutableAttributedString() - let attributedSubject = NSAttributedString(string: event.text,attributes: subjectAttributes) - attributedText.append(attributedSubject) - - if let location = event.location { - let attributedLocation = NSAttributedString(string: location, attributes: locationAttributes) - attributedText.append(separator) - attributedText.append(attributedLocation) - } - - stactTextLabel.attributedText = attributedText - containerTextLabel.text = event.text - descriptor = event - backgroundColor = isZeroDuration ? .clear : event.backgroundColor - pointView.backgroundColor = event.color - - color = isZeroDuration ? .clear : event.color - - eventResizeHandles.forEach{ - $0.borderColor = event.color - $0.isHidden = event.editedEvent == nil + public func updateWithDescriptor(event: EventDescriptor) { + let attributedText = NSMutableAttributedString() + + let cancelledSubject = NSAttributedString( + string: event.text, + attributes: [.strikethroughStyle: NSUnderlineStyle.single.rawValue] + ) + + let attributedSubject = NSAttributedString(string: event.text, attributes: subjectAttributes) + attributedText.append(event.isCancelledAppointment ? cancelledSubject : attributedSubject) + + if let location = event.location { + let attributedLocation = NSAttributedString(string: location, attributes: locationAttributes) + attributedText.append(separator) + attributedText.append(attributedLocation) + } + + stactTextLabel.attributedText = attributedText + containerTextLabel.text = event.text + descriptor = event + + setupViewStyle(with: CalendarResponse(rawValue: event.responseType), + isCancelledAppointment: event.isCancelledAppointment) + + pointView.backgroundColor = event.color + + eventResizeHandles.forEach{ + $0.borderColor = event.color + $0.isHidden = event.editedEvent == nil + } + drawsShadow = event.editedEvent != nil + + clipsToBounds = true + layer.cornerRadius = 2 + setNeedsDisplay() + setNeedsLayout() } - drawsShadow = event.editedEvent != nil - setNeedsDisplay() - setNeedsLayout() - } - + public func animateCreation() { transform = CGAffineTransform(scaleX: 0.8, y: 0.8) func scaleAnimation() { @@ -202,7 +212,7 @@ open class AppointmentView: UIView { containerTextLabel.isHidden = true stackView.isHidden = false - // Не показывать stackView для событий с длительностью 0. Вместо него использовать containerView + // Не показывать stackView для событий с длительностью 0. Вместо него использовать containerView if isZeroDuration { stackView.isHidden = true containerView.isHidden = false @@ -221,9 +231,9 @@ open class AppointmentView: UIView { pointView.layer.cornerRadius = pointSize/2 - containerTextLabel.frame = CGRect(x: pointView.frame.maxX + 5, - y: 0, - width: containerView.bounds.width - pointSize * 2, + containerTextLabel.frame = CGRect(x: pointView.frame.maxX + 5, + y: 0, + width: containerView.bounds.width - pointSize * 2, height: containerView.frame.height) } @@ -255,8 +265,8 @@ open class AppointmentView: UIView { y: bounds.maxY - lockImageSize - 3, width: lockImageSize, height: lockImageSize) - } - }() + } + }() guard let descriptor else { return } // Не показывать иконку замка для частных событий длительностью менее 15 мин (900 сек) @@ -300,4 +310,95 @@ open class AppointmentView: UIView { layer.shadowPath = UIBezierPath(rect: rect).cgPath } } + + private func setupBackgroundColor(_ responseType: CalendarResponse?, + _ isCancelledAppointment: Bool) -> UIColor { + if !isCancelledAppointment { + switch responseType { + case .unknown: return .WPXBlue + case .organizer: return .WPXBlue + case .tentative: return .WPXBlue.withAlphaComponent(0.50).patternStripes(color2: .WPXBlue.withAlphaComponent(0.25)) + case .accept: return .WPXBlue.withAlphaComponent(0.75) + case .decline: return .WPXBlue + case .noResponseReceived: return .WPXBlue.withAlphaComponent(0.25) + case .none: return .WPXBlue + } + } else { + return .WPXGray + } + } + + private func setupViewStyle(with responseType: CalendarResponse?,isCancelledAppointment: Bool) { + backgroundColor = setupBackgroundColor(responseType, isCancelledAppointment) + color = isZeroDuration ? .clear : setupBackgroundColor(responseType, isCancelledAppointment) + + if responseType == .noResponseReceived, !isCancelledAppointment { + setupDashedBorder(view: self) + } + } + + private func setupDashedBorder(view: UIView) { + let cornerRadius: CGFloat = 2 + let dashWidth: CGFloat = 2 + let dashColor: UIColor = .systemBlue.withAlphaComponent(0.75) + let dashLength: CGFloat = 5 + let betweenDashesSpace: CGFloat = 5 + let dashBorder = CAShapeLayer() + dashBorder.removeFromSuperlayer() + + view.layer.cornerRadius = cornerRadius + view.layer.masksToBounds = true + + dashBorder.lineWidth = dashWidth + dashBorder.strokeColor = dashColor.cgColor + dashBorder.lineDashPattern = [dashLength, betweenDashesSpace] as [NSNumber] + dashBorder.frame = view.bounds + dashBorder.fillColor = nil + dashBorder.path = UIBezierPath(roundedRect: view.bounds, cornerRadius: cornerRadius).cgPath + + view.layer.addSublayer(dashBorder) + } +} + +extension UIColor { + + /// make a diagonal striped pattern + func patternStripes(color2: UIColor = .white, barThickness t: CGFloat = 3) -> UIColor { + let dim: CGFloat = t * 2.0 * sqrt(2.0) + + let img = UIGraphicsImageRenderer(size: .init(width: dim, height: dim)).image { context in + + // rotate the context and shift up + context.cgContext.rotate(by: CGFloat.pi / 4.0) + context.cgContext.translateBy(x: 0.0, y: -2.0 * t) + + let bars: [(UIColor,UIBezierPath)] = [ + (self, UIBezierPath(rect: .init(x: 0.0, y: 0.0, width: dim * 2.0, height: t))), + (color2,UIBezierPath(rect: .init(x: 0.0, y: t, width: dim * 2.0, height: t))) + ] + + bars.forEach { $0.0.setFill(); $0.1.fill() } + + // move down and paint again + context.cgContext.translateBy(x: 0.0, y: 2.0 * t) + bars.forEach { $0.0.setFill(); $0.1.fill() } + } + + return UIColor(patternImage: img) + } +} + +extension UIColor { + static var WPXBlue = UIColor(red: 0/255, green: 128/255, blue: 255/255, alpha: 1) + static var WPXGray: UIColor = { + return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in + if UITraitCollection.userInterfaceStyle == .dark { + /// Return the color for Dark Mode + return .systemGray3 + } else { + /// Return the color for Light Mode + return .systemGray6 + } + } + }() } diff --git a/Sources/Timeline/Event.swift b/Sources/Timeline/Event.swift index fae8c07a..79025f33 100644 --- a/Sources/Timeline/Event.swift +++ b/Sources/Timeline/Event.swift @@ -1,6 +1,9 @@ import UIKit public final class Event: EventDescriptor { + public var responseType: Int = 0 + public var isCancelledAppointment: Bool = false + public var dateInterval = DateInterval() public var isAllDay = false public var isPrivate = false diff --git a/Sources/Timeline/EventDescriptor.swift b/Sources/Timeline/EventDescriptor.swift index b777d0ee..36d9c9cf 100644 --- a/Sources/Timeline/EventDescriptor.swift +++ b/Sources/Timeline/EventDescriptor.swift @@ -14,6 +14,17 @@ public protocol EventDescriptor: AnyObject { var textColor: UIColor {get} var backgroundColor: UIColor {get} var editedEvent: EventDescriptor? {get set} + var responseType: Int { get } + var isCancelledAppointment: Bool { get } func makeEditable() -> Self func commitEditing() } + +public enum CalendarResponse: Int { + case unknown + case organizer + case tentative + case accept + case decline + case noResponseReceived +}