Post

Replies

Boosts

Views

Activity

SwiftUI: Setting offset for View in TabView causes stuttering
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)) } }
0
0
543
Oct ’23
Zoom navigation transitions for tabViewBottomAccessory are not working in SwiftUI with ObservableObject or Observable
The zoom navigation transition with matchedTransitionSource in tabViewBottomAccessory does not work when a Published var in an ObservableObjector Observable gets changed. Here is an minimal reproducible example with ObservableObject: import SwiftUI import Combine private final class ViewModel: ObservableObject { @Published var isPresented = false } struct ContentView: View { @Namespace private var namespace @StateObject private var viewModel = ViewModel() // @State private var isPresented = false var body: some View { TabView { Button { viewModel.isPresented = true } label: { Text("Start") } .tabItem { Image(systemName: "house") Text("Home") } Text("Search") .tabItem { Image(systemName: "magnifyingglass") Text("Search") } Text("Profile") .tabItem { Image(systemName: "person") Text("Profile") } } .sheet(isPresented: $viewModel.isPresented) { Text("Sheet") .presentationDragIndicator(.visible) .navigationTransition(.zoom(sourceID: "tabViewBottomAccessoryTransition", in: namespace)) } .tabViewBottomAccessory { Button { viewModel.isPresented = true } label: { Text("BottomAccessory") } .matchedTransitionSource(id: "tabViewBottomAccessoryTransition", in: namespace) } } } However, when using only a State property everything works: import SwiftUI import Combine private final class ViewModel: ObservableObject { @Published var isPresented = false } struct ContentView: View { @Namespace private var namespace // @StateObject private var viewModel = ViewModel() @State private var isPresented = false var body: some View { TabView { Button { isPresented = true } label: { Text("Start") } .tabItem { Image(systemName: "house") Text("Home") } Text("Search") .tabItem { Image(systemName: "magnifyingglass") Text("Search") } Text("Profile") .tabItem { Image(systemName: "person") Text("Profile") } } .sheet(isPresented: $isPresented) { Text("Sheet") .presentationDragIndicator(.visible) .navigationTransition(.zoom(sourceID: "tabViewBottomAccessoryTransition", in: namespace)) } .tabViewBottomAccessory { Button { isPresented = true } label: { Text("BottomAccessory") } .matchedTransitionSource(id: "tabViewBottomAccessoryTransition", in: namespace) } } }
0
0
53
4d