SwiftUI 动态添加子视图但动画不起作用
SwiftUI add subview dynamically but the animation doesn't work
我想在 SwiftUI 中创建一个动态添加子视图并带有动画的视图。
struct ContentView : View {
@State private var isButtonVisible = false
var body: some View {
VStack {
Toggle(isOn: $isButtonVisible.animation()) {
Text("add view button")
}
if isButtonVisible {
AnyView(DetailView())
.transition(.move(edge: .trailing))
.animation(Animation.linear(duration: 2))
}else{
AnyView(Text("test"))
}
}
}
}
上面的代码可以很好地处理动画。然而,当我将视图选择部分移动到一个函数中时,动画不再起作用(因为我想动态添加不同的视图,因此,我将逻辑放在一个函数中。)
struct ContentView : View {
@State private var isButtonVisible = false
var body: some View {
VStack {
Toggle(isOn: $isButtonVisible.animation()) {
Text("add view button")
}
subView().transition(.move(edge: .trailing))
.animation(Animation.linear(duration: 2))
}
func subView() -> some View {
if isButtonVisible {
return AnyView(DetailView())
}else{
return AnyView(Text("test"))
}
}
}
我看起来完全一样,但是,我不明白为什么他们有不同的结果。有人可以向我解释为什么吗?还有更好的解决方案吗?非常感谢!
这是您的代码,经过修改后可以正常工作:
struct ContentView : View {
@State private var isButtonVisible = false
var body: some View {
VStack {
Toggle(isOn: $isButtonVisible.animation()) {
Text("add view button")
}
subView()
.transition(.move(edge: .trailing))
.animation(Animation.linear(duration: 2))
}
}
func subView() -> some View {
Group {
if isButtonVisible {
DetailView()
} else {
Text("test")
}
}
}
}
注意两点:
- 你上面的两个例子是不同的,这就是为什么你得到不同的结果。第一个应用过渡和动画到 DetailView,然后用 AnyView 类型擦除它。第二种类型使用 AnyView 擦除 DetailView,然后应用过渡和动画。
- 而不是使用 AnyView 和类型擦除,我更喜欢将条件逻辑封装在
Group
视图中。然后你 return 的类型是 Group
,它将正确地动画。
- 如果您想在子视图的两种可能性上使用不同的动画,您现在可以将它们直接应用于
DetailView()
或 Text("test")
。
更新
Group
方法仅适用于 if
、elseif
和 else
语句。如果要使用开关,则必须将每个分支包装在 AnyView()
中。然而,这打破了 transitions/animations。目前无法使用 switch
和 设置自定义动画。
通过将 returns 和 AnyView
的函数包装在 VStack
中,我能够让它与 switch 语句一起工作。我还必须给 AnyView
一个 .id
以便 SwiftUI 可以知道它何时更改。这是 Xcode 11.3 和 iOS 13.3
struct EnumView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
view(for: viewModel.viewState)
.id(viewModel.viewState)
.transition(.opacity)
}
}
func view(for viewState: ViewModel.ViewState) -> AnyView {
switch viewState {
case .loading:
return AnyView(LoadingStateView(viewModel: self.viewModel))
case .dataLoaded:
return AnyView(LoadedStateView(viewModel: self.viewModel))
case let .error(error):
return AnyView(ErrorView(error: error, viewModel: viewModel))
}
}
}
同样对于我在 ViewModel
中的示例,我需要将 viewState
更改包装在 withAnimation
块中
withAnimation {
self.viewState = .loading
}
在 iOS 14 中,他们添加了在函数构建器中使用 if let
和 switch
语句的可能性。也许对您的问题有帮助:
https://www.hackingwithswift.com/articles/221/whats-new-in-swiftui-for-ios-14(在文章底部)
我想在 SwiftUI 中创建一个动态添加子视图并带有动画的视图。
struct ContentView : View {
@State private var isButtonVisible = false
var body: some View {
VStack {
Toggle(isOn: $isButtonVisible.animation()) {
Text("add view button")
}
if isButtonVisible {
AnyView(DetailView())
.transition(.move(edge: .trailing))
.animation(Animation.linear(duration: 2))
}else{
AnyView(Text("test"))
}
}
}
}
上面的代码可以很好地处理动画。然而,当我将视图选择部分移动到一个函数中时,动画不再起作用(因为我想动态添加不同的视图,因此,我将逻辑放在一个函数中。)
struct ContentView : View {
@State private var isButtonVisible = false
var body: some View {
VStack {
Toggle(isOn: $isButtonVisible.animation()) {
Text("add view button")
}
subView().transition(.move(edge: .trailing))
.animation(Animation.linear(duration: 2))
}
func subView() -> some View {
if isButtonVisible {
return AnyView(DetailView())
}else{
return AnyView(Text("test"))
}
}
}
我看起来完全一样,但是,我不明白为什么他们有不同的结果。有人可以向我解释为什么吗?还有更好的解决方案吗?非常感谢!
这是您的代码,经过修改后可以正常工作:
struct ContentView : View {
@State private var isButtonVisible = false
var body: some View {
VStack {
Toggle(isOn: $isButtonVisible.animation()) {
Text("add view button")
}
subView()
.transition(.move(edge: .trailing))
.animation(Animation.linear(duration: 2))
}
}
func subView() -> some View {
Group {
if isButtonVisible {
DetailView()
} else {
Text("test")
}
}
}
}
注意两点:
- 你上面的两个例子是不同的,这就是为什么你得到不同的结果。第一个应用过渡和动画到 DetailView,然后用 AnyView 类型擦除它。第二种类型使用 AnyView 擦除 DetailView,然后应用过渡和动画。
- 而不是使用 AnyView 和类型擦除,我更喜欢将条件逻辑封装在
Group
视图中。然后你 return 的类型是Group
,它将正确地动画。 - 如果您想在子视图的两种可能性上使用不同的动画,您现在可以将它们直接应用于
DetailView()
或Text("test")
。
更新
Group
方法仅适用于 if
、elseif
和 else
语句。如果要使用开关,则必须将每个分支包装在 AnyView()
中。然而,这打破了 transitions/animations。目前无法使用 switch
和 设置自定义动画。
通过将 returns 和 AnyView
的函数包装在 VStack
中,我能够让它与 switch 语句一起工作。我还必须给 AnyView
一个 .id
以便 SwiftUI 可以知道它何时更改。这是 Xcode 11.3 和 iOS 13.3
struct EnumView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
view(for: viewModel.viewState)
.id(viewModel.viewState)
.transition(.opacity)
}
}
func view(for viewState: ViewModel.ViewState) -> AnyView {
switch viewState {
case .loading:
return AnyView(LoadingStateView(viewModel: self.viewModel))
case .dataLoaded:
return AnyView(LoadedStateView(viewModel: self.viewModel))
case let .error(error):
return AnyView(ErrorView(error: error, viewModel: viewModel))
}
}
}
同样对于我在 ViewModel
中的示例,我需要将 viewState
更改包装在 withAnimation
块中
withAnimation {
self.viewState = .loading
}
在 iOS 14 中,他们添加了在函数构建器中使用 if let
和 switch
语句的可能性。也许对您的问题有帮助:
https://www.hackingwithswift.com/articles/221/whats-new-in-swiftui-for-ios-14(在文章底部)