-
Notifications
You must be signed in to change notification settings - Fork 68
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
[Feature Request] replaceStack(with routes: [Route<Screen>], animated: Bool = true)
#21
Comments
Code example above can be slightly improved by use of modifiers and Group with _ConditionalContent: @main
struct ClipCardAnimationApp: SwiftUI.App {
@ObservedObject var auth = AuthState.shared
var body: some Scene {
WindowGroup {
Group {
if auth.isAuthenticated {
MainView()
} else {
WelcomeView()
}
}
.cardAnimation(isFaceUp: auth.isAuthenticated)
// Styling
.foregroundColor(.white)
.font(.largeTitle)
.buttonStyle(.borderedProminent)
}
}
}
struct CardAnimationModifier: ViewModifier {
let isFaceUp: Bool
let axis: Axis
func body(content: Content) -> some View {
return content
.rotation3DEffect(
Angle.degrees(isFaceUp ? 180 : 0),
axis: axis.value
)
.rotation3DEffect(
Angle.degrees(isFaceUp ? 180: 0),
axis: axis.value
)
.animation(.easeOut(duration: 1), value: isFaceUp)
}
}
public protocol ConditionalContentView {
associatedtype FalseContent
associatedtype TrueContent
}
extension _ConditionalContent: ConditionalContentView {}
extension Group where Content: ConditionalContentView & View, Content.FalseContent: View, Content.TrueContent: View {
func cardAnimation(isFaceUp: Bool, axis: Axis = .y) -> some View {
modifier(CardAnimationModifier(isFaceUp: isFaceUp, axis: axis))
}
} |
Thanks for raising this issue @Sajjon! This is a feature I've really wanted to add but I've hit issues. I'll document the issues and my progress here. Most updates to the route array (e.g. pushing and popping) will change its count, which will result in a push/pop animation. But sometimes, as you say, we might want to replace the top screen. Since the count stays the same there will be no navigation animation for these updates - it just cuts straight from screen A to screen B. I've always like UIKit's In theory we should be able to use SwiftUI transitions to handle animations in these cases, but I haven't found them easy to work with. I've tried the following (if it worked well, I would provide a nice API for it in this library): import SwiftUI
import FlowStacks
struct TransitionCoordinator: View {
enum ExampleScreen {
case one
case two
}
@State var routes: Routes<ExampleScreen> = [.root(.one)]
@State var transition: AnyTransition = .push
var body: some View {
NavigationView {
Router($routes) { screen, _ in
WithReplaceTransition(transition: transition) {
switch screen {
case .one:
VStack {
Text("1")
Button("Go to 2", action: goToTwo)
}
case .two:
VStack {
Text("2")
Button("Go to 1", action: goToOne)
}
}
}
}
}
}
func goToTwo() {
transition = .push
withAnimation {
self.routes = [.root(.two)]
}
}
func goToOne() {
transition = .pop
withAnimation {
self.routes = [.root(.one)]
}
}
}
struct WithReplaceTransition<C: View>: View {
var transition: AnyTransition
@ViewBuilder var content: () -> C
var body: some View {
// For some reason, transitions have no effect if we don't embed the content in a VStack.
VStack {
content()
// Make the content fill the available space (like a screen)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.transition(transition)
}
}
}
public extension AnyTransition {
static var pop: AnyTransition {
return .asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))
}
static var push: AnyTransition {
return .asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
}
} This works well when the screen you're replacing is the root of the navigation stack, but behaves strangely when replacing a screen that has itself been pushed, i.e. it works if the count is 1 but not when the count is more than one:, when it often fails to animate the update. I've tried reproducing in vanilla SwiftUI without FlowStacks and the issue goes away in that case, so there seems to be something about FlowStacks' setup that interferes with the transition. My next task is to figure out what. In the meantime, you should be able to use transitions for your use case, as you're only swapping out the root view. It should also be possible to design your own custom transitions using |
@johnpatrickmorgan Thx for your response! Yeah that solution works, just would be sooooo nice if we could get it out of the box with FlowStacks!
I hope you find it! Please feel free to share any WIP branch and I might take a look :) |
This works well for replacing with one screen. |
Hey! FlowStacks is great! I use it indirectly through [TCACoordinators[(https://github.com/johnpatrickmorgan/TCACoordinators) (so this Feature Request might spill over to TCACoordinators, depending on implementation?), however, it seems that one fundamental navigation primitive is missing!
When we completely replace the current navigation tree (navigation stack) with a new one, e.g. when the user completes some onboarding and ends with a signed in state, we want to replace the root (currently holding the root of the onboarding flow) with the root of main flow. We can do this using FlowStacks today simply by replacing the routes array with a new one, containing the root of main flow.:
However, this is not animated! In UIKit we can animate this kind of replacement of navigation stack using:
I've done this is in this UIKit app
This creates a pretty nice animation! It would be great if we could achieve something similar using FlowStacks (if possible in SwiftUI)
EDIT:
It would be nice with a "flipping card" animation, like this:
Screen.Recording.2022-04-19.at.18.12.57.mov
Here is the complete source code for the demo movie above.
The text was updated successfully, but these errors were encountered: