7 C
New York
Saturday, March 1, 2025

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


I am implementing a horizontal ScrollView in SwiftUI to permit customers to scroll by a listing of days (dayPickerView). In Xcode’s Canvas, all the pieces works as anticipated, and scrolling is strictly horizontal. Nonetheless, when operating the app on an actual system, the scrollable space could be moved barely in all instructions (up, down, and even diagonally).

This surprising conduct typically even triggers the pull-to-refresh gesture, making the UI really feel glitchy.

Right here’s a GIF exhibiting 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(colour: 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", position: .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 {
                Job { @MainActor in
                    await viewModel.fetchData(forceRefresh: false)
                }
            }
            
            // Подписываемся на уведомление о добавлении/обновлении/удалении записи
            NotificationCenter.default.addObserver(
                forName: .newSleepRecordAdded,
                object: nil,
                queue: .principal
            ) { _ in
                self.refreshData()
            }
        }
        .onDisappear {
            // Отписываемся при исчезновении представления
            NotificationCenter.default.removeObserver(self, identify: .newSleepRecordAdded, object: nil)
        }
        .refreshable {
            // Сбрасываем кэш для режима, который сейчас не отображается
            if viewModel.viewMode == .each day {
                viewModel.weeklyData = [] // Сбрасываем недельные данные
            } else {
                viewModel.dailyData = [] // Сбрасываем дневные данные
            }
            
            await viewModel.fetchData(forceRefresh: true)
            await MainActor.run {
                lastUpdateTime = Date().timeIntervalSince1970
            }
        }
    }


// Customized header part
    personal 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 each day mode
            if viewModel.viewMode == .each day {
                dayPickerView
                    .padding(.prime, 20)
                    .padding(.backside, 20)
            }
        }
        .background(Shade("Background"))
    }

// Day picker part with horizontal scrolling
    personal var dayPickerView: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
                // Present at this time's day first, adopted by the 6 earlier days
                // Type an array with unfavourable offsets (0 - at this time, -1 - yesterday, and so on.)
                ForEach(0..<7, id: .self) { index in
                    let offset = -index // Convert the index to a unfavourable 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)
        }
    }

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles