diff --git a/.swiftlint.yml b/.swiftlint.yml index 546464d..00fad1d 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -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 diff --git a/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos.swift b/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos.swift index fea9767..2750231 100644 --- a/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos.swift +++ b/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos.swift @@ -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), diff --git a/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos/MSDatePickerDemoController.swift b/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos/MSDatePickerDemoController.swift deleted file mode 100644 index 5889c86..0000000 --- a/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos/MSDatePickerDemoController.swift +++ /dev/null @@ -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) - } -} diff --git a/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos/MSDateTimePickerDemoController.swift b/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos/MSDateTimePickerDemoController.swift new file mode 100644 index 0000000..2f8defc --- /dev/null +++ b/OfficeUIFabric.Demo/OfficeUIFabric.Demo/Demos/MSDateTimePickerDemoController.swift @@ -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() + } +} diff --git a/OfficeUIFabric.xcodeproj/project.pbxproj b/OfficeUIFabric.xcodeproj/project.pbxproj index d78efba..35e24d6 100644 --- a/OfficeUIFabric.xcodeproj/project.pbxproj +++ b/OfficeUIFabric.xcodeproj/project.pbxproj @@ -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 = ""; }; FD256C5A2183B90B00EC9588 /* MSDatePickerSelectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDatePickerSelectionManager.swift; sourceTree = ""; }; FD4F2A1A2148937100C437D6 /* MSPageCardPresenterController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSPageCardPresenterController.swift; sourceTree = ""; }; - FD4F2A1D214ADBCF00C437D6 /* MSDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDatePicker.swift; sourceTree = ""; }; FD4F2A1F214AE20400C437D6 /* MSDatePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDatePickerController.swift; sourceTree = ""; }; FD599D0121348439008845EE /* MSCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSCalendarView.swift; sourceTree = ""; }; FD599D052134A682008845EE /* AccessibleViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibleViewDelegate.swift; sourceTree = ""; }; FD599D072134AB0E008845EE /* MSCalendarViewWeekdayHeadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCalendarViewWeekdayHeadingView.swift; sourceTree = ""; }; FD599D092134AB15008845EE /* MSCalendarViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCalendarViewLayout.swift; sourceTree = ""; }; FD599D0B2134AB1E008845EE /* MSCalendarViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCalendarViewDataSource.swift; sourceTree = ""; }; - FD5ADBF32190CDC80005A9AF /* MSDateTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePicker.swift; sourceTree = ""; }; + FD5ADBF32190CDC80005A9AF /* MSDateTimePickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerController.swift; sourceTree = ""; }; FD5BBE3A214B2F44008964B4 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; FD5BBE3E214C68F8008964B4 /* UINavigationBar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Extensions.swift"; sourceTree = ""; }; FD5BBE40214C6AF3008964B4 /* MSTwoLinesTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSTwoLinesTitleView.swift; sourceTree = ""; }; @@ -169,6 +168,7 @@ FD777528219E3F6C00033D58 /* MSDayOfMonth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDayOfMonth.swift; sourceTree = ""; }; FD77752A219E455A00033D58 /* MSAccessibilityContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSAccessibilityContainerView.swift; sourceTree = ""; }; FD77752C219E62E100033D58 /* MSDateTimePickerViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerViewLayout.swift; sourceTree = ""; }; + FD77752F21A490BA00033D58 /* MSDateTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSDateTimePicker.swift; sourceTree = ""; }; FD9758062191118D00B67319 /* MSDateTimePickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerView.swift; sourceTree = ""; }; FD9758072191118E00B67319 /* MSDateTimePickerViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerViewDataSource.swift; sourceTree = ""; }; FD9758082191118E00B67319 /* MSDateTimePickerViewComponentTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDateTimePickerViewComponentTableView.swift; sourceTree = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/OfficeUIFabric/Calendar/Views/MSCalendarViewWeekdayHeadingView.swift b/OfficeUIFabric/Calendar/Views/MSCalendarViewWeekdayHeadingView.swift index 85c9000..8326c0b 100644 --- a/OfficeUIFabric/Calendar/Views/MSCalendarViewWeekdayHeadingView.swift +++ b/OfficeUIFabric/Calendar/Views/MSCalendarViewWeekdayHeadingView.swift @@ -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) { diff --git a/OfficeUIFabric/Date Time Pickers/Date Picker/MSDatePicker.swift b/OfficeUIFabric/Date Time Pickers/Date Picker/MSDatePicker.swift deleted file mode 100644 index 928bd8b..0000000 --- a/OfficeUIFabric/Date Time Pickers/Date Picker/MSDatePicker.swift +++ /dev/null @@ -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() - } -} diff --git a/OfficeUIFabric/Date Time Pickers/Date Picker/MSDatePickerController.swift b/OfficeUIFabric/Date Time Pickers/Date Picker/MSDatePickerController.swift index beb170e..8b09602 100644 --- a/OfficeUIFabric/Date Time Pickers/Date Picker/MSDatePickerController.swift +++ b/OfficeUIFabric/Date Time Pickers/Date Picker/MSDatePickerController.swift @@ -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) } } diff --git a/OfficeUIFabric/Date Time Pickers/Date Time Picker/MSDateTimePicker.swift b/OfficeUIFabric/Date Time Pickers/Date Time Picker/MSDateTimePickerController.swift similarity index 62% rename from OfficeUIFabric/Date Time Pickers/Date Time Picker/MSDateTimePicker.swift rename to OfficeUIFabric/Date Time Pickers/Date Time Picker/MSDateTimePickerController.swift index fa81941..ff2db8b 100644 --- a/OfficeUIFabric/Date Time Pickers/Date Time Picker/MSDateTimePicker.swift +++ b/OfficeUIFabric/Date Time Pickers/Date Time Picker/MSDateTimePickerController.swift @@ -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) diff --git a/OfficeUIFabric/Date Time Pickers/MSDateTimePicker.swift b/OfficeUIFabric/Date Time Pickers/MSDateTimePicker.swift new file mode 100644 index 0000000..3b9309c --- /dev/null +++ b/OfficeUIFabric/Date Time Pickers/MSDateTimePicker.swift @@ -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 + } + } +} diff --git a/OfficeUIFabric/Extensions/Calendar+Extensions.swift b/OfficeUIFabric/Extensions/Calendar+Extensions.swift index f38021a..6fe14cd 100644 --- a/OfficeUIFabric/Extensions/Calendar+Extensions.swift +++ b/OfficeUIFabric/Extensions/Calendar+Extensions.swift @@ -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 {