Skip to content

Commit

Permalink
Adds context menu to ArrivalDeparture rows in Stop Controller
Browse files Browse the repository at this point in the history
Fixes #55 - Add support for context menus on iOS 13
  • Loading branch information
aaronbrethorst committed Apr 13, 2020
1 parent 5c9a603 commit 9cd4a0a
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 31 deletions.
13 changes: 13 additions & 0 deletions OBAKit/ContextMenus/Previewable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Previewable.swift
// OBAKit
//
// Created by Aaron Brethorst on 4/12/20.
//

import UIKit

protocol Previewable: NSObjectProtocol {
func enterPreviewMode()
func exitPreviewMode()
}
30 changes: 29 additions & 1 deletion OBAKit/Controls/ListKit/CollectionController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public enum TableCollectionStyle {
}

/// Meant to be used as a child view controller. It hosts a `UICollectionView` plus all of the logic for using `IGListKit`.
public class CollectionController: UIViewController {
public class CollectionController: UIViewController, UICollectionViewDelegate {
private let application: Application
public let listAdapter: ListAdapter
public let style: TableCollectionStyle
Expand All @@ -35,6 +35,7 @@ public class CollectionController: UIViewController {

self.listAdapter.collectionView = collectionView
self.listAdapter.dataSource = dataSource
self.listAdapter.collectionViewDelegate = self
}

required init?(coder aDecoder: NSCoder) {
Expand Down Expand Up @@ -120,4 +121,31 @@ public class CollectionController: UIViewController {
collectionView.contentInset = UIEdgeInsets(top: collectionView.contentInset.top, left: 0, bottom: 0, right: 0)
collectionView.scrollIndicatorInsets = collectionView.contentInset
}

// MARK: - UICollectionViewDelegate

@available(iOS 13.0, *)
public func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
guard let provider = listAdapter.sectionController(forSection: indexPath.section) as? ContextMenuProvider else {
return nil
}

return provider.contextMenuConfiguration(forItemAt: indexPath)
}

@available(iOS 13.0, *)
public func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
guard
let viewController = animator.previewViewController,
let parent = parent
else { return }

animator.addCompletion {
if let previewable = viewController as? Previewable {
previewable.exitPreviewMode()
}

self.application.viewRouter.navigate(to: viewController, from: parent, animated: false)
}
}
}
11 changes: 11 additions & 0 deletions OBAKit/Controls/ListKit/OBAListSectionController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@ import Foundation
import IGListKit
import OBAKitCore

// MARK: - ContextMenuProvider

@available(iOS 13.0, *)
protocol ContextMenuProvider {
func contextMenuConfiguration(forItemAt indexPath: IndexPath) -> UIContextMenuConfiguration?
}

// MARK: - OBAListSectionControllerInitializer

protocol OBAListSectionControllerInitializer {
init(formatters: Formatters, style: TableCollectionStyle, hasVisualEffectBackground: Bool)
}

// MARK: - OBAListSectionController

