Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
helje5 committed May 1, 2022
2 parents 8fd9b35 + b151f52 commit 3699adc
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,19 @@ open class NavigationController<RootVC>: ViewController, _NavigationController
public let _rootViewController : RootVC

public var rootViewController : _ViewController { _rootViewController }


public enum NavigationViewStyle: Equatable {
case automatic

@available(iOS 13.0, tvOS 13.0, watchOS 7.0, *)
@available(macOS, unavailable)
case stack

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
case columns
}
@Published public var navigationViewStyle = NavigationViewStyle.automatic

public init(rootViewController: RootVC) {
self._rootViewController = rootViewController
markAsPresentingViewController()
Expand All @@ -161,13 +173,32 @@ open class NavigationController<RootVC>: ViewController, _NavigationController

// MARK: - View

public var view: some View {
private var _view: some View {
NavigationView {
_rootViewController.view
.controlled(by: _rootViewController)
.navigationTitle(_rootViewController.navigationTitle)
}
}
public var view: some View {
switch navigationViewStyle {
case .automatic :
_view
case .stack :
#if os(macOS)
_view
#else
_view.navigationViewStyle(.stack)
#endif
case .columns :
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
_view.navigationViewStyle(.columns)
}
else {
_view
}
}
}
}

public extension AnyViewController {
Expand Down
15 changes: 13 additions & 2 deletions Sources/ViewController/Debugging/HierarchyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ struct HierarchyView: View {

let title : String
let controllers : [ _ViewController ]
let active : _ViewController

private func titleForController(_ vc: _ViewController) -> String {
"\(vc.typeName)[\(vc.oidString)]"
}

var body: some View {
if controllers.count > 1 {
Expand All @@ -24,11 +29,17 @@ struct HierarchyView: View {
VStack(alignment: .leading, spacing: 12) {
// Don't do this at home
ForEach(Array(zip(controllers.indices, controllers)), id: \.0) {
( idx, parent ) in
( idx, vc ) in
HStack(alignment: .firstTextBaseline) {
Text("\(idx)")
Text(verbatim: "\(parent)")
Text(verbatim: titleForController(vc))
}
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder()
.padding(-8)
.opacity(vc === active ? 1.0 : 0.0)
)
}
}
.padding(.horizontal)
Expand Down
13 changes: 8 additions & 5 deletions Sources/ViewController/Debugging/ViewControllerInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct ViewControllerInfo: View {
let toRoot = sequence(first: viewController) {
$0.presentingViewController
}
.reversed()
if let presented = viewController.presentedViewController {
let downwards = sequence(first: presented) {
$0.presentedViewController
Expand All @@ -66,7 +67,7 @@ struct ViewControllerInfo: View {
return "AnyVC: " + addressString
}
else {
return String(describing: type(of: viewController))
return viewController.typeName
}
}

Expand Down Expand Up @@ -111,10 +112,12 @@ struct ViewControllerInfo: View {
}
.padding()

HierarchyView(title: "Parent Hierarchy",
controllers: parentHierarchy)
HierarchyView(title: "Presentation Hierarchy",
controllers: presentationHierarchy)
HierarchyView(title : "Parent Hierarchy",
controllers : parentHierarchy,
active : viewController)
HierarchyView(title : "Presentation Hierarchy",
controllers : presentationHierarchy,
active : viewController)

Spacer()
}
Expand Down
15 changes: 12 additions & 3 deletions Sources/ViewController/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@ import os

@usableFromInline
struct PrintLogger {

let level : OSLogType = .error // TODO: derive from LOGLEVEL env variable

static let logLevel : OSLogType = {
switch ProcessInfo.processInfo.environment["LOGLEVEL"]?.lowercased() {
case "error" : return .error
case "debug" : return .debug
case "fault" : return .fault
case "info" : return .info
default: return OSLogType.default
}
}()
var logLevel : OSLogType { Self.logLevel }

func log(_ level: OSLogType, _ prefix: String, _ message: () -> String) {
guard level.rawValue >= self.level.rawValue else { return }
guard level.rawValue >= self.logLevel.rawValue else { return }
print(prefix + message())
}

Expand Down
41 changes: 35 additions & 6 deletions Sources/ViewController/Presentations/AutoPresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,48 @@ internal struct AutoPresentationViewModifier<VC>: ViewModifier
@ObservedObject var presentingViewController : VC
let mode : ViewControllerPresentationMode

var body: some View {
// Keep a handle to the VC being presented. We do this to avoid issue #6,
// i.e. when a sheet is dismissed and transitions off-screen, the
// "presentedViewController" is already gone (dismissed).
// The `body` of this `Present` View would then evaluate to the
// `TypeMismatchInfoView` during the dismiss.
// So we keep the VC being presented around, to make sure we still have a
// handle for the content-view while it is being dismissed.
@State private var viewController : _ViewController?

private var activeVC: _ViewController? {
if let activeVC = viewController { return activeVC }

if let presentation =
presentingViewController.activePresentation(for: mode)
presentingViewController.activePresentation(for: mode)
{
let presentedViewController = presentation.viewController
// Note: Do not modify `@State` in here! (i.e. do not push to the
// `viewController` variable as part of the evaluation)
// This happens if the VC is getting presented.
return presentation.viewController
}

return nil
}

var body: some View {
if let presentedViewController = activeVC {
presentedViewController.anyControlledContentView
.environment(\.viewControllerPresentationMode, mode)
.navigationTitle(presentedViewController.navigationTitle)
.onAppear {
viewController = presentedViewController
}
.onDisappear { // This seems to be a proper onDidDisappear
viewController = nil
}
}
else {
TypeMismatchInfoView<AnyViewController, VC>(
parent: presentingViewController, expectedMode: mode
)
#if DEBUG
TypeMismatchInfoView<AnyViewController, VC>(
parent: presentingViewController, expectedMode: mode
)
#endif
}
}
}
Expand Down
18 changes: 15 additions & 3 deletions Sources/ViewController/Presentations/Presentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public extension _ViewController {

guard let presentation = self.activePresentation(for: mode) else {
// This is fine, could have happened by other means.
return logger.debug("Did not find VC to deactivate: \(self)")
return logger.debug("Did not find VC to deactivate in: \(self)")
}

assert(mode != .automatic)
Expand Down Expand Up @@ -154,7 +154,14 @@ public extension _ViewController {
// Only during presentation, we may need to dismiss the other type!
Binding(
get: {
self.activePresentations.contains(where: { $0.mode == mode })
if self.activePresentations.contains(where: { $0.mode == mode }) {
logger.debug("Is presenting in mode \(mode): \(self)")
return true
}
else {
logger.debug("Not presenting in mode \(mode): \(self)")
return false
}
},
set: { isShowing in
// We cannot make VCs "appear", that would require a factory.
Expand All @@ -178,7 +185,8 @@ public extension _ViewController {

guard let presentation = self.activePresentation(for: mode) else {
// This is fine, could have happened by other means.
return logger.debug("did not find VC to deactivate \(self)?")
return logger.debug(
"did not find VC for mode \(mode) to deactivate in: \(self)?")
}

/// If a mode was requested, make sure it is the right one.
Expand Down Expand Up @@ -262,6 +270,8 @@ public extension _ViewController {
where VC.ContentView == DefaultViewControllerView
{
// Requires a custom `PushPresentation` or `SheetPresentation`
logger.debug(
"Presenting(.custom) a VC w/o an explicit ContentView: \(viewController)")
defaultPresent(viewController, mode: .custom)
}

Expand Down Expand Up @@ -298,6 +308,8 @@ public extension _ViewController {
if VC.ContentView.self == DefaultViewControllerView.self {
// Requires an explicit ``PushPresentation`` or ``SheetPresentation`` in
// the associated `View`.
logger.debug(
"modalPresentationMode(.custom) for custom VC: \(viewController)")
return .custom
}

Expand Down
25 changes: 20 additions & 5 deletions Sources/ViewController/ViewController/DefaultDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
// Copyright © 2022 ZeeZide GmbH. All rights reserved.
//

extension _ViewController {

internal var oidString : String {
String(UInt(bitPattern: ObjectIdentifier(self)), radix: 16)
}

internal var typeName : String { String(describing: type(of: self)) }
}

extension ViewController { // MARK: - Description

@inlinable
public var description: String {
let addr = String(UInt(bitPattern: ObjectIdentifier(self)), radix: 16)
var ms = "<\(type(of: self))[\(addr)]:"
var ms = "<\(typeName)[\(oidString)]:"
appendAttributes(to: &ms)
ms += ">"
return ms
Expand All @@ -22,14 +29,22 @@ extension ViewController { // MARK: - Description
defaultAppendAttributes(to: &description)
}

@inlinable
public func defaultAppendAttributes(to description: inout String) {
// public, so that subclasses can call this "super" implementation!
if let v = title { description += " '\(v)'" }
assert(self !== presentedViewController)

if activePresentations.count == 1, let v = activePresentations.first {
description += " presenting=\(v.viewController)[\(v.mode)]"
let vc = "\(v.viewController.typeName)[\(v.viewController.oidString)]"
switch v.mode {
case .automatic:
assertionFailure("Unexpected presentation mode: \(v)")
description += " presenting[AUTO!!]=\(vc)"
case .custom : description += " presenting[CUSTOM]=\(vc)"
case .sheet : description += " presenting[sheet]=\(vc)"
case .navigation : description += " presenting[nav]=\(vc)"
case .pushLink : description += " presenting[link]=\(vc)"
}
}
else if !activePresentations.isEmpty {
description += " presenting=#\(activePresentations.count)"
Expand Down
4 changes: 2 additions & 2 deletions Sources/ViewController/ViewControllerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ extension EmptyView: ViewControllerView {}
Label("Missing VC View", systemImage: "questionmark.circle")
Spacer()
if let viewController = viewController {
Text(verbatim: "Class: \(type(of: viewController))")
Text(verbatim: "ID: \(ObjectIdentifier(viewController))")
Text(verbatim: "Class: \(viewController.typeName)")
Text(verbatim: "ID: \(viewController.oidString)")
Text(verbatim: "\(viewController)")
}
else {
Expand Down

0 comments on commit 3699adc

Please sign in to comment.