Skip to content

Commit

Permalink
Add more documentation, comments
Browse files Browse the repository at this point in the history
Minor API change:
- `presentedViewController(of:mode:)` drops the `of` label
  • Loading branch information
helje5 committed Apr 30, 2022
1 parent ffbf9ed commit 5034037
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 36 deletions.
30 changes: 30 additions & 0 deletions Sources/ViewController/NavigationLink/PushLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,28 @@ public struct PushLink<VC, CV, Label>: View
logger.debug("PushLink[dismiss]: done: \(activeVC.description)")
}

/**
* Returns a `Binding<Bool>` that handles presentation and dismiss of an
* associated ``ViewController``.
*
* That is:
* - It returns `true` if the link has an associated ``ViewController``
* that is being presented.
* - If it is set to `true`:
* - If a ``ViewController`` is already presented by this link, it stays
* presented.
* - If no ``ViewController`` is being presented yet, this will construct
* and present a new associated ``ViewController``
* - If it is set to `false` (i.e. the user navigated away from the
* destination), the link's ViewController gets dismissed.
*
* Note: When switching between two `NavigationLink`s, SwiftUI can set the
* Binding for the new controller to `true`, before the Binding of the
* old controller was set to `false`. PushLink deals w/ that and
* dismisses old VCs before presenting a new.
*
* - Returns: A Binding that controls whether the PushLink is active.
*/
private var isActiveBinding: Binding<Bool> {
Binding(
get: {
Expand All @@ -170,6 +192,14 @@ public struct PushLink<VC, CV, Label>: View
)
}

/**
* Returns a View representing the destination of the `NavigationLink`.
*
* Which is usually going to be the `contentView` of the destination
* ``ViewController``, bound to the same.
* This also pushes the ``ViewController/navigationTitle`` to the
* SwiftUI environment.
*/
@ViewBuilder private var destination: some View {
if let activeVC = childViewController {
if let presentedVC = activeVC as? VC {
Expand Down
4 changes: 3 additions & 1 deletion Sources/ViewController/Presentations/AutoPresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import SwiftUI
* It watches the current VC to detect presentation changes,
* and binds the sheet/navlink to the respective mode.
*/
struct AutoPresentationViewModifier<VC>: ViewModifier where VC: ViewController {
internal struct AutoPresentationViewModifier<VC>: ViewModifier
where VC: ViewController
{

@ObservedObject var viewController : VC

Expand Down
107 changes: 76 additions & 31 deletions Sources/ViewController/Presentations/Presentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,72 @@ public extension ViewController {
}
}


// MARK: - Lookup Presentations
public extension _ViewController {


/**
* Returns the active ``ViewControllerPresentation`` for a given
* ``ViewController/PresentationMode`` (or the first, if no mode
* is specified.
*
* - Parameter mode: An optional presentation mode that has to match.
* - Returns: An active presentation for the mode, if there is one.
* Or the first active presentation if `mode` is `nil`.
*/
@inlinable
func activePresentation(for mode: PresentationMode?)
-> ViewControllerPresentation?
{
guard let mode = mode else { return activePresentations.first }
return activePresentations.first(where: { $0.mode == mode })
}

/**
* Returns the active ``ViewControllerPresentation`` for a specific
* ``ViewController`` object.
*
* - Parameter presentedViewController: The ``ViewController`` to check for.
* - Returns: A presentation for the ``ViewController``, if it is indeed
* being presented.
*/
@inlinable
func activePresentation(for presentedViewController: _ViewController)
-> ViewControllerPresentation?
{
activePresentations.first { $0.viewController === presentedViewController }
}

// MARK: - Bindings
/**
* Lookup a presented ``ViewController`` of a particular type. Returns nil
* if there is none such (or the mode doesn't match).
*
* Example:
* ```swift
* let settingsVC = presentedViewController(Settings.self)
* ```
*
* - Parameters:
* - type: The type of the ViewController to lookup
* - mode: Optionally the mode the viewcontroller is presented in
* (e.g. `sheet`, `navigation` or `custom`)
* - Returns: A ``ViewController`` of the specified type, if one exists.
*/
@inlinable
func presentedViewController<VC>(_ type: VC.Type,
mode: ViewControllerPresentationMode?)
-> VC?
where VC: ViewController
{
guard let presentation = activePresentation(for: mode) else { return nil }
if let mode = mode, mode != presentation.mode { return nil }
return presentation.viewController as? VC
}
}


// MARK: - Bindings
public extension _ViewController {

/**
* This allows us to check whether a particular VC is being presented,
Expand Down Expand Up @@ -83,8 +134,17 @@ public extension _ViewController {
}

/**
* Only checks whether a specific mode is active. This is used for the
* internally supported "auto" modes (`.sheet` and `.navigation`).
* A Binding that represents whether a presentation in a particular mode is
* active (e.g. `sheet`, `navigation` or `custom`).
*
* Used for the internally supported "auto" modes (`.sheet` and
* `.navigation`).
*
* - Parameters:
* - mode: The mode the viewcontroller is presented in
* (e.g. `sheet`, `navigation` or `custom`)
* - Returns: A `Bool` `Binding` that can be used w/ an `isActive` parameter
* of a `sheet` or `NavigationLink`.
*/
@inlinable
func isPresentingMode(_ mode: ViewControllerPresentationMode)
Expand Down Expand Up @@ -133,34 +193,22 @@ public extension _ViewController {
}
)
}

/**
* Lookup a presented ``ViewController`` of a particular type. Returns nil
* if there is none such (or the mode doesn't match)
*
* E.g. this is used by the SheetPresentation.
*/
@inlinable
func presentedViewController<VC>(of type: VC.Type,
mode: ViewControllerPresentationMode?)
-> VC?
where VC: ViewController
{
guard let presentation = activePresentation(for: mode) else { return nil }
if let mode = mode, mode != presentation.mode { return nil }
return presentation.viewController as? VC
}


/**
* This allows us to check whether a particular type of VC is being presented,
* e.g. in case the presentation should be done differently (e.g. sheet vs
* navigation).
* A Binding that represents whether a particular type of ``ViewController``
* is being presented.
*
* CAREFUL: This only checks the type, there could be multiple presentations
* with the same type! (leading to multiple Bindings being true,
* and different ContentViews being active, potentially capturing the
* wrong environment).
*
* - Parameters:
* - type: The type of the ViewController to lookup
* - mode: Optionally the mode the viewcontroller is presented in
* (e.g. `sheet`, `navigation` or `custom`)
* - Returns: A `Bool` `Binding` that can be used w/ an `isActive` parameter
* of a `sheet` or `NavigationLink`.
*/
func isPresenting<VC>(_ controllerType: VC.Type,
mode: ViewControllerPresentationMode?)
Expand All @@ -169,9 +217,10 @@ public extension _ViewController {
{
isPresenting(mode: mode) { $0 is VC }
}
}


// MARK: - API Methods
// MARK: - API Methods
public extension _ViewController {

@inlinable
func show<VC: ViewController>(_ viewController: VC) {
Expand Down Expand Up @@ -208,10 +257,6 @@ public extension _ViewController {
defaultPresent(viewController, mode: mode)
}

/**
* Present a ``ViewController`` that doesn't have a
* ``ViewController/ContentView`` assigned.
*/
@inlinable
func present<VC: ViewController>(_ viewController: VC)
where VC.ContentView == DefaultViewControllerView
Expand Down
10 changes: 6 additions & 4 deletions Sources/ViewController/Presentations/PresentationMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ public enum ViewControllerPresentationMode: Hashable {
// FIXME: Used in two different ways, for accessing the actual presentation,
// and for deciding what presentation to use.

/// The ``ViewController`` will decide on an appropriate presentation mode.
/**
* The ``ViewController`` will decide on an appropriate presentation mode.
*/
case automatic

/**
* The ``ViewController`` won't do the presentation automagically,
* the user needs to handle the presentation explicitly.
* E.g. using `presentAsSheet()` or `presentInNavigation()`, or in a
* completely manual way.
* the user needs to handle it explicitly
* (e.g. using the `.sheet` modifier or a programmatic `NavigationLink` with
* the `isActive` bound to the `presentedViewController`).
*/
case custom

Expand Down
36 changes: 36 additions & 0 deletions Sources/ViewController/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,47 @@ public protocol ViewController: _ViewController, ObservableObject, Identifiable
func present<VC: ViewController>(_ viewController: VC)
where VC.ContentView == DefaultViewControllerView

/**
* Present the ``ViewController`` in a context aware mode.
* E.g. if it is within a ``NavigationController``, it'll get presented as
* a navigation.
* By default ``ViewController``s are presented as sheets.
*
* - Parameter viewController: The ``ViewController`` to present.
*/
func show<VC: ViewController>(_ viewController: VC)
/**
* Present a ``ViewController`` that doesn't specify an explicit
* ``ViewController/ContentView`` type (i.e. doesn't implement `view` or
* typealias/nest a `ContentView` type).
* Unless specified otherwise in the presentationMode, this will end up in
* a ``ViewControllerPresentationMode/custom`` (i.e. the user has to deal
* with the presentation himself).
*
* - Parameter viewController: The ``ViewController`` to present.
*/
func show<VC: ViewController>(_ viewController: VC)
where VC.ContentView == DefaultViewControllerView

/**
* Present the ``ViewController`` in a context aware, "detail", mode.
*
* If the container ViewController doesn't support an explicit "detail" mode,
* this acts like ``ViewController/show``.
*
* - Parameter viewController: The ``ViewController`` to present.
*/
func showDetail<VC: ViewController>(_ viewController: VC)
/**
* Present a ``ViewController`` that doesn't specify an explicit
* ``ViewController/ContentView`` type (i.e. doesn't implement `view` or
* typealias/nest a `ContentView` type).
* Unless specified otherwise in the presentationMode, this will end up in
* a ``ViewControllerPresentationMode/custom`` (i.e. the user has to deal
* with the presentation himself).
*
* - Parameter viewController: The ``ViewController`` to present.
*/
func showDetail<VC: ViewController>(_ viewController: VC)
where VC.ContentView == DefaultViewControllerView

Expand Down

0 comments on commit 5034037

Please sign in to comment.