Skip to content

Commit

Permalink
Merge pull request #51 from SwiftfulThinking/master
Browse files Browse the repository at this point in the history
Master
  • Loading branch information
SwiftfulThinking committed Mar 2, 2024
2 parents ef3ef17 + b01ee01 commit dc16487
Show file tree
Hide file tree
Showing 16 changed files with 957 additions and 129 deletions.
16 changes: 16 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "SwiftfulRecursiveUI",
"repositoryURL": "https://github.com/SwiftfulThinking/SwiftfulRecursiveUI.git",
"state": {
"branch": null,
"revision": "d8de572e731d2f45a7abf05642893cb848bfec8c",
"version": "1.0.1"
}
}
]
},
"version": 1
}
7 changes: 4 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ let package = Package(
targets: ["SwiftfulRouting"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/SwiftfulThinking/SwiftfulRecursiveUI.git", from: "1.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "SwiftfulRouting",
dependencies: []),
dependencies: [
.product(name: "SwiftfulRecursiveUI", package: "SwiftfulRecursiveUI")
]),
.testTarget(
name: "SwiftfulRoutingTests",
dependencies: ["SwiftfulRouting"]),
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ Sample project: https://github.com/SwiftfulThinking/SwiftfulRoutingExample
<details>
<summary> Details (Click to expand) </summary>
<br>

SwiftUI is a declarative framework, and therefore, a SwiftUI router must be declarative by nature. Routers based on programatic code do not declare the view heirarchy in advance, but rather at the time of execution. The solution herein is to declare modifiers to support all possible routing in advance. The result is a Router struct that is fully decoupled from the View and added into the Environment on each screen.

As you segue to a new screen, the framework adds a set ViewModifers to the root of the destination View that will support all potential navigation routes. Currently, the framework can simultaneously support 1 active Segue, 1 active Alert, and 1 active Modal on each View in the heirarchy. The ViewModifiers are based on generic and/or type-erased destinations, which maintains a declarative view heirarchy while allowing the developer to still determine the destination at the time of execution.
Routers based on programatic code do not declare the view heirarchy in advance, but rather at the time of execution. However, SwiftUI is declarative, and so we must declare the view heirarchy in advance. The solution herein is to convert SwiftUI's declarative code to behave as programmatic code by connecting view modifiers to support the routing in advance.
<br>
<br>
As you segue to a new screen, the framework adds a set view modifiers to the root of the destination View that will support all potential navigation routes. The modifiers are based on generic and/or type-erased destinations, which maintains a declarative view heirarchy while allowing the developer to still determine the destination at the time of execution.
<br>
<br>

- The ViewModifiers are in `RouterView.swift -> body`.
- Accessible routing methods are in `AnyRouter.swift`.
Expand Down
193 changes: 193 additions & 0 deletions Sources/SwiftfulRouting/Components/ModalSupportView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//
// File.swift
//
//
// Created by Nick Sarno on 1/19/24.
//

import Foundation
import SwiftUI
import SwiftfulRecursiveUI

struct AnyModalWithDestination: Identifiable, Equatable {
let id: String
let configuration: ModalConfiguration
let destination: AnyDestination
private(set) var didDismiss: Bool = false

static func == (lhs: AnyModalWithDestination, rhs: AnyModalWithDestination) -> Bool {
lhs.id == rhs.id && lhs.didDismiss == rhs.didDismiss
}

mutating func dismiss() {
didDismiss = true
}

static var origin: AnyModalWithDestination {
AnyModalWithDestination(
id: "origin",
configuration: ModalConfiguration(
transition: .identity,
animation: .default,
alignment: .center,
backgroundColor: nil,
ignoreSafeArea: true
),
destination: AnyDestination(EmptyView())
)
}
}

struct ModalSupportView: View {

@State private var selection: AnyModalWithDestination? = nil

let transitions: [AnyModalWithDestination]
let onDismissModal: (AnyModalWithDestination) -> Void

var body: some View {
ZStack {
LazyZStack(allowSimultaneous: true, selection: selection, items: transitions) { data in
LazyZStack(allowSimultaneous: true, selection: true) { showView1 in
if showView1 {
data.destination.destination
.frame(configuration: data.configuration)
.transition(data.configuration.transition)
.zIndex(2)
} else {
if let backgroundColor = data.configuration.backgroundColor {
backgroundColor
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.transition(AnyTransition.opacity.animation(.easeInOut))
.onTapGesture {
onDismissModal(data)
}
.zIndex(1)
} else {
EmptyView()
}
}
}
}
.animation(transitions.last?.configuration.animation ?? .default, value: (selection?.id ?? "") + "\(transitions.count)")
}
.onFirstAppear {
selection = transitions.last
}
.onChange(of: transitions, perform: { newValue in
Task { @MainActor in
try? await Task.sleep(nanoseconds: 0)
if let new = newValue.last(where: { !$0.didDismiss }), self.selection?.id != new.id {
self.selection = new
}
}
})
}
}

public enum TransitionOption: String, CaseIterable {
case trailing, trailingCover, leading, leadingCover, top, topCover, bottom, bottomCover // identity //, scale, opacity, slide, slideCover

var insertion: AnyTransition {
switch self {
case .trailing, .trailingCover:
return .move(edge: .trailing)
case .leading, .leadingCover:
return .move(edge: .leading)
case .top, .topCover:
return .move(edge: .top)
case .bottom, .bottomCover:
return .move(edge: .bottom)
// case .scale:
// return .scale.animation(.default)
// case .opacity:
// return .opacity.animation(.default)
// case .slide, .slideCover:
// return .slide.animation(.default)
// case .identity:
// return .identity
}
}
//
// var removal: AnyTransition {
// switch self {
// case .trailingCover, .leadingCover, .topCover, .bottomCover:
// return AnyTransition.opacity.animation(.easeInOut.delay(1))
// case .trailing:
// return .move(edge: .leading)
// case .leading:
// return .move(edge: .trailing)
// case .top:
// return .move(edge: .bottom)
// case .bottom:
// return .move(edge: .top)
//// case .scale:
//// return .scale.animation(.easeInOut)
//// case .opacity:
//// return .opacity.animation(.easeInOut)
//// case .slide:
//// return .slide.animation(.easeInOut)
//// case .identity:
//// return .identity
//
// }
// }

var reversed: TransitionOption {
switch self {
case .trailing: return .leading
case .trailingCover: return .leading
case .leading: return .trailing
case .leadingCover: return .trailing
case .top: return .bottom
case .topCover: return .bottom
case .bottom: return .top
case .bottomCover: return .top
// case .identity: return .identity
}
}

var asAlignment: Alignment {
switch self {
case .trailing:
return .trailing
case .trailingCover:
return .trailing
case .leading:
return .leading
case .leadingCover:
return .leading
case .top:
return .top
case .topCover:
return .top
case .bottom:
return .bottom
case .bottomCover:
return .bottom
}
}

var asAxis: Axis.Set {
switch self {
case .trailing:
return .horizontal
case .trailingCover:
return .horizontal
case .leading:
return .horizontal
case .leadingCover:
return .horizontal
case .top:
return .vertical
case .topCover:
return .vertical
case .bottom:
return .vertical
case .bottomCover:
return .vertical
}
}
}

Loading

0 comments on commit dc16487

Please sign in to comment.