/// An OBAKit-specific subclass of `ListSectionController` meant to be overriden instead of `ListSectionController`.
///
/// Provides easy access to the application-wide `formatters` object, along with the current view controller's table collection style.
Expand Down
52 changes: 51 additions & 1 deletion OBAKit/Stops/StopArrivalListItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ final class ArrivalDepartureSectionData: NSObject, ListDiffable {
let arrivalDeparture: ArrivalDeparture
let isAlarmAvailable: Bool
let selected: VoidBlock

var onCreateAlarm: VoidBlock?
var onShowOptions: VoidBlock?
var onAddBookmark: VoidBlock?
var onShareTrip: VoidBlock?

var previewDestination: (() -> UIViewController?)?

init(arrivalDeparture: ArrivalDeparture, isAlarmAvailable: Bool = false, selected: @escaping VoidBlock) {
self.arrivalDeparture = arrivalDeparture
Expand All @@ -37,7 +42,9 @@ final class ArrivalDepartureSectionData: NSObject, ListDiffable {

// MARK: - Controller

final class StopArrivalSectionController: OBAListSectionController<ArrivalDepartureSectionData>, SwipeCollectionViewCellDelegate {
final class StopArrivalSectionController: OBAListSectionController<ArrivalDepartureSectionData>,
ContextMenuProvider,
SwipeCollectionViewCellDelegate {

override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let object = sectionData else { fatalError() }
Expand Down Expand Up @@ -84,6 +91,49 @@ final class StopArrivalSectionController: OBAListSectionController<ArrivalDepart

return actions
}

// MARK: - Context Menu

@available(iOS 13.0, *)
func contextMenuConfiguration(forItemAt indexPath: IndexPath) -> UIContextMenuConfiguration? {
let previewProvider = { [weak self] () -> UIViewController? in
guard
let self = self,
let sectionData = self.sectionData
else { return nil }

return sectionData.previewDestination?()
}

return UIContextMenuConfiguration(identifier: nil, previewProvider: previewProvider) { [weak self] _ in
guard
let self = self,
let sectionData = self.sectionData
else { return nil }

var actions = [UIAction]()

if sectionData.isAlarmAvailable {
let alarm = UIAction(title: Strings.addAlarm, image: Icons.addAlarm) { _ in
sectionData.onCreateAlarm?()
}
actions.append(alarm)
}

let addBookmark = UIAction(title: Strings.addBookmark, image: Icons.bookmark) { _ in
sectionData.onAddBookmark?()
}
actions.append(addBookmark)

let shareTrip = UIAction(title: Strings.shareTrip, image: UIImage(systemName: "square.and.arrow.up")) { _ in
sectionData.onShareTrip?()
}
actions.append(shareTrip)

// Create and return a UIMenu with all of the actions as children
return UIMenu(title: "", children: actions)
}
}
}

// MARK: - View
Expand Down
27 changes: 21 additions & 6 deletions OBAKit/Stops/StopViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public class StopViewController: UIViewController,
AlarmBuilderDelegate,
AppContext,
BookmarkEditorDelegate,
Idleable,
ListAdapterDataSource,
ModalDelegate,
Idleable,
StopPreferencesDelegate {

public let application: Application
Expand Down Expand Up @@ -457,18 +457,33 @@ public class StopViewController: UIViewController,
private func buildArrivalDepartureSectionData(arrivalDeparture: ArrivalDeparture) -> ArrivalDepartureSectionData {
let alarmAvailable = canCreateAlarm(for: arrivalDeparture)

let data = ArrivalDepartureSectionData(
arrivalDeparture: arrivalDeparture,
isAlarmAvailable: alarmAvailable) { [weak self] in
let data = ArrivalDepartureSectionData(arrivalDeparture: arrivalDeparture, isAlarmAvailable: alarmAvailable) { [weak self] in
guard let self = self else { return }
self.application.viewRouter.navigateTo(arrivalDeparture: arrivalDeparture, from: self)
}

data.previewDestination = { [weak self] in
guard let self = self else { return nil }
let controller = TripViewController(application: self.application, arrivalDeparture: arrivalDeparture)
controller.enterPreviewMode()
return controller
}

data.onCreateAlarm = { [weak self] in
guard let self = self else { return }
self.addAlarm(arrivalDeparture: arrivalDeparture)
}

data.onAddBookmark = { [weak self] in
guard let self = self else { return }
self.addBookmark(arrivalDeparture: arrivalDeparture)
}

data.onShareTrip = { [weak self] in
guard let self = self else { return }
self.shareTripStatus(arrivalDeparture: arrivalDeparture)
}

data.onShowOptions = { [weak self] in
guard let self = self else { return }
self.showMoreOptions(arrivalDeparture: arrivalDeparture)
Expand Down Expand Up @@ -645,13 +660,13 @@ public class StopViewController: UIViewController,
public func showMoreOptions(arrivalDeparture: ArrivalDeparture) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)

actionSheet.addAction(title: OBALoc("stop_controller.add_bookmark", value: "Add Bookmark", comment: "Action sheet button title for adding a bookmark")) { [weak self] _ in
actionSheet.addAction(title: Strings.addBookmark) { [weak self] _ in
guard let self = self else { return }
self.addBookmark(arrivalDeparture: arrivalDeparture)
}

if application.features.deepLinking == .running {
actionSheet.addAction(title: OBALoc("stop_controller.share_trip", value: "Share Trip", comment: "Action sheet button title for sharing the status of your trip (i.e. location, arrival time, etc.)")) { [weak self] _ in
actionSheet.addAction(title: Strings.shareTrip) { [weak self] _ in
guard let self = self else { return }
self.shareTripStatus(arrivalDeparture: arrivalDeparture)
}
Expand Down
65 changes: 44 additions & 21 deletions OBAKit/Trip/TripViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,16 @@ import FloatingPanel
import OBAKitCore

class TripViewController: UIViewController,
AppContext,
FloatingPanelControllerDelegate,
Idleable,
MKMapViewDelegate,
AppContext {
Previewable {

public let application: Application

private let tripConvertible: TripConvertible

public var selectedStopTime: TripStopTime? {
didSet {
var animated = true
if isFirstStopTimeLoad {
animated = false
isFirstStopTimeLoad.toggle()
}
self.mapView.deselectAnnotation(oldValue, animated: animated)

guard
oldValue != self.selectedStopTime,
let selectedStopTime = self.selectedStopTime
else { return }

self.mapView.selectAnnotation(selectedStopTime, animated: animated)
}
}
private var isFirstStopTimeLoad = true

init(application: Application, tripConvertible: TripConvertible) {
self.application = application
self.tripConvertible = tripConvertible
Expand Down Expand Up @@ -82,7 +64,9 @@ class TripViewController: UIViewController,

loadData()

floatingPanel.addPanel(toParent: self)
if !isBeingPreviewed {
floatingPanel.addPanel(toParent: self)
}
}

override func viewWillAppear(_ animated: Bool) {
Expand Down Expand Up @@ -111,6 +95,24 @@ class TripViewController: UIViewController,
self.userActivity = activity
}

// MARK: Previewable

/// Set this to `true` before `viewDidLoad` to present the UI in a stripped-down 'preview mode'
/// suitable for display in a context menu.
var isBeingPreviewed = false

func enterPreviewMode() {
isBeingPreviewed = true
}

func exitPreviewMode() {
isBeingPreviewed = false

if isViewLoaded, floatingPanel.parent == nil {
floatingPanel.addPanel(toParent: self)
}
}

// MARK: - Idle Timer

public var idleTimerFailsafe: Timer?
Expand Down Expand Up @@ -355,4 +357,25 @@ class TripViewController: UIViewController,
default: return nil
}
}

public var selectedStopTime: TripStopTime? {
didSet {
guard !isBeingPreviewed else { return }

var animated = true
if isFirstStopTimeLoad {
animated = false
isFirstStopTimeLoad.toggle()
}
self.mapView.deselectAnnotation(oldValue, animated: animated)

guard
oldValue != self.selectedStopTime,
let selectedStopTime = self.selectedStopTime
else { return }

self.mapView.selectAnnotation(selectedStopTime, animated: animated)
}
}
private var isFirstStopTimeLoad = true
}
5 changes: 3 additions & 2 deletions OBAKit/ViewRouting/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ public class ViewRouter: NSObject, UINavigationControllerDelegate {
/// - Parameters:
/// - viewController: The 'to' view controller.
/// - fromController: The 'from' view controller.
public func navigate(to viewController: UIViewController, from fromController: UIViewController) {
/// - animated: Is the transition animated or not.
public func navigate(to viewController: UIViewController, from fromController: UIViewController, animated: Bool = true) {
assert(fromController.navigationController != nil)
viewController.hidesBottomBarWhenPushed = true
fromController.navigationController?.pushViewController(viewController, animated: true)
fromController.navigationController?.pushViewController(viewController, animated: animated)
}

public func navigateTo(stop: Stop, from fromController: UIViewController, bookmark: Bookmark? = nil) {
Expand Down

0 comments on commit 9cd4a0a

Please sign in to comment.