Merged PR 210708: MSDateTimePicker Presentation API with DateTime support

Introduces a new `MSDateTimePicker` class to manage and handle presenting multiple Date or Time related pickers and coordinating between them.

Includes a refactor of `MSDatePicker` into `MSDatePickerController`, leaving one Controller class for each type of picker.

Also includes some miscellaneous SwiftLint update fixes.

![Screen Shot 2018-12-07 at 4.27.44 PM.png](https://onedrive.visualstudio.com/4dcbf0bc-c3cd-49c8-a7c3-ec1924691d9b/_apis/git/repositories/93ac71ee-b53a-4fc6-a8c4-d46a80d4ca39/pullRequests/210708/attachments/Screen%20Shot%202018-12-07%20at%204.27.44%20PM.png) ![Screen Shot 2018-12-07 at 4.27.48 PM.png](https://onedrive.visualstudio.com/4dcbf0bc-c3cd-49c8-a7c3-ec1924691d9b/_apis/git/repositories/93ac71ee-b53a-4fc6-a8c4-d46a80d4ca39/pullRequests/210708/attachments/Screen%20Shot%202018-12-07%20at%204.27.48%20PM.png)

Related work items: #665501
This commit is contained in:
Will Richman 2018-12-14 00:35:42 +00:00
Родитель 522ae9c053
Коммит 198f12b1cb
11 изменённых файлов: 304 добавлений и 311 удалений

Просмотреть файл

@ -50,9 +50,6 @@ whitelist_rules:
# Computed read-only properties and subscripts should avoid using the get keyword.
- implicit_getter
# If defer is at the end of its parent scope, it will be executed right where it is anyway.
- inert_defer
# Files should not contain leading whitespace.
- leading_whitespace

Просмотреть файл

@ -8,7 +8,7 @@ import UIKit
let demos: [(title: String, controllerClass: UIViewController.Type)] = [
("MSAvatarView", MSAvatarViewDemoController.self),
("MSBadgeView", MSBadgeViewDemoController.self),
("MSDatePicker", MSDatePickerDemoController.self),
("MSDateTimePicker", MSDateTimePickerDemoController.self),
("MSDrawerController", MSDrawerDemoController.self),
("MSLabel", MSLabelDemoController.self),
("MSPersonaListView", MSPersonaListViewDemoController.self),

Просмотреть файл

@ -1,34 +0,0 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
import OfficeUIFabric
import UIKit
class MSDatePickerDemoController: DemoController {
private var dateLabel: MSLabel?
override func viewDidLoad() {
super.viewDidLoad()
dateLabel = MSLabel(style: .headline, colorStyle: .primary)
dateLabel?.text = "No date selected"
container.addArrangedSubview(dateLabel!)
container.addArrangedSubview(createButton(title: "Show date picker", action: #selector(presentDatePicker)))
}
@objc func presentDatePicker() {
let datePicker = MSDatePicker(initialDate: Date())
datePicker.delegate = self
datePicker.present(from: self)
}
}
// MARK: - MSDatePickerDemoController: MSDatePickerDelegate
extension MSDatePickerDemoController: MSDatePickerDelegate {
func datePicker(_ datePicker: MSDatePicker, didPickDate date: Date) {
dateLabel?.text = String.dateString(from: date, compactness: .longDaynameDayMonthYear)
dismiss(animated: true)
}
}

Просмотреть файл

@ -0,0 +1,41 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
import OfficeUIFabric
import UIKit
class MSDateTimePickerDemoController: DemoController {
private let dateLabel = MSLabel(style: .headline, colorStyle: .primary)
private let dateTimePicker = MSDateTimePicker()
override func viewDidLoad() {
super.viewDidLoad()
dateTimePicker.delegate = self
dateLabel.text = "No date selected"
container.addArrangedSubview(dateLabel)
container.addArrangedSubview(createButton(title: "Show date picker", action: #selector(presentDatePicker)))
container.addArrangedSubview(createButton(title: "Show date time picker", action: #selector(presentDateTimePicker)))
}
@objc func presentDatePicker() {
dateTimePicker.present(from: self, with: .date)
}
@objc func presentDateTimePicker() {
dateTimePicker.present(from: self, with: .dateTime)
}
}
// MARK: - MSDateTimePickerDemoController: MSDatePickerDelegate
extension MSDateTimePickerDemoController: MSDateTimePickerDelegate {
func dateTimePicker(_ dateTimePicker: MSDateTimePicker, didPickDate date: Date) {
var compactness = MSDateStringCompactness.longDaynameDayMonthYear
if dateTimePicker.mode == .dateTime {
compactness = .longDaynameDayMonthHoursColumnsMinutes
}
dateLabel.text = String.dateString(from: date, compactness: compactness)
dateTimePicker.dismiss()
}
}

Просмотреть файл

@ -55,13 +55,12 @@
FD256C5B2183B90B00EC9588 /* MSDatePickerSelectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD256C5A2183B90B00EC9588 /* MSDatePickerSelectionManager.swift */; };
FD36F1A9216C0A6900CECBC6 /* MSCardPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF9221487225001AE720 /* MSCardPresentationController.swift */; };
FD4F2A1B2148937100C437D6 /* MSPageCardPresenterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4F2A1A2148937100C437D6 /* MSPageCardPresenterController.swift */; };
FD4F2A1E214ADBCF00C437D6 /* MSDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4F2A1D214ADBCF00C437D6 /* MSDatePicker.swift */; };
FD4F2A20214AE20400C437D6 /* MSDatePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4F2A1F214AE20400C437D6 /* MSDatePickerController.swift */; };
FD56FD92219123FE0023C7EA /* MSDateTimePickerViewComponentTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9758082191118E00B67319 /* MSDateTimePickerViewComponentTableView.swift */; };
FD56FD94219128BF0023C7EA /* MSDateTimePickerViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9758072191118E00B67319 /* MSDateTimePickerViewDataSource.swift */; };
FD56FD95219131430023C7EA /* MSDateTimePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9758062191118D00B67319 /* MSDateTimePickerView.swift */; };
FD56FD962192754B0023C7EA /* MSDateTimePickerViewComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9758092191118E00B67319 /* MSDateTimePickerViewComponent.swift */; };
FD56FD9A2194E50D0023C7EA /* MSDateTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5ADBF32190CDC80005A9AF /* MSDateTimePicker.swift */; };
FD56FD9A2194E50D0023C7EA /* MSDateTimePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5ADBF32190CDC80005A9AF /* MSDateTimePickerController.swift */; };
FD599D0221348439008845EE /* MSCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD599D0121348439008845EE /* MSCalendarView.swift */; };
FD599D062134A682008845EE /* AccessibleViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD599D052134A682008845EE /* AccessibleViewDelegate.swift */; };
FD599D082134AB0E008845EE /* MSCalendarViewWeekdayHeadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD599D072134AB0E008845EE /* MSCalendarViewWeekdayHeadingView.swift */; };
@ -77,6 +76,7 @@
FD777529219E3F6C00033D58 /* MSDayOfMonth.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD777528219E3F6C00033D58 /* MSDayOfMonth.swift */; };
FD77752B219E455A00033D58 /* MSAccessibilityContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77752A219E455A00033D58 /* MSAccessibilityContainerView.swift */; };
FD77752D219E62E100033D58 /* MSDateTimePickerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77752C219E62E100033D58 /* MSDateTimePickerViewLayout.swift */; };
FD77753021A490BA00033D58 /* MSDateTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77752F21A490BA00033D58 /* MSDateTimePicker.swift */; };
FD97580F2191118E00B67319 /* MSDateTimePickerViewComponentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97580A2191118E00B67319 /* MSDateTimePickerViewComponentCell.swift */; };
FD9A5C872179464F00D224D9 /* DateComponents+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9A5C862179464F00D224D9 /* DateComponents+Extensions.swift */; };
FDA1AF8C21484625001AE720 /* MSBlurringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1AF8B21484625001AE720 /* MSBlurringView.swift */; };
@ -152,14 +152,13 @@
FD0D29D52151A3D700E8655E /* MSCardPresenterNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSCardPresenterNavigationController.swift; sourceTree = "<group>"; };
FD256C5A2183B90B00EC9588 /* MSDatePickerSelectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDatePickerSelectionManager.swift; sourceTree = "<group>"; };
FD4F2A1A2148937100C437D6 /* MSPageCardPresenterController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSPageCardPresenterController.swift; sourceTree = "<group>"; };
FD4F2A1D214ADBCF00C437D6 /* MSDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDatePicker.swift; sourceTree = "<group>"; };
FD4F2A1F214AE20400C437D6 /* MSDatePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDatePickerController.swift; sourceTree = "<group>"; };
FD599D0121348439008845EE /* MSCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSCalendarView.swift; sourceTree = "<group>"; };
FD599D052134A682008845EE /* AccessibleViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibleViewDelegate.swift; sourceTree = "<group>"; };
FD599D072134AB0E008845EE /* MSCalendarViewWeekdayHeadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCalendarViewWeekdayHeadingView.swift; sourceTree = "<group>"; };
FD599D092134AB15008845EE /* MSCalendarViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCalendarViewLayout.swift; sourceTree = "<group>"; };
FD599D0B2134AB1E008845EE /* MSCalendarViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCalendarViewDataSource.swift; sourceTree = "<group>"; };
FD5ADBF32190CDC80005A9AF /* MSDateTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePicker.swift; sourceTree = "<group>"; };
FD5ADBF32190CDC80005A9AF /* MSDateTimePickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerController.swift; sourceTree = "<group>"; };
FD5BBE3A214B2F44008964B4 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
FD5BBE3E214C68F8008964B4 /* UINavigationBar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Extensions.swift"; sourceTree = "<group>"; };
FD5BBE40214C6AF3008964B4 /* MSTwoLinesTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSTwoLinesTitleView.swift; sourceTree = "<group>"; };
@ -169,6 +168,7 @@
FD777528219E3F6C00033D58 /* MSDayOfMonth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDayOfMonth.swift; sourceTree = "<group>"; };
FD77752A219E455A00033D58 /* MSAccessibilityContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSAccessibilityContainerView.swift; sourceTree = "<group>"; };
FD77752C219E62E100033D58 /* MSDateTimePickerViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerViewLayout.swift; sourceTree = "<group>"; };
FD77752F21A490BA00033D58 /* MSDateTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePicker.swift; sourceTree = "<group>"; };
FD9758062191118D00B67319 /* MSDateTimePickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerView.swift; sourceTree = "<group>"; };
FD9758072191118E00B67319 /* MSDateTimePickerViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerViewDataSource.swift; sourceTree = "<group>"; };
FD9758082191118E00B67319 /* MSDateTimePickerViewComponentTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerViewComponentTableView.swift; sourceTree = "<group>"; };
@ -399,7 +399,6 @@
FD4F2A1C214ADBBF00C437D6 /* Date Picker */ = {
isa = PBXGroup;
children = (
FD4F2A1D214ADBCF00C437D6 /* MSDatePicker.swift */,
FD4F2A1F214AE20400C437D6 /* MSDatePickerController.swift */,
FD256C5A2183B90B00EC9588 /* MSDatePickerSelectionManager.swift */,
);
@ -411,6 +410,7 @@
children = (
FD4F2A1C214ADBBF00C437D6 /* Date Picker */,
FD5C8C2A2190C3470063562C /* Date Time Picker */,
FD77752F21A490BA00033D58 /* MSDateTimePicker.swift */,
);
path = "Date Time Pickers";
sourceTree = "<group>";
@ -418,7 +418,7 @@
FD5C8C2A2190C3470063562C /* Date Time Picker */ = {
isa = PBXGroup;
children = (
FD5ADBF32190CDC80005A9AF /* MSDateTimePicker.swift */,
FD5ADBF32190CDC80005A9AF /* MSDateTimePickerController.swift */,
FD97580521910FE800B67319 /* Views */,
);
path = "Date Time Picker";
@ -640,7 +640,6 @@
A5B87B04211E22B70038C37C /* MSDimmingView.swift in Sources */,
A5961F9F218A256B00E2A506 /* MSPopupMenuItem.swift in Sources */,
B4E782C321793AB200A7DFCE /* MSActivityIndicatorCell.swift in Sources */,
FD4F2A1E214ADBCF00C437D6 /* MSDatePicker.swift in Sources */,
FD599D0A2134AB15008845EE /* MSCalendarViewLayout.swift in Sources */,
FDD454EE21405B390006E84E /* MSDotView.swift in Sources */,
FD256C5B2183B90B00EC9588 /* MSDatePickerSelectionManager.swift in Sources */,
@ -657,10 +656,11 @@
FD5BBE43214C73CE008964B4 /* MSEasyTapButton.swift in Sources */,
FD77752B219E455A00033D58 /* MSAccessibilityContainerView.swift in Sources */,
FDF41ED92141A02200EC527C /* MSCalendarConfiguration.swift in Sources */,
FD77753021A490BA00033D58 /* MSDateTimePicker.swift in Sources */,
FD7254E92147059D002F4069 /* Calendar+Extensions.swift in Sources */,
A559BB7E212B6D100055E107 /* String+Extension.swift in Sources */,
A5961FA5218A260500E2A506 /* MSPopupMenuSectionHeaderView.swift in Sources */,
FD56FD9A2194E50D0023C7EA /* MSDateTimePicker.swift in Sources */,
FD56FD9A2194E50D0023C7EA /* MSDateTimePickerController.swift in Sources */,
B46D3F9D215985AC0029772C /* MSPersonaListView.swift in Sources */,
A5DCA76421224026005F4CB7 /* MSSeparator.swift in Sources */,
FD599D062134A682008845EE /* AccessibleViewDelegate.swift in Sources */,

Просмотреть файл

@ -28,8 +28,6 @@ class MSCalendarViewWeekdayHeadingView: UIView {
private var firstWeekday: Int?
private let headerStyle: MSDatePickerHeaderStyle
// TODO: Remove this exception when SwiftLint rule false positive is fixed
// swiftlint:disable:next explicit_type_interface
private var headingLabels = [UILabel]()
init(headerStyle: MSDatePickerHeaderStyle) {

Просмотреть файл

@ -1,203 +0,0 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
import Foundation
// MARK: MSDatePickerHeaderStyle
@objc public enum MSDatePickerHeaderStyle: Int {
case light
case dark
}
// MARK: - MSDatePickerDelegate
/// Allows a class to be notified when a user confirms their selected date
public protocol MSDatePickerDelegate: class {
func datePicker(_ datePicker: MSDatePicker, didPickDate date: Date)
}
// MARK: - MSDatePicker
/// A view controller that can present itself modally to display a calendar used for picking a date, with a navigation bar with title.
open class MSDatePicker: UIViewController {
private struct Constants {
// TODO: Make title button width dynamic
static let titleButtonWidth: CGFloat = 160
static let preloadAvailabilityDaysOffset: Int = 30
}
/// The currently selected whole date. Automatically changes to start of day when set.
open var date: Date {
get {
return contentController.startDate
}
set {
let startDate = newValue.startOfDay
contentController.setup(startDate: startDate, endDate: startDate.adding(hours: 23, minutes: 59))
updateNavigationBar()
}
}
public weak var delegate: MSDatePickerDelegate?
private var titleView: MSTwoLinesTitleView!
private let subtitle: String?
private var contentController: MSDatePickerController!
// TODO: Add availability back in? - contactAvailabilitySummaryDataSource: ContactAvailabilitySummaryDataSource?,
/// Creates and sets up a calendar-style date picker, with a specified date shown first.
///
/// - Parameters:
/// - initialDate: A date object on the day chosen to be selected.
/// - overrideSubtitle: An optional string describing an optional subtitle for this date picker.
public init(initialDate: Date, subtitle: String? = nil) {
self.subtitle = subtitle
super.init(nibName: nil, bundle: nil)
let selectedDate = initialDate.startOfDay
initContentController(startDate: selectedDate, endDate: selectedDate.adding(hours: 23, minutes: 59), selectionMode: .start)
initTitleView()
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Presents this date picker over the selected view controller, using a card modal style.
///
/// - Parameter presentingViewController: The view controller the date picker will be presented on top of
public func present(from presentingViewController: UIViewController) {
if UIAccessibilityIsVoiceOverRunning() {
let dateTimePicker = MSDateTimePicker(date: date, showsTime: false)
dateTimePicker.delegate = self
dateTimePicker.present(from: presentingViewController)
}
let navController = MSCardPresenterNavigationController(rootViewController: self)
let pageCardPresenterVC = MSPageCardPresenterController(viewControllers: [navController], startingIndex: 0)
pageCardPresenterVC.onDismiss = {
presentingViewController.dismiss(animated: true)
}
presentingViewController.present(pageCardPresenterVC, animated: true)
}
open override func viewDidLoad() {
super.viewDidLoad()
addChildController(contentController)
initNavigationBar()
}
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateTitleFrame()
contentController.view.frame = view.bounds
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide default bottom border of navigation bar
navigationController?.navigationBar.hideBottomBorder()
}
// MARK: Create components
private func initTitleView() {
titleView = MSTwoLinesTitleView()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTitleButtonTapped))
titleView.addGestureRecognizer(tapRecognizer)
updateNavigationBar()
}
private func initContentController(startDate: Date, endDate: Date, selectionMode: MSDatePickerSelectionManager.SelectionMode) {
contentController = MSDatePickerController(
startDate: startDate,
endDate: endDate,
selectionMode: selectionMode
)
contentController.delegate = self
}
// MARK: Add components to hierarchy
private func initNavigationBar() {
if let image = UIImage.staticImageNamed("checkmark-blue-25x25"),
let landscapeImage = UIImage.staticImageNamed("checkmark-blue-thin-20x20") {
navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, landscapeImagePhone: landscapeImage, style: .plain, target: self, action: #selector(handleDidTapDone))
}
if let image = UIImage.staticImageNamed("back-25x25") {
navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(handleDidTapBack))
navigationItem.leftBarButtonItem?.tintColor = MSColors.buttonImage
}
navigationItem.titleView = titleView
}
// MARK: Update components
private func updateNavigationBar() {
let title = String.dateString(from: contentController.focusDate, compactness: .shortDaynameShortMonthnameDay)
titleView.setup(title: title, subtitle: subtitle)
updateTitleFrame()
}
private func updateTitleFrame() {
// Title
if let navigationController = navigationController {
titleView.frame = CGRect(
x: 0.0,
y: 0.0,
width: Constants.titleButtonWidth,
height: navigationController.navigationBar.height
)
}
}
// MARK: Handlers
@objc private func handleTitleButtonTapped() {
contentController.scrollToStartDate(animated: true)
}
@objc private func handleDidTapDone() {
delegate?.datePicker(self, didPickDate: date)
}
@objc private func handleDidTapBack() {
parent?.dismiss(animated: true)
}
}
// MARK: - MSDatePicker: MSDatePickerControllerDelegate
extension MSDatePicker: MSDatePickerControllerDelegate {
func datePickerController(_ datePickerController: MSDatePickerController, didSelectDate: Date) {
updateNavigationBar()
// TODO: Add delegate call for notifying date did change
}
}
// MARK: - MSDatePicker: MSDateTimePickerDelegate
extension MSDatePicker: MSDateTimePickerDelegate {
func dateTimePicker(_ dateTimePicker: MSDateTimePicker, didPickDate date: Date) {
delegate?.datePicker(self, didPickDate: date)
}
}
// MARK: - MSDatePicker: MSCardPresentable
extension MSDatePicker: MSCardPresentable {
public func idealSize() -> CGSize {
return contentController.idealSize()
}
}

Просмотреть файл

@ -4,10 +4,11 @@
import Foundation
// MARK: MSDatePickerControllerDelegate
// MARK: MSDatePickerHeaderStyle
protocol MSDatePickerControllerDelegate: class {
func datePickerController(_ datePickerController: MSDatePickerController, didSelectDate date: Date)
@objc enum MSDatePickerHeaderStyle: Int {
case light
case dark
}
// MARK: - MSDatePickerController
@ -16,10 +17,25 @@ protocol MSDatePickerControllerDelegate: class {
* Represents a date picker, that enables the user to scroll through years vertically week by week.
* The user can select a date or a range of dates.
*/
class MSDatePickerController: UIViewController {
class MSDatePickerController: UIViewController, DateTimePicker {
private struct Constants {
static let idealWidth: CGFloat = 343
// TODO: Make title button width dynamic
static let titleButtonWidth: CGFloat = 160
static let preloadAvailabilityDaysOffset: Int = 30
}
// Temporary date property for single date selection and DateTimePicker conformance. Will remove when MSDateSelectable is refactored to include start and end date.
/// The currently selected whole date. Automatically changes to start of day when set.
var date: Date {
get {
return startDate
}
set {
let startDate = newValue.startOfDay
setup(startDate: startDate, endDate: startDate.adding(hours: 23, minutes: 59))
updateNavigationBar()
}
}
var firstWeekday: Int = Calendar.current.firstWeekday
@ -27,8 +43,6 @@ class MSDatePickerController: UIViewController {
var startDate: Date { return selectionManager.selectedDates.startDate }
var endDate: Date { return selectionManager.selectedDates.endDate }
weak var delegate: MSDatePickerControllerDelegate?
var visibleDates: (startDate: Date, endDate: Date)? {
let contentOffset = calendarView.collectionView.contentOffset
return visibleDates(at: CGPoint(x: 0, y: contentOffset.y))
@ -38,6 +52,11 @@ class MSDatePickerController: UIViewController {
return selectionManager.selectionMode == .start ? startDate : endDate
}
weak var delegate: DateTimePickerDelegate?
private var titleView: MSTwoLinesTitleView!
private let subtitle: String?
private var monthOverlayIsShown: Bool = false
private var reloadDataAfterOverlayIsNeeded: Bool = false
@ -45,20 +64,33 @@ class MSDatePickerController: UIViewController {
private let calendarView = MSCalendarView()
private var calendarViewDataSource: MSCalendarViewDataSource!
// TODO: Add availability back in? availabilityDataSource: MeetingCalendarDatePickerAvailabilityDataSource?
init(startDate: Date, endDate: Date, selectionMode: MSDatePickerSelectionManager.SelectionMode) {
// TODO: Add availability back in? - contactAvailabilitySummaryDataSource: ContactAvailabilitySummaryDataSource?,
/// Creates and sets up a calendar-style date picker, with a specified date shown first.
///
/// - Parameters:
/// - startDate: A date object for the start day or day/time to be initially selected. Until range implemented, changes to start of day.
/// - endDate: An optional date object for an end day or day/time to be initially selected. Until range implemented, ignored.
/// - selectionMode: The side (start or end) of the current range to be selected on this picker.
/// - subtitle: An optional string describing an optional subtitle for this date picker.
init(startDate: Date, endDate: Date? = nil, selectionMode: MSDatePickerSelectionManager.SelectionMode = .start, subtitle: String? = nil) {
self.subtitle = subtitle
super.init(nibName: nil, bundle: nil)
calendarViewDataSource = MSCalendarViewDataSource(styleDataSource: self)
let startDate = startDate.startOfDay
selectionManager = MSDatePickerSelectionManager(
dataSource: calendarViewDataSource,
startDate: startDate,
endDate: endDate,
endDate: startDate.adding(hours: 23, minutes: 59),
selectionMode: selectionMode
)
initTitleView()
}
public required init?(coder aDecoder: NSCoder) {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -79,7 +111,7 @@ class MSDatePickerController: UIViewController {
}
}
public override func viewDidLoad() {
override func viewDidLoad() {
super.viewDidLoad()
calendarView.weekdayHeadingView.setup(horizontalSizeClass: traitCollection.horizontalSizeClass, firstWeekday: firstWeekday)
@ -99,27 +131,65 @@ class MSDatePickerController: UIViewController {
calendarView.collectionViewLayout.delegate = self
view.addSubview(calendarView)
initNavigationBar()
}
public override func viewWillAppear(_ animated: Bool) {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
scrollToStartDate(animated: false)
// Hide default bottom border of navigation bar
navigationController?.navigationBar.hideBottomBorder()
}
public override func viewWillLayoutSubviews() {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
calendarView.frame = view.bounds
}
func scrollToStartDate(animated: Bool) {
private func initTitleView() {
titleView = MSTwoLinesTitleView()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTitleButtonTapped))
titleView.addGestureRecognizer(tapRecognizer)
updateNavigationBar()
}
private func initNavigationBar() {
if let image = UIImage.staticImageNamed("checkmark-blue-25x25"),
let landscapeImage = UIImage.staticImageNamed("checkmark-blue-thin-20x20") {
navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, landscapeImagePhone: landscapeImage, style: .plain, target: self, action: #selector(handleDidTapDone))
}
navigationItem.titleView = titleView
}
private func updateNavigationBar() {
let title = String.dateString(from: focusDate, compactness: .shortDaynameShortMonthnameDay)
titleView.setup(title: title, subtitle: subtitle)
updateTitleFrame()
}
private func updateTitleFrame() {
if let navigationController = navigationController {
titleView.frame = CGRect(
x: 0.0,
y: 0.0,
width: Constants.titleButtonWidth,
height: navigationController.navigationBar.height
)
}
}
private func scrollToStartDate(animated: Bool) {
let targetIndexPath = IndexPath(item: 0, section: max(selectionManager.startDateIndexPath.section - 1, 0))
calendarView.collectionView.scrollToItem(at: targetIndexPath, at: [.top], animated: animated)
// TODO: Notify of visible date?
}
func setNeedsReloadAvailability() {
private func setNeedsReloadAvailability() {
reloadDataAfterOverlayIsNeeded = true
reloadDataAfterOverlayHiddenIfNeeded()
}
@ -151,12 +221,22 @@ class MSDatePickerController: UIViewController {
let endDate = calendarViewDataSource.dayEnd(forDayAt: endIndexPath)
return (startDate: startDate, endDate: endDate)
}
// MARK: Handlers
@objc private func handleTitleButtonTapped() {
scrollToStartDate(animated: true)
}
@objc private func handleDidTapDone() {
delegate?.dateTimePicker(self, didPickDate: date)
}
}
// MARK: - MSDatePickerController: UICollectionViewDelegate
extension MSDatePickerController: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
guard let monthBannerView = view as? MSCalendarViewMonthBannerView else {
return
}
@ -164,7 +244,7 @@ extension MSDatePickerController: UICollectionViewDelegate {
monthBannerView.setVisible(monthOverlayIsShown, animated: false)
}
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let dayCell = cell as? MSCalendarViewDayCell else {
return
}
@ -174,11 +254,11 @@ extension MSDatePickerController: UICollectionViewDelegate {
updateSelectionOfVisibleCells()
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
didTapItem(at: indexPath)
}
public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
didTapItem(at: indexPath)
}
@ -188,7 +268,9 @@ extension MSDatePickerController: UICollectionViewDelegate {
updateSelectionOfVisibleCells()
let (startDate, _) = selectionManager.selectedDates
delegate?.datePickerController(self, didSelectDate: startDate)
delegate?.dateTimePicker(self, didSelectDate: startDate)
updateNavigationBar()
}
private func updateSelectionOfVisibleCells() {
@ -248,7 +330,7 @@ extension MSDatePickerController: UIScrollViewDelegate {
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
changeMonthOverlayVisibility(false)
}
}

Просмотреть файл

@ -4,28 +4,28 @@
import Foundation
// MARK: MSDateTimePickerDelegate
// MARK: MSDateTimePickerController
@objc protocol MSDateTimePickerDelegate: class {
@objc func dateTimePicker(_ dateTimePicker: MSDateTimePicker, didPickDate date: Date)
@objc optional func dateTimePicker(_ dateTimePicker: MSDateTimePicker, didSelectDate date: Date)
}
// MARK: - MSDateTimePicker
class MSDateTimePicker: UIViewController {
class MSDateTimePickerController: UIViewController, DateTimePicker {
private struct Constants {
static let idealRowCount: Int = 7
static let idealWidth: CGFloat = 320
static let titleButtonWidth: CGFloat = 160
}
var mode: MSDateTimePickerViewMode { return dateTimePickerView.mode }
weak var delegate: MSDateTimePickerDelegate?
var date: Date {
didSet {
dateTimePickerView.setDate(date, animated: false)
updateNavigationBar()
}
}
private var date: Date
weak var delegate: DateTimePickerDelegate?
private let dateTimePickerView: MSDateTimePickerView
private let titleView = MSTwoLinesTitleView()
// TODO: Add availability back in? - contactAvailabilitySummaryDataSource: ContactAvailabilitySummaryDataSource?,
init(date: Date, showsTime: Bool = true) {
@ -38,32 +38,19 @@ class MSDateTimePicker: UIViewController {
dateTimePickerView.addTarget(self, action: #selector(handleDidSelectDate(_:)), for: .valueChanged)
initNavigationBar()
updateNavigationBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Presents this date time picker over the selected view controller, using a card modal style.
///
/// - Parameter presentingViewController: The view controller the date picker will be presented on top of
func present(from presentingViewController: UIViewController) {
let navController = MSCardPresenterNavigationController(rootViewController: self)
let pageCardPresenterVC = MSPageCardPresenterController(viewControllers: [navController], startingIndex: 0)
pageCardPresenterVC.onDismiss = {
self.dismiss(accept: false)
}
presentingViewController.present(pageCardPresenterVC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = MSColors.background
view.addSubview(dateTimePickerView)
initNavigationBar()
}
override func viewDidLayoutSubviews() {
@ -83,9 +70,23 @@ class MSDateTimePicker: UIViewController {
let landscapeImage = UIImage.staticImageNamed("checkmark-blue-thin-20x20") {
navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, landscapeImagePhone: landscapeImage, style: .plain, target: self, action: #selector(handleDidTapDone))
}
if let image = UIImage.staticImageNamed("back-25x25") {
navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(handleDidTapBack))
navigationItem.leftBarButtonItem?.tintColor = MSColors.buttonImage
navigationItem.titleView = titleView
}
private func updateNavigationBar() {
let title = String.dateString(from: date, compactness: .shortDaynameShortMonthnameDay)
titleView.setup(title: title)
updateTitleFrame()
}
private func updateTitleFrame() {
if let navigationController = navigationController {
titleView.frame = CGRect(
x: 0.0,
y: 0.0,
width: Constants.titleButtonWidth,
height: navigationController.navigationBar.height
)
}
}
@ -98,21 +99,17 @@ class MSDateTimePicker: UIViewController {
@objc private func handleDidSelectDate(_ datePicker: MSDateTimePickerView) {
date = datePicker.date
delegate?.dateTimePicker?(self, didSelectDate: date)
delegate?.dateTimePicker(self, didSelectDate: date)
}
@objc private func handleDidTapDone(_ item: UIBarButtonItem) {
dismiss(accept: true)
}
@objc private func handleDidTapBack(_ item: UIBarButtonItem) {
dismiss(accept: false)
}
}
// MARK: - MSDateTimePicker: MSCardPresentable
// MARK: - MSDateTimePickerController: MSCardPresentable
extension MSDateTimePicker: MSCardPresentable {
extension MSDateTimePickerController: MSCardPresentable {
func idealSize() -> CGSize {
let height = MSDateTimePickerViewLayout.height(forRowCount: Constants.idealRowCount)
return CGSize(width: Constants.idealWidth, height: height)

Просмотреть файл

@ -0,0 +1,117 @@
//
// Copyright © 2018 Microsoft Corporation. All rights reserved.
//
import Foundation
// MARK: MSDateTimePickerMode
@objc public enum MSDateTimePickerMode: Int {
case date
case dateTime
}
// MARK: - MSDateTimePickerDelegate
@objc public protocol MSDateTimePickerDelegate: class {
/// Allows a class to be notified when a user confirms their selected date
func dateTimePicker(_ dateTimePicker: MSDateTimePicker, didPickDate date: Date)
}
// MARK: - DateTimePicker
protocol DateTimePicker: class {
var date: Date { get set }
var delegate: DateTimePickerDelegate? { get set }
}
// MARK: - DateTimePickerDelegate
protocol DateTimePickerDelegate: class {
func dateTimePicker(_ dateTimePicker: DateTimePicker, didPickDate date: Date)
func dateTimePicker(_ dateTimePicker: DateTimePicker, didSelectDate date: Date)
}
// MARK: - MSDateTimePicker
/// Manages the presentation and coordination of different date and time pickers
public class MSDateTimePicker: NSObject {
public private(set) var mode: MSDateTimePickerMode?
@objc public weak var delegate: MSDateTimePickerDelegate?
private var presentingController: UIViewController?
private var presentedPickers: [DateTimePicker]?
/// Presents a picker or set of pickers from a `presentingController` depending on the mode selected. Also handles accessibility replacement presentation.
///
/// - Parameters:
/// - presentingController: The view controller that is presenting these pickers
/// - mode: Enum describing which mode of pickers should be presented
/// - date: The initial date selected on the presented pickers
@objc public func present(from presentingController: UIViewController, with mode: MSDateTimePickerMode, for date: Date = Date()) {
self.presentingController = presentingController
if UIAccessibilityIsVoiceOverRunning() {
presentDateTimePickerForAccessibility(initialDate: date, showsTime: mode == .dateTime)
return
}
self.mode = mode
switch mode {
case .date:
presentDatePicker(initialDate: date)
case .dateTime:
presentDateTimePicker(initialDate: date)
}
}
@objc public func dismiss() {
presentingController?.dismiss(animated: true)
mode = nil
presentedPickers = nil
presentingController = nil
}
private func presentDatePicker(initialDate: Date) {
let datePicker = MSDatePickerController(startDate: initialDate)
present([datePicker])
}
private func presentDateTimePicker(initialDate: Date) {
let datePicker = MSDatePickerController(startDate: initialDate)
let dateTimePicker = MSDateTimePickerController(date: initialDate, showsTime: true)
present([datePicker, dateTimePicker])
}
private func presentDateTimePickerForAccessibility(initialDate: Date, showsTime: Bool) {
let dateTimePicker = MSDateTimePickerController(date: initialDate, showsTime: showsTime)
present([dateTimePicker])
}
private func present(_ pickers: [DateTimePicker]) {
pickers.forEach { $0.delegate = self }
presentedPickers = pickers
let viewControllers = pickers.map { MSCardPresenterNavigationController(rootViewController: $0 as! UIViewController) }
let pageCardPresenter = MSPageCardPresenterController(viewControllers: viewControllers, startingIndex: 0)
pageCardPresenter.onDismiss = {
self.dismiss()
}
presentingController?.present(pageCardPresenter, animated: true)
}
}
// MARK: - MSDateTimePicker: DateTimePickerDelegate
extension MSDateTimePicker: DateTimePickerDelegate {
func dateTimePicker(_ dateTimePicker: DateTimePicker, didPickDate date: Date) {
delegate?.dateTimePicker(self, didPickDate: date)
}
func dateTimePicker(_ dateTimePicker: DateTimePicker, didSelectDate date: Date) {
guard let presentedPickers = presentedPickers else {
return
}
for picker in presentedPickers where picker !== dateTimePicker {
picker.date = date
}
}
}

Просмотреть файл

@ -6,8 +6,6 @@ import Foundation
public extension Calendar {
private static let sharedAutoUpdatingCalendar: Calendar = .autoupdatingCurrent
// TODO: Remove this exception when SwiftLint rule false positive is fixed
// swiftlint:disable:next explicit_type_interface
private static var sharedTimeZoneCalendars = [Calendar]()
static func sharedCalendarWithTimeZone(_ timeZone: TimeZone?) -> Calendar {