如何阻止SwiftUI Dragesture设置子视图动画
我正在构建一个自定义模态,当我拖动该模态时,任何附加了动画的子视图都会在我拖动的同时设置动画。我该如何阻止这种情况发生 我曾考虑过传递一个带有如何阻止SwiftUI Dragesture设置子视图动画,swiftui,draggesture,Swiftui,Draggesture,我正在构建一个自定义模态,当我拖动该模态时,任何附加了动画的子视图都会在我拖动的同时设置动画。我该如何阻止这种情况发生 我曾考虑过传递一个带有isDraging标志的@EnvironmentObject,但它的可伸缩性不是很好(并且不能与自定义按钮样式一起使用) 在现实生活中,容器包含一个按钮,其按下状态带有动画 struct MyButtonStyle: ButtonStyle { func makeBody(configuration: Self.Configuration) -&g
isDraging
标志的@EnvironmentObject
,但它的可伸缩性不是很好(并且不能与自定义按钮样式一起使用)
在现实生活中,容器包含一个按钮,其按下状态带有动画
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.9 : 1)
.animation(.spring())
}
}
将animationsDisabled功能添加到子视图中,该功能实际上会在拖动过程中停止子视图的移动
它没有做的是在动画最初滑入或消失时停止动画
有没有办法知道视图何时基本上没有移动/转换?所以这是我最新的答案。我不认为有一个很好的方法可以做到这一点,所以现在我用一个自定义按钮
import SwiftUI
struct ContentView: View {
@State var isShowing = false
var body: some View {
Text("Hello, world!")
.padding()
.onTapGesture(count: 1, perform: {
withAnimation(.spring()) {
self.isShowing.toggle()
}
})
.showModal(isShowing: self.$isShowing)
}
}
extension View {
func showModal(isShowing: Binding<Bool>) -> some View {
ViewOverlay(isShowing: isShowing, presenting: { self })
}
}
struct ViewOverlay<Presenting>: View where Presenting: View {
@Binding var isShowing: Bool
let presenting: () -> Presenting
@State var bottomState: CGFloat = 0
@State var isDragging = false
var body: some View {
ZStack(alignment: .center) {
presenting().blur(radius: isShowing ? 1 : 0)
VStack {
if isShowing {
Container()
.background(Color.red)
.offset(y: bottomState)
.gesture(
DragGesture()
.onChanged { value in
isDragging = true
bottomState = value.translation.height
}
.onEnded { _ in
isDragging = false
if bottomState > 50 {
withAnimation(.spring()) {
isShowing = false
}
}
bottomState = 0
})
.transition(.move(edge: .bottom))
}
}
}
}
}
struct Container: View {
var body: some View {
CustomButton(action: {}, label: {
Text("Pressme")
})
.frame(maxWidth: .infinity, maxHeight: 200)
}
}
struct CustomButton<Label >: View where Label: View {
@State var isPressed = false
var action: () -> ()
var label: () -> Label
var body: some View {
label()
.scaleEffect(self.isPressed ? 0.9 : 1.0)
.gesture(DragGesture(minimumDistance: 0).onChanged({_ in
withAnimation(.spring()) {
self.isPressed = true
}
}).onEnded({_ in
withAnimation(.spring()) {
self.isPressed = false
action()
}
}))
}
}
导入快捷界面
结构ContentView:View{
@状态变量isShowing=false
var body:一些观点{
文本(“你好,世界!”)
.padding()
.ONTAPPORATE(计数:1,执行:{
使用动画(.spring()){
self.isShowing.toggle()
}
})
.showModal(isShowing:self.$isShowing)
}
}
扩展视图{
func showmodel(isShowing:Binding)->一些视图{
视图覆盖(显示:显示,显示:{self})
}
}
结构视图覆盖:视图显示位置:视图{
@绑定变量显示:Bool
让我们展示:()->展示
@状态变量bottomState:CGFloat=0
@状态变量IsDraging=false
var body:一些观点{
ZStack(对齐:。中心){
呈现().blur(半径:isShowing?1:0)
VStack{
如果是显示{
容器()
.背景(颜色.红色)
.偏移量(y:底部状态)
.手势(
德拉格斯特尔()
.onChanged{中的值
IsDraging=true
底部状态=value.translation.height
}
.onEnded{uuin
IsDraging=false
如果bottomState>50{
使用动画(.spring()){
isShowing=false
}
}
底部状态=0
})
.transition(.move(边:。底部))
}
}
}
}
}
结构容器:视图{
var body:一些观点{
自定义按钮(操作:{},标签:{
文本(“按我”)
})
.frame(最大宽度:无穷大,最大高度:200)
}
}
结构自定义按钮:视图,其中标签:视图{
@状态变量isPressed=false
变量作用:()->()
变量标签:()->标签
var body:一些观点{
标签()
.scaleEffect(self.isPressed?0.9:1.0)
.手势(DragGesture(最小距离:0)。一旦更改({uu}in
使用动画(.spring()){
self.isPressed=true
}
}).onEnded({uu}in
使用动画(.spring()){
self.isPressed=false
行动()
}
}))
}
}
问题是不能在容器内使用隐式动画,因为它们在容器移动时将被设置动画。因此,还需要使用withAnimation
为按下的按钮显式设置动画,我现在使用自定义按钮和DragGesture进行了设置
这是显式动画和隐式动画之间的区别
请看此视频,其中详细探讨了此主题:
理论上,在这种情况下,SwiftUI不应该翻译动画,但我不确定这是否是一个bug-我不会以这种通用方式在容器中使用动画。我使用动画越多,就越倾向于将它们直接连接到特定值
无论如何。。。这里有一个可能的解决方法-通过在中间插入不同的宿主控制器来中断动画可见性
使用Xcode 12/iOS 14进行测试
struct ViewOverlay:视图显示位置:视图{
@绑定变量显示:Bool
让我们展示:()->展示
@状态变量bottomState:CGFloat=0
var body:一些观点{
ZStack(对齐:。中心){
呈现().blur(半径:isShowing?1:0)
VStack{
颜色。清晰
如果是显示{
助手视图{
容器()
.背景(颜色.红色)
}
.偏移量(y:底部状态)
.手势(
德拉格斯特尔()
.onChanged{中的值
底部状态=value.translation.height
}
.onEnded{uuin
如果bottomState>50{
动画片{
isShowing=false
}
}
底部状态=0
})
.transition(.move(边:。底部))
}
颜色。清晰
}
}
}
}
结构HelperView:UIViewRepresentable{
让内容:()->内容
func makeUIView(上下文:context)->UIView{
让
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.9 : 1)
.animation(.spring())
}
}
import SwiftUI
struct ContentView: View {
@State var isShowing = false
var body: some View {
Text("Hello, world!")
.padding()
.onTapGesture(count: 1, perform: {
withAnimation(.spring()) {
self.isShowing.toggle()
}
})
.showModal(isShowing: self.$isShowing)
}
}
extension View {
func showModal(isShowing: Binding<Bool>) -> some View {
ViewOverlay(isShowing: isShowing, presenting: { self })
}
}
struct ViewOverlay<Presenting>: View where Presenting: View {
@Binding var isShowing: Bool
let presenting: () -> Presenting
@State var bottomState: CGFloat = 0
@State var isDragging = false
var body: some View {
ZStack(alignment: .center) {
presenting().blur(radius: isShowing ? 1 : 0)
VStack {
if isShowing {
Container()
.background(Color.red)
.offset(y: bottomState)
.gesture(
DragGesture()
.onChanged { value in
isDragging = true
bottomState = value.translation.height
}
.onEnded { _ in
isDragging = false
if bottomState > 50 {
withAnimation(.spring()) {
isShowing = false
}
}
bottomState = 0
})
.transition(.move(edge: .bottom))
}
}
}
}
}
struct Container: View {
var body: some View {
CustomButton(action: {}, label: {
Text("Pressme")
})
.frame(maxWidth: .infinity, maxHeight: 200)
}
}
struct CustomButton<Label >: View where Label: View {
@State var isPressed = false
var action: () -> ()
var label: () -> Label
var body: some View {
label()
.scaleEffect(self.isPressed ? 0.9 : 1.0)
.gesture(DragGesture(minimumDistance: 0).onChanged({_ in
withAnimation(.spring()) {
self.isPressed = true
}
}).onEnded({_ in
withAnimation(.spring()) {
self.isPressed = false
action()
}
}))
}
}
struct ViewOverlay<Presenting>: View where Presenting: View {
@Binding var isShowing: Bool
let presenting: () -> Presenting
@State var bottomState: CGFloat = 0
var body: some View {
ZStack(alignment: .center) {
presenting().blur(radius: isShowing ? 1 : 0)
VStack {
Color.clear
if isShowing {
HelperView {
Container()
.background(Color.red)
}
.offset(y: bottomState)
.gesture(
DragGesture()
.onChanged { value in
bottomState = value.translation.height
}
.onEnded { _ in
if bottomState > 50 {
withAnimation {
isShowing = false
}
}
bottomState = 0
})
.transition(.move(edge: .bottom))
}
Color.clear
}
}
}
}
struct HelperView<Content: View>: UIViewRepresentable {
let content: () -> Content
func makeUIView(context: Context) -> UIView {
let controller = UIHostingController(rootView: content())
return controller.view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
struct Container: View {
@Binding var bottomState: CGFloat
.
.
.
.
}
Container(bottomState: $bottomState)
Text("CONTAINER")
.frame(maxWidth: .infinity, maxHeight: 200)
.animation(nil, value: bottomState) // You Need To Add This
.animation(.spring())
.animation(nil, value: bottomState)
.animation(.spring())
.animation(.spring())
.animation(nil, value: bottomState)