I'm trying to create a parallax effect for a specific view in a TabView
. The problem is that the view slightly stutters while being dragged. That's why I created a small project to determine if the problem originates from my app or the TabView
. I've noticed that my test project also has a similar problem. Is there a workaround to prevent this stuttering effect? The stuttering effect is more pronounced on a real device (I tried it on the iPhone 14 Pro with iOS 17).
I've asked the same question in StackOverflow: https://stackoverflow.com/questions/77228741/swiftui-setting-offset-for-view-in-tabview-causes-stuttering
Below is my test project:
import SwiftUI
struct ContentView: View {
@State private var currentIndex: Int = .zero
var body: some View {
TabView(selection: $currentIndex) {
ForEach(0..<10) { index in
TabViewContentView(text: String(UUID().uuidString.prefix(Constant.prefixLength)), isVisible: index == currentIndex)
.tag(index)
}
}
.tabViewStyle(PageTabViewStyle())
}
private enum Constant {
static let prefixLength = 7
}
}
private struct TabViewContentView: View {
private let text: String
private let isVisible: Bool
@State private var offset: CGFloat = .zero
init(text: String, isVisible: Bool = true) {
self.text = text
self.isVisible = isVisible
}
var body: some View {
VStack {
Color.blue.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay(alignment: .bottomLeading) {
Text(text)
.font(.largeTitle)
.offset(x: offset)
}
.opacity(isVisible ? 1 : .zero)
Color.green.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.tabViewOffsetListener { newOffset in
var parallaxOffset: CGFloat = .zero
parallaxOffset = isVisible ? newOffset * -Constant.parallaxMultiplier : newOffset
DispatchQueue.main.async {
offset = parallaxOffset
}
}
}
private enum Constant {
static let parallaxMultiplier = 0.7
}
}
private struct TabViewOffsetViewModifier: ViewModifier {
private let offsetHandler: (CGFloat) -> Void
private let clearColorView = Color.clear
init(offsetHandler: @escaping (CGFloat) -> Void) {
self.offsetHandler = offsetHandler
}
func body(content: Content) -> some View {
content
.background(
GeometryReader { proxy -> Color in
let dragOffset = proxy.frame(in: .global).minX
offsetHandler(dragOffset)
return clearColorView
}
)
}
}
public extension View {
func tabViewOffsetListener(offsetHandler: @escaping (CGFloat) -> Void) -> some View {
modifier(TabViewOffsetViewModifier(offsetHandler: offsetHandler))
}
}