ios – Motion in all instructions when utilizing ScrollView(.horizontal)

ios – Motion in all instructions when utilizing ScrollView(.horizontal)


I am implementing a horizontal ScrollView in SwiftUI to permit customers to scroll via an inventory of days (dayPickerView). In Xcode’s Canvas, every thing works as anticipated, and scrolling is strictly horizontal. Nevertheless, when working the app on an actual gadget, the scrollable space may be moved barely in all instructions (up, down, and even diagonally).

This sudden habits generally even triggers the pull-to-refresh gesture, making the UI really feel glitchy.

Right here’s a GIF displaying the difficulty:
Gif

What I attempted:

  1. Wrapping ScrollView in GeometryReader to detect offsets.

  2. Including .simultaneousGesture(DragGesture()) to restrict motion.

  3. Utilizing .contentShape(Rectangle()) to limit interactions.

None of those options labored.

My code:

var physique: some View {
        ZStack {
            Shade("Background")
                .ignoresSafeArea()
            
            VStack(spacing: 0) {
                customHeader
                
                // Content material in white card with rounded corners
                ZStack {
                    RoundedRectangle(cornerRadius: 40)
                        .fill(Shade.white)
                        .shadow(shade: Shade.black.opacity(0.1), radius: 5, x: 0, y: 0)
                    
                    ScrollView {
                        VStack(spacing: 20) {
                            viewModePicker
                            
                            if viewModel.isLoading {
                                loadingView
                            } else if isDataEmpty {
                                emptyStateView
                            } else {
                                statisticsView
                                
                                chartView
                                
                                if hasDataToShow {
                                    recordsListView
                                }
                            }
                            
                            // Add backside padding for higher scrolling expertise
                            Spacer()
                                .body(peak: 20)
                        }
                        .padding(.backside)
                    }
                    .padding(.horizontal, 2) // Small horizontal padding for scroll view
                }
                .padding(.horizontal, 0)
                .padding(.prime, 10)
                .padding(.backside, 5)
                .edgesIgnoringSafeArea(.backside)
            }
        }
        .navigationBarHidden(true)
        .sheet(isPresented: $showingAddRecord) {
            NavigationView {
                AddSleepRecordView(childId: childId)
            }
        }
        .onChange(of: showingAddRecord) { oldValue, newValue in
            if !newValue { // Если форма была закрыта
                refreshData()
            }
        }
        .alert("Помилка", isPresented: $showingAlert) {
            Button("OK", function: .cancel) {
                viewModel.errorMessage = nil
            }
        } message: {
            if let error = viewModel.errorMessage {
                Textual content(error)
            }
        }
        .onChange(of: viewModel.errorMessage) { _, newValue in
            showingAlert = newValue != nil
        }
        .onAppear {
            let currentTime = Date().timeIntervalSince1970
            let shouldRefresh = currentTime - lastUpdateTime > 300 // 5 минут
            
            if shouldRefresh {
                refreshData()
            } else {
                Process { @MainActor in
                    await viewModel.fetchData(forceRefresh: false)
                }
            }
            
            // Подписываемся на уведомление о добавлении/обновлении/удалении записи
            NotificationCenter.default.addObserver(
                forName: .newSleepRecordAdded,
                object: nil,
                queue: .predominant
            ) { _ in
                self.refreshData()
            }
        }
        .onDisappear {
            // Отписываемся при исчезновении представления
            NotificationCenter.default.removeObserver(self, title: .newSleepRecordAdded, object: nil)
        }
        .refreshable {
            // Сбрасываем кэш для режима, который сейчас не отображается
            if viewModel.viewMode == .day by day {
                viewModel.weeklyData = [] // Сбрасываем недельные данные
            } else {
                viewModel.dailyData = [] // Сбрасываем дневные данные
            }
            
            await viewModel.fetchData(forceRefresh: true)
            await MainActor.run {
                lastUpdateTime = Date().timeIntervalSince1970
            }
        }
    }


// Customized header part
    non-public var customHeader: some View {
        VStack(spacing: 0) {
            HStack(spacing: 16) {
                // Again button
                IconButtonCircle(systemName: "chevron.left", model: .main, measurement: .medium, buttonSize: 40) {
                    presentationMode.wrappedValue.dismiss()
                }
                
                Spacer()
                
                // Add report button
                TextIconButtonLeft("Додати запис", systemName: "plus.circle.fill", model: .main, measurement: .small) {
                    showingAddRecord = true
                }
                .body(maxWidth: 200)
            }
            .padding(.horizontal)
            .padding(.vertical, 12)
            
            // Solely present day selector in day by day mode
            if viewModel.viewMode == .day by day {
                dayPickerView
                    .padding(.prime, 20)
                    .padding(.backside, 20)
            }
        }
        .background(Shade("Background"))
    }

// Day picker part with horizontal scrolling
    non-public var dayPickerView: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
                // Present at this time's day first, adopted by the 6 earlier days
                // Kind an array with damaging offsets (0 - at this time, -1 - yesterday, and so forth.)
                ForEach(0..<7, id: .self) { index in
                    let offset = -index // Convert the index to a damaging offset
                    let date = Calendar.present.date(byAdding: .day, worth: offset, to: Date()) ?? Date()
                    dayButton(for: date)
                }
            }
            .padding(.prime, 10)
            .padding(.backside, 10)
            .padding(.horizontal, 24)
        }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *