Post

Replies

Boosts

Views

Activity

Reply to SwiftUI ScrollView Jittery/Glitchy Scrolling in iOS 17
opps. here is code import CoreLocation struct Home: View { // MARK: - Incoming Parameters var topEdge: CGFloat var size: CGSize // MARK: - Environment & State @EnvironmentObject var weatherViewModel: WeatherViewModel @StateObject private var trainingViewModel = TrainingViewModel() @State private var offset: CGFloat = 0 @State private var isFavoriteToggled: Bool = false @State private var scrollToTop: UUID = UUID() @State private var lastStationID: String? = nil // Adjustable bottom padding for the main content @State private var bottomPadding: CGFloat = 30 // MARK: - Body var body: some View { let displayedStation = weatherViewModel.currentStation // Calculate offsets used in WeatherHeaderView and TempView let baseOffset = -offset let positiveOffset = offset > 0 ? (offset / size.width) * 100 : 0 let titleOffset = getTitleOffset() // Subtle upward shift based on scroll let finalOffset = baseOffset + positiveOffset + titleOffset ZStack { // MARK: - Gradient Background LinearGradient( gradient: Gradient(colors: [ Color(red: 0.0, green: 0.1, blue: 0.1), // Dark blue/gray Color(red: 0.1, green: 0.1, blue: 0.2), // Medium blue Color(red: 0.1, green: 0.2, blue: 0.5) // Light blue ]), startPoint: .topLeading, endPoint: .bottomTrailing ) .ignoresSafeArea(.container, edges: .top) .blur(radius: min(10, offset / 15)) .overlay( Color.black.opacity(0.15) // Slight darkening overlay .ignoresSafeArea() ) // MARK: - Main Scroll ScrollViewReader { proxy in ScrollView(.vertical, showsIndicators: false) { VStack { // Dummy view to scroll to top Color.clear .frame(height: 0) .id(scrollToTop) // MARK: - Weather Header WeatherHeaderView(displayedStation: displayedStation) .offset(y: finalOffset) // MARK: - Temperature View TempView( displayedStation: displayedStation, isFavoriteToggled: $isFavoriteToggled ) { toggleFavorite(for: displayedStation) } .offset(y: finalOffset) .opacity(getTempViewOpacity()) // MARK: - Wind Details if let stationID = displayedStation?.stationID { WindDetailsView(stationID: stationID) .padding(.top, 10) .environmentObject(weatherViewModel) // MARK: - Weather Data WeatherDataView( stationID: stationID, stations: weatherViewModel.weatherData, trainingViewModel: trainingViewModel ) .environmentObject(weatherViewModel) // Provide the environment object .id(stationID) // Force a refresh if the station ID changes .onChange(of: weatherViewModel.currentStation?.stationID) { oldID, newID in guard let newID = newID else { return } guard newID != lastStationID else { return } lastStationID = newID withAnimation { proxy.scrollTo(scrollToTop, anchor: .top) } } .padding(.bottom, bottomPadding) .environmentObject(weatherViewModel) } else { // Loading indicator if no station is displayed ProgressView("Loading station data...") .foregroundColor(.white) .padding() .background(Color.black.opacity(0.3)) .cornerRadius(10) } } .padding(.top, 25) .padding(.top, topEdge) .padding(.horizontal) .safeAreaInset(edge: .bottom) { Color.clear.frame(height: 10) } .offsetChangeHome { rect in offset = rect.minY } } // Also update this one to the TWO-parameter closure .onChange(of: weatherViewModel.currentStation?.stationID) { oldID, newID in guard let newID = newID else { return } guard newID != lastStationID else { return } lastStationID = newID withAnimation { proxy.scrollTo(scrollToTop, anchor: .top) } } } } // MARK: - Favorite Limit Alert .alert(isPresented: $weatherViewModel.showFavoriteLimitAlert) { Alert( title: Text("Favorite Limit Reached"), message: Text("You can only have up to 5 favorite stations."), dismissButton: .default(Text("OK")) ) } } } // MARK: - Private Methods extension Home { private func toggleFavorite(for station: RawsWeatherStation?) { guard let station = station else { return } weatherViewModel.toggleFavoriteStatus(for: station) } private func getTitleOffset() -> CGFloat { let progress = max(0, min(1, -offset / 120)) return -progress * 34 } private func getTempViewOpacity() -> CGFloat { let fadeOutStart: CGFloat = 0 let fadeOutEnd: CGFloat = 100 let progress = (offset - fadeOutEnd) / (fadeOutStart - fadeOutEnd) return max(2 - progress, 0) } }```
Topic: UI Frameworks SubTopic: SwiftUI
Apr ’25