视图修改器阻止动画 SwiftUI

问题描述 投票:0回答:1

以下视图在切换选项卡时以立方体旋转效果对内容进行动画处理。该动画通过单击屏幕的左侧或右侧或滑动选项卡来实现。当我添加

.rect
修改器时,从第二个 -> 第三个选项卡单击不会产生动画。有人知道为什么添加修饰符会导致这个错误吗?

import SwiftUI

struct cubeexample: View {
    @State var small: CGFloat = 0.0
    @State var big: CGFloat = 0.0
    @State var opacity = 1.0
    @State private var progress: CGFloat = .zero
    @State var selection = "Story 1"
    let data = ["Story 1", "Story 2", "Story 3"]
    
    var body: some View {
        ZStack {
            Color.black.opacity(opacity).ignoresSafeArea()
            TabView(selection: $selection){
                ForEach(data, id: \.self) { story in
                    GeometryReader { g in
                        ZStack {
                            HStack(spacing: 0){
                                Rectangle()
                                    .fill(Color.blue)
                                    .onTapGesture {
                                        if let index = data.firstIndex(where: { $0 == selection }), index > 0 {
                                            withAnimation(.easeInOut(duration: 0.25)){
                                                selection = data[index - 1]
                                            }
                                        }
                                    }
                                Rectangle()
                                    .fill(Color.blue)
                                    .onTapGesture {
                                        if let index = data.firstIndex(where: { $0 == selection }), (index + 1) < data.count {
                                            withAnimation(.easeInOut(duration: 0.25)){
                                                selection = data[index + 1]
                                            }
                                        }
                                    }
                            }
                            Text(story)
                                .foregroundColor(.white)
                        }
                        .frame(width: g.frame(in: .global).width, height: g.frame(in: .global).height)
                        .rect { tabProgress(tabID: story, rect: $0, size: g.size) }
                        .rotation3DEffect(
                            .init(degrees: getAngle(xOffset: g.frame(in: .global).minX)),
                            axis: (x: 0.0, y: 1.0, z: 0.0),
                            anchor: g.frame(in: .global).minX > 0 ? .leading : .trailing,
                            perspective: 2.5
                        )
                    }.tag(story)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
        }
    }
    func getAngle(xOffset: CGFloat) -> Double {
        let tempAngle = xOffset / (UIScreen.main.bounds.width / 2)
        let rotationDegree: CGFloat = 25
        return Double(tempAngle * rotationDegree)
    }
    func tabProgress(tabID: String, rect: CGRect, size: CGSize) {
        if let index = data.firstIndex(where: { $0 == tabID }), let last = data.last, tabID == selection && last == tabID {
            
            let offsetX = rect.minX - (size.width * CGFloat(index))
            progress = -offsetX / size.width
            var temp: CGFloat = progress - CGFloat(index)

            if (temp + 0.005) >= 0.0 {
                self.opacity = 1.0 - min(1.0, ((temp + 0.005) / 0.08))
            } else {
                self.opacity = 1.0
            }
            if temp >= 0.0 {
                self.big = temp
                if small > big {
                    //close view
                }
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                    self.small = temp
                }
            }
        }
    }
}

struct RectKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }
}

extension View {
    @ViewBuilder
    func rect(completion: @escaping (CGRect) -> ()) -> some View {
        self
            .overlay {
                GeometryReader {
                    let rect = $0.frame(in: .scrollView(axis: .horizontal))
                    
                    Color.clear
                        .preference(key: RectKey.self, value: rect)
                        .onPreferenceChange(RectKey.self, perform: completion)
                }
            }
    }
}

#Preview {
    cubeexample()
}
swift swiftui transition
1个回答
0
投票

我通过添加一个名为 SwitchToLast 的变量修复了该错误。如果选项卡切换到第一个或最后一个,则此变量设置为 true。如果此 var 为 true,则

.rect
处理函数不会执行 0.2 秒,这是选项卡更改的动画时间。我猜状态变量在转换期间无法更新。函数
tabProgress
仅针对最后一个选项卡运行(不包括第一个选项卡的逻辑),因此阻止它在最后一个选项卡更改时运行 0.2 秒即可修复此问题。

import SwiftUI

#Preview {
    cubeexample()
}

struct cubeexample: View {
    @State var switchedToStartLast = false
    @State var small: CGFloat = 0.0
    @State var big: CGFloat = 0.0
    @State var opacity = 1.0
    @State private var progress: CGFloat = .zero
    @State var selection = "Story 1"
    let data = ["Story 1", "Story 2", "Story 3", "Story 9", "Story 10"]
    
    var body: some View {
        ZStack {
            Color.black.opacity(opacity).ignoresSafeArea()
            TabView(selection: $selection){
                ForEach(data, id: \.self) { story in
                    GeometryReader { g in
                        ZStack {
                            HStack(spacing: 0){
                                Rectangle()
                                    .fill(Color.blue)
                                    .onTapGesture {
                                        if let index = data.firstIndex(where: { $0 == selection }), index > 0 {
                                            withAnimation(.easeInOut(duration: 0.25)){
                                                selection = data[index - 1]
                                            }
                                            switchedToStartLast = index == 1
                                        }
                                    }
                                Rectangle()
                                    .fill(Color.blue)
                                    .onTapGesture {
                                        if let index = data.firstIndex(where: { $0 == selection }), (index + 1) < data.count {
                                            withAnimation(.easeInOut(duration: 0.25)){
                                                selection = data[index + 1]
                                            }
                                            switchedToStartLast = (index + 2) == data.count
                                        }
                                    }
                            }
                            Text(story)
                                .foregroundColor(.white)
                        }
                        .frame(width: g.frame(in: .global).width, height: g.frame(in: .global).height)
                        .rect { tabProgress(tabID: story, rect: $0, size: g.size) }
                        .rotation3DEffect(
                            .init(degrees: getAngle(xOffset: g.frame(in: .global).minX)),
                            axis: (x: 0.0, y: 1.0, z: 0.0),
                            anchor: g.frame(in: .global).minX > 0 ? .leading : .trailing,
                            perspective: 2.5
                        )
                    }.tag(story)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            Text(selection)
                .font(.title)
                .offset(y: 30)
            Text(data.last ?? "NA")
                .font(.title)
                .offset(y: 60)
        }
    }
    func getAngle(xOffset: CGFloat) -> Double {
        let tempAngle = xOffset / (UIScreen.main.bounds.width / 2)
        let rotationDegree: CGFloat = 25
        return Double(tempAngle * rotationDegree)
    }
    func tabProgress(tabID: String, rect: CGRect, size: CGSize) {
        if !switchedToStartLast {
            if let index = data.firstIndex(where: { $0 == tabID }), let last = data.last, tabID == selection && last == tabID {
                
                let offsetX = rect.minX - (size.width * CGFloat(index))
                progress = -offsetX / size.width
                let temp: CGFloat = progress - CGFloat(index)
                
                if (temp + 0.005) >= 0.0 {
                    self.opacity = 1.0 - min(1.0, ((temp + 0.005) / 0.08))
                } else {
                    self.opacity = 1.0
                }
                if temp >= 0.0 {
                    self.big = temp
                    if small > big {
                        //close view
                    }
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                        self.small = temp
                    }
                }
            }
        } else {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                switchedToStartLast = false
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.