以下视图在切换选项卡时以立方体旋转效果对内容进行动画处理。该动画通过单击屏幕的左侧或右侧或滑动选项卡来实现。当我添加
.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()
}
我通过添加一个名为 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
}
}
}
}