Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
helje5 committed Apr 30, 2022
2 parents a7d5ad0 + b8dd0ce commit 8fd9b35
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 394 deletions.
28 changes: 27 additions & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- cron: "0 9 * * 1"

jobs:
nextstep:
SwiftPackage:
runs-on: macos-latest
steps:
- name: Select latest available Xcode
Expand All @@ -20,3 +20,29 @@ jobs:
run: swift build -c debug
- name: Build Swift Release Package
run: swift build -c release
iOS:
runs-on: macos-latest
steps:
- name: Select latest available Xcode
uses: maxim-lobanov/[email protected]
with:
xcode-version: 13.2
- name: Checkout Repository
uses: actions/checkout@v2
- name: Prerequisites
run: gem install xcpretty
- name: Build
run: set -o pipefail; xcodebuild -scheme ViewController-iOS build | xcpretty --color
NeXTstep:
runs-on: macos-latest
steps:
- name: Select latest available Xcode
uses: maxim-lobanov/[email protected]
with:
xcode-version: 13.2
- name: Checkout Repository
uses: actions/checkout@v2
- name: Prerequisites
run: gem install xcpretty
- name: Build
run: set -o pipefail; xcodebuild -scheme ViewController-macOS build | xcpretty --color
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

ViewController's for SwiftUI.

WIP.

The core idea is that the `ViewController` is owning, or at least driving,
the View(s). Not the other way around.

Blog entry explaining all the things:
[Model View Controller for SwiftUI](http://www.alwaysrightinstitute.com/viewcontroller/)

## How to Use
## Quick: How to Use

More details will be posted but to get started.
Just the basics to get started quickly.

### Step A: Setup Project and Root VC

Expand Down
59 changes: 51 additions & 8 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,17 +192,38 @@ 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 presentedVC = parentViewController
.presentedViewController(of: VC.self, mode: mode)
{
contentView
.controlled(by: presentedVC)
.environment(\.viewControllerPresentationMode, .navigation)
.navigationTitle(presentedVC.navigationTitle)
if let activeVC = childViewController {
if let presentedVC = activeVC as? VC {
if let presentation =
parentViewController.activePresentation(for: presentedVC),
presentation.mode == mode
{
contentView
.controlled(by: presentedVC)
.environment(\.viewControllerPresentationMode, .navigation)
.navigationTitle(presentedVC.navigationTitle)
}
else {
SwiftUI.Label("Error: The linked VC is not being presented as a link",
systemImage: "exclamationmark.triangle")
}
}
else {
SwiftUI.Label("Error: The linked VC has an unexpected type!",
systemImage: "exclamationmark.triangle")
}
}
else {
SwiftUI.Label("Error: Missing/wrong presented VC",
SwiftUI.Label("Linked VC is not yet being presented.",
systemImage: "exclamationmark.triangle")
}
}
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
Loading

0 comments on commit 8fd9b35

Please sign in to comment.