-
Notifications
You must be signed in to change notification settings - Fork 310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make FeedbackViewController public #1605
Conversation
guard let strongSelf = self else { return } | ||
|
||
// todo, can this be moved as a delegate on this view controller? | ||
//strongSelf.delegate?.mapViewController(strongSelf, didSendFeedbackAssigned: uuid, feedbackType: item.feedbackType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to make a protocol for FeedbackViewController here? This would remove some plumbing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this delegate method even helpful? It only returns the uuid and the feedback type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make it useful, pass in the feedback item itself instead of the feedback type.
} | ||
|
||
strongSelf.dismiss(animated: true) { | ||
DialogViewController().present(on: parent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason, parent is nil here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be because you're using a custom transitioning coordinator. If so, you may need to manually add the child VC and do all the lifecycle methods, such as UIViewController.willMove(toParentViewController:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JThramer solved with bc32b5e#diff-a25e8b3bfe9ceec368b83a004a6d1708R200. How do you feel about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah. That makes a lot more sense. 👍
MapboxNavigation/FeedbackItem.swift
Outdated
@@ -7,7 +7,7 @@ extension UIImage { | |||
} | |||
} | |||
|
|||
struct FeedbackItem { | |||
public struct FeedbackItem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that a Swift struct can’t bridge to Objective-C, nor can any method or property that uses that type.
@@ -3,29 +3,25 @@ import MapboxCoreNavigation | |||
import AVFoundation | |||
|
|||
extension FeedbackViewController: UIViewControllerTransitioningDelegate { | |||
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |||
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we make these methods public, we’ll need to give them less awkward names (forDismissing
?) and documentation and consider making them accessible to Objective-C.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noting, we have no control over the naming conventions of this method since it's a func defined on UIViewControllerTransitioningDelegate
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it is! 😄 (By the way, the Objective-C method name looks fine; it’s just the Swift name that’s funky.) Oh well, carry on.
override func viewDidLoad() { | ||
var eventsManager: EventsManager | ||
|
||
public init(for eventsManager: EventsManager) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a more descriptive argument label: init(eventsManager:)
.
} | ||
|
||
required public init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this method be implemented?
@@ -31,6 +33,8 @@ class CustomViewController: UIViewController, MGLMapViewDelegate { | |||
|
|||
mapView.delegate = self | |||
mapView.compassView.isHidden = true | |||
|
|||
feedbackViewController = FeedbackViewController(eventsManager: routeController.eventsManager) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Postpone initializing the FeedbackViewController until we present it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is that? Is it really that expensive to just keep it in memory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not that expensive, I'd guess a few MBs but it's good practice not to allocate view controllers, views, and objects in general before use, unless it needs to be pre-cached.
In this case, the feedback view controller is not always going to be presented, e.g. if you cancel navigation or never reach the destination, making it excessive to keep in memory during the whole session.
Also, some settings or state might change after the view is loaded, e.g. landscape orientation, or accessibility settings. Then the view could've been created w/ an incorrect draggable height, font settings etc...
This is ready for review. |
MapboxNavigation/FeedbackItem.swift
Outdated
@@ -7,7 +7,8 @@ extension UIImage { | |||
} | |||
} | |||
|
|||
struct FeedbackItem { | |||
@objc(MBFeedbackItem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class needs documentation.
MapboxNavigation/FeedbackItem.swift
Outdated
@@ -7,7 +7,8 @@ extension UIImage { | |||
} | |||
} | |||
|
|||
struct FeedbackItem { | |||
@objc(MBFeedbackItem) | |||
public class FeedbackItem: NSObject { | |||
var title: String | |||
var image: UIImage | |||
var feedbackType: FeedbackType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do any of these properties need to be public? What about the initializer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the initializer should be enough, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initializer is now public, but the arguments passed into the initializer are thereafter inaccessible to client code, making the feedback item something of a dropbox. I think we should make these three properties public for consistency.
/** | ||
The feedback items that are visible and selectable by the user. | ||
*/ | ||
public var sections: [[FeedbackItem]] = [[.turnNotAllowed, .closure, .reportTraffic, .confusingInstructions, .generalMapError, .badRoute]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this an array nested inside an array? What does the outer array represent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nested sections were previously used to forcefully use one or two rows based on device orientation. This is being handled automatically now:
mapbox-navigation-ios/MapboxNavigation/FeedbackViewController.swift
Lines 287 to 289 in 4fa5157
let width = traitCollection.verticalSizeClass == .compact | |
? floor(availableWidth / CGFloat(items.count)) | |
: floor(availableWidth / CGFloat(items.count / 2)) |
So should be fine to flatten [[FeedbackItem]]
to [FeedbackItem]
and always use the first section. However, keeping the current structure is a bit more versatile.
@@ -71,7 +75,33 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |||
return fullHeight | |||
} | |||
|
|||
override func viewDidLoad() { | |||
var eventsManager: EventsManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property (or at least its getter) should be public. If application code can pass an EventsManger into the initializer, it should be able to see which EventsManager the object wound up with.
@@ -165,10 +202,48 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |||
progressBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true | |||
progressBar.bottomAnchor.constraint(equalTo: view.safeBottomAnchor).isActive = true | |||
} | |||
|
|||
func defaultFeedbackHandlers(source: FeedbackSource = .user) -> (send: (FeedbackItem) -> Void, dismiss: () -> Void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline this method into the one call site, to avoid the cognitive overhead of passing these closures around.
@1ec5 updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To-do items need to be addressed or removed before merging.
MapboxNavigation/FeedbackItem.swift
Outdated
@@ -7,7 +7,8 @@ extension UIImage { | |||
} | |||
} | |||
|
|||
struct FeedbackItem { | |||
@objc(MBFeedbackItem) | |||
public class FeedbackItem: NSObject { | |||
var title: String | |||
var image: UIImage | |||
var feedbackType: FeedbackType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initializer is now public, but the arguments passed into the initializer are thereafter inaccessible to client code, making the feedback item something of a dropbox. I think we should make these three properties public for consistency.
MapboxNavigation/FeedbackItem.swift
Outdated
/** | ||
Creates a new `FeedbackItem`. | ||
*/ | ||
public init(title: String, image: UIImage, feedbackType: FeedbackType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add @objc
to make this initializer accessible to Objective-C code.
guard let strongSelf = self else { return } | ||
|
||
// todo, can this be moved as a delegate on this view controller? | ||
//strongSelf.delegate?.mapViewController(strongSelf, didSendFeedbackAssigned: uuid, feedbackType: item.feedbackType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make it useful, pass in the feedback item itself instead of the feedback type.
@@ -165,12 +205,42 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |||
progressBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true | |||
progressBar.bottomAnchor.constraint(equalTo: view.safeBottomAnchor).isActive = true | |||
} | |||
|
|||
func defaultSendFeedbackHandler(source: FeedbackSource = .user, uuid: UUID) -> (FeedbackItem) -> Void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There’s only one call site and it doesn’t specify the source
argument, so inline the default .user
value below.
|
||
let uuid = eventsManager.recordFeedback() | ||
sendFeedbackHandler = defaultSendFeedbackHandler(uuid: uuid) | ||
dismissFeedbackHandler = defaultDismissFeedbackHandler(uuid: uuid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of currying closures within defaultSendFeedbackHandler(uuid:)
and defaultDismissFeedbackHandler(uuid:)
, pull those methods into simple send(_:withUUID:)
and dismiss(_:)
methods and store uuid
on the class instead. Then, instead of calling sendFeedbackHandler
, call send(_:withUUID:)
.
@@ -39,6 +57,8 @@ public class FeedbackViewController: UIViewController, DismissDraggable, UIGestu | |||
*/ | |||
public var sections: [FeedbackItem] = [.turnNotAllowed, .closure, .reportTraffic, .confusingInstructions, .generalMapError, .badRoute] | |||
|
|||
public var delegate: FeedbackViewControllerDelegate? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: weak
@@ -165,12 +227,32 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |||
progressBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true | |||
progressBar.bottomAnchor.constraint(equalTo: view.safeBottomAnchor).isActive = true | |||
} | |||
|
|||
func sendFeedback(_ item: FeedbackItem) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: send(_:)
is the more Swiftian way of naming it these days, or sendFeedbackItem(_:)
if there’s a conflict with another send(_:)
.
} | ||
} | ||
|
||
func defaultDismissFeedback() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just dismissFeedbackItem()
, no longer “default”.
/** | ||
Called when a `FeedbackViewController` is dismissed for any reason without giving explicit feedback. | ||
*/ | ||
@objc optional func feedbackViewControllerDidCancelFeedback(_ feedbackViewController: FeedbackViewController) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to pass in the FeedbackItem so the application can distinguish between cancellations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Negative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then feedbackViewControllerDidCancel(_:)
.
/** | ||
Called when the user submits a feedback event. | ||
*/ | ||
@objc optional func feedbackViewController(_ feedbackViewController: FeedbackViewController, didSendFeedbackAssigned: UUID, feedback: FeedbackItem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name this method feedbackViewController(_:didSend:uuid:)
in Swift and -feedbackViewController:didSendFeedbackItem:UUID:
in Objective-C.
/** | ||
Called when the user opens the feedback form. | ||
*/ | ||
@objc optional func feedbackViewControllerDidOpenFeedback(_ feedbackViewController: FeedbackViewController) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name this method feedbackViewControllerDidOpen(_:)
, since it gets called when opening the form, not when opening a specific piece of feedback.
/** | ||
Called when the user submits a feedback event. | ||
*/ | ||
@objc optional func feedbackViewController(_ feedbackViewController: FeedbackViewController, didSend: FeedbackItem, UUID: UUID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Distinguish between the argument label and argument name:
didSend feedbackItem: FeedbackItem
- The Swift name is currently
feedbackViewController(_:didSend:UUID:)
; it should befeedbackViewController(_:didSend:uuid:)
- The Objective-C name is currently
-feedbackViewController:didSend:UUID:
; it should be-feedbackViewController:didSendFeedbackItem:UUID:
/** | ||
Called when a `FeedbackViewController` is dismissed for any reason without giving explicit feedback. | ||
*/ | ||
@objc optional func feedbackViewControllerDidCancelFeedback(_ feedbackViewController: FeedbackViewController) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then feedbackViewControllerDidCancel(_:)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to mention any new APIs in the changelog.
This PR makes the FeedbackViewController public for developers to show within their custom navigation UI.
todo:
self.parent
is nil in many instances./cc @mapbox/navigation-ios