Created
June 28, 2022 19:52
-
-
Save shengchl/abfee38030e844897f1ef2de31353474 to your computer and use it in GitHub Desktop.
SwiftUI transition modifier with advanced behavior
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 2022 (c) Alexey Grigorev, Ivan Oparin | |
// licensed under MIT | |
import SwiftUI | |
internal struct FixedTransactionTransition: ViewModifier { | |
@Binding var isPresentInParentContainer: Bool | |
@Binding var isPresentInBody: Bool | |
let transition: AnyTransition | |
let animation: Animation | |
func body(content: Content) -> some View { | |
VStack(spacing: 0) { | |
if isPresentInParentContainer, isPresentInBody { | |
content | |
.transaction { | |
$0.animation = animation | |
} | |
.transition(transition) | |
} else { | |
// keeps layout intact | |
content.opacity(0) | |
} | |
} | |
.onAppear { | |
isPresentInBody = true | |
} | |
} | |
} | |
public extension View { | |
/// A convinient way to apply transition to a view with advanced behavior that fixes certain edge cases (or bugs) of default SwiftUI transitions. | |
/// - Parameter transition: a transition associated with a state-processing update. Do not apply animation within **this** parameter. | |
/// - Parameter animation: animation associated with a transition. Overrides any other animation associated with a transaction. | |
/// - Parameter isPresentInBody: a toggle that will automatically trigger a transition on view appear. Must be associated with a hosting view (e.g. a State property on a view) | |
/// - Parameter isPresentInParentContainer: advanced toggle that must provide a context of the current state-processing update. See discussion for more info. | |
/// | |
/// With the basic usage the modifier will just apply a provided transition with specified animation to a view. | |
/// When you have a complex view-hierarchy with a branched structure which switches depending on context, and you need to provide transitions to specific elements | |
/// withing the branch, SwiftUI will animate the transition of the whole branch and willl not propagate information about context switching deeper into hierarchy, i.e. views | |
/// will not know they were out of active hierachy and transitions will not occur. | |
/// | |
/// To fix this behavior, switch the branch within an animation block and toggle a proxy propery that holds a boolean information about branch presence in active | |
/// view-hierarchy. It's important to switch branches within an animation block **and** pass the proxy as a binding, otherwise SwiftUI will lose the context associated with the transition | |
/// and it will not be applied properly. | |
/// | |
/// @State private var branchAProxy = false | |
/// @State private var branchBProxy = false | |
/// var body: some View { | |
/// VStack(spacing: 0) { | |
/// switch currentBranch { | |
/// case .branchA: | |
/// ViewHierarchyA(isPresentInParentContainer: $branchAProxy) | |
/// case .branchB: | |
/// ViewHierarchyB(isPresentInParentContainer: $branchBProxy) | |
/// } | |
/// } | |
/// .onAppear { | |
/// setBranch(.branchA, animation: .linear) | |
/// } | |
/// } | |
/// | |
/// private func setBranch(_ branch: Branch, animation: Animation) { | |
/// withAnimation(animation) { | |
/// self.currentBranch = branchpage | |
/// self.branchAProxy = (branch == .branchA) | |
/// self.branchBProxy = (page == .branchB) | |
/// } | |
/// } | |
/// | |
/// // within ViewHierarchyA / ViewHierarchyB apply this modifier to individual elements | |
/// | |
func transition( | |
_ transition: AnyTransition, | |
animation: Animation = .default, | |
isPresentInBody: Binding<Bool>, | |
isPresentInParentContainer: Binding<Bool> = .constant(true) | |
) -> some View { | |
self.modifier( | |
FixedTransactionTransition( | |
isPresentInParentContainer: isPresentInParentContainer, | |
isPresentInBody: isPresentInBody, | |
transition: transition, | |
animation: animation | |
) | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
updated version with internal toggle moved to view-modifier