AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79557197
Accepted
Deepak Sharma
Deepak Sharma
Asked: 2025-04-05 23:42:57 +0800 CST2025-04-05 23:42:57 +0800 CST 2025-04-05 23:42:57 +0800 CST

SwiftUI ScrollView scrollTargetLayout 不起作用

  • 772

我在 SwiftUI 中实现了以下清理器。+按钮应该ScrollView向上移动 1 个刻度(或scrollPosition增加 1),但问题是,直到我单击 8-9 次后才会发生滚动。这是 iOS 中的错误还是编程错误?


struct BrokenVerticalScrubberDemo: View {
    @State private var scrollPosition: Int? = 0
    @State private var count: Int = 20

    var body: some View {
        VStack {
            Text("Scroll Position: \(scrollPosition ?? 0)")
                .font(.headline)

            ScrollView(.vertical, showsIndicators: true) {
                VStack(spacing: 8) {
                    ForEach(0..<count, id: \.self) { index in
                        Text("Tick \(index)")
                            .frame(height: 30)
                            .id(index)
                    }
                }
                .scrollTargetLayout()
                .padding(.vertical, 50)
            }
            .scrollTargetBehavior(.viewAligned)
            .scrollPosition(id: $scrollPosition)
            .frame(height: 300)
            .border(Color.gray)

            Button("+1") {
                withAnimation {
                    scrollPosition = min((scrollPosition ?? 0) + 1, count - 1)
                }
            }
            .padding(.top)
        }
        .padding()
    }
}

#Preview {
    BrokenVerticalScrubberDemo()
}

相反,如果我使用ScrollViewReader解决方法,它会在点击 2 次“+”按钮后开始滚动。

import SwiftUI

struct SomeWhatWorkingVerticalScrubberDemo: View {
    @State private var scrollPosition: Int = 0
    @State private var count: Int = 20

    var body: some View {
        VStack {
            Text("Scroll Position: \(scrollPosition)")
                .font(.headline)

            ScrollView(.vertical, showsIndicators: true) {
                ScrollViewReader { scrollViewProxy in
                    VStack(spacing: 8) {
                        ForEach(0..<count, id: \.self) { index in
                            Text("Tick \(index)")
                                .frame(height: 30)
                                .id(index)
                        }
                    }
                    .padding(.vertical, 50)
                    .onChange(of: scrollPosition) { newPosition in
                        withAnimation {
                            scrollViewProxy.scrollTo(newPosition, anchor: .center)
                        }
                    }
                }
            }
            .frame(height: 300)
            .border(Color.gray)

            Button("+1") {
                scrollPosition = min(scrollPosition + 1, count - 1)
            }
            .padding(.top)
        }
        .padding()
    }
}

#Preview {
    SomeWhatWorkingVerticalScrubberDemo()
}

  • 2 2 个回答
  • 50 Views

2 个回答

  • Voted
  1. Best Answer
    Marcel Derks
    2025-04-06T03:13:02+08:002025-04-06T03:13:02+08:00

    请尝试使用顶部锚点

    .scrollPosition(id: $scrollPosition, anchor: .top)
    

    在滚动位置上。

    关于默认锚点行为的文档对我来说有点晦涩难懂。但在您的示例中,它似乎是底部锚点。

     /// If no anchor has been provided, SwiftUI will scroll the minimal amount
     /// when using the scroll position to programmatically scroll to a view.
    
    • 2
  2. Andrei G.
    2025-04-07T06:08:13+08:002025-04-07T06:08:13+08:00

    添加锚点可能会修复按钮滚动问题,但并不能真正修复洗涤器的布局和整体使用中的缺陷,因为您永远无法选择整个值范围(例如 0 或 13 以上的任何值)。

    此外,如果拖动洗涤器(不使用按钮),值将无法正确对齐 - 它们要么一次前进两个值,要么需要拖动两个步骤才能将其增加一步,如下面的记录所示。

    在此处输入图片描述

    还有一个问题,设置 的默认值scrollPosition实际上并不是这样工作的。在您的示例中,您将其设置为零,并且在 中展开时将其显示为零Text,但实际上它仍然是nil。

    因此,如果您需要通过将默认值设置为 5 来实际滚动到第 5 个刻度scrollPosition,则它不起作用。要真正使其工作,请保留无默认值并在 中设置初始.onAppear值ScrollView。

    视图对齐(如果正确实现)应该适用于任何锚点(甚至没有锚点)。在这种情况下,除 之外的任何锚点.top基本上都不起作用(甚至 .top 也存在上述问题)。

    VStack造成这一切的罪魁祸首是用于布局目标的垂直填充:

    .padding(.vertical, 50) // <- breaks everything
    

    以下是重现该问题的完整代码,其中包含选择不同锚点的选项:

    在此处输入图片描述

    import SwiftUI
    
    struct DamagedVerticalScrubberDemo: View {
        
        //State values
        @State private var scrollPosition: Int? = 0
        @State private var count: Int = 20
        @State private var selectedAnchor: UnitPoint?
        
        //Body
        var body: some View {
            let step = 1
            let range = 0...count
            
            VStack {
                Text("Scroll Position: \(scrollPosition ?? 0)")
                    .font(.headline)
                
                ScrollView(.vertical, showsIndicators: false) {
                    VStack(spacing: 8) {
                        ForEach(0..<count, id: \.self) { index in
                            Text("Tick \(index)")
                                .frame(height: 30)
                                // .border(.red)
                                .id(index)
                        }
                    }
                    // .border(.green)
                    .padding(.vertical, 50)
                    .scrollTargetLayout()
                }
                .scrollTargetBehavior(.viewAligned)
                .scrollPosition(id: $scrollPosition, anchor: selectedAnchor)
                .frame(height: 300)
                .frame(maxWidth: .infinity, alignment: .center)
                .overlay {
                    Divider()
                }
                .border(Color.gray)
                
                VStack {
                    Stepper(
                        value: Binding(
                            get: { scrollPosition ?? 0 },
                            set: { newValue in
                                withAnimation {
                                    scrollPosition = newValue
                                }
                            }),
                        in: range,
                        step: step
                    ){}
                        .fixedSize()
                        .padding(.top)
                    
                    Text("Adjust scroll position")
                        .font(.caption2)
                        .foregroundStyle(.secondary)
                    
                    Picker("", selection: $selectedAnchor) {
                        Text("Select anchor").tag(nil as UnitPoint?)
                        Label("Top anchor", systemImage: "inset.filled.topthird.rectangle").tag(UnitPoint.top)
                        Label("Center anchor", systemImage: "inset.filled.center.rectangle").tag(UnitPoint.center)
                        Label("Bottom anchor", systemImage: "inset.filled.bottomthird.rectangle").tag(UnitPoint.bottom)
                    }
                    .padding(.top)
                }
            }
            .padding()
        }
    }
    
    #Preview {
        DamagedVerticalScrubberDemo()
    }
    

    解决方案:

    修复方法相当简单,只需移除 中的任何填充VStack,并向 增加边距ScrollView,考虑到每个 的高度tick,以便垂直居中所有内容并允许选择完整的值范围。因此,在这种情况下,它将是:

    ScrollView {
        //content...
    }
    .contentMargins(.vertical, (300 - 30) / 2)
    

    在此处输入图片描述

    以下是完整的工作代码:

    import SwiftUI
    
    
    struct FixedVerticalScrubberDemo: View {
        
        //Values
        let step = 1
        let range = 1...20
        let scrollViewHeight: CGFloat = 300
        let tickHeight: CGFloat = 30
    
        //State values
        @State private var scrollPosition: Int?
        @State private var showInfoOverlay = false
        
        //Computed values
        private var margins: CGFloat {
            (scrollViewHeight - tickHeight) / 2
        }
        
        var body: some View {
            VStack {
                Text("Scroll Position: \(scrollPosition ?? 0)")
                    .font(.headline)
                
                ScrollView(.vertical, showsIndicators: false) {
                    VStack(spacing: 8) {
                        ForEach(range, id: \.self) { index in
                            Text("Tick \(index)")
                                .padding()
                                .frame(height: tickHeight)
                                .scrollTransition(.animated) { content, phase in
                                    content
                                        .opacity(phase.isIdentity ? 1 : 0.3)
                                        .blur(radius: phase.isIdentity ? 0 : 0.5)
                                        .scaleEffect(phase.isIdentity ? 1.2 : 1)
                                }
                                .id(index)
                        }
                    }
                    .scrollTargetLayout()
                }
                .contentMargins(.vertical, margins)
                .scrollTargetBehavior(.viewAligned)
                .scrollPosition(id: $scrollPosition)
                .frame(height: scrollViewHeight)
                .frame(maxWidth: .infinity, alignment: .center)
                .background {
                    Divider()
                        .background(.gray)
                        .overlay{
                            Capsule()
                                .fill(Color(.systemBackground))
                                .stroke(.gray.opacity(0.5))
                                .frame(width: 100, height: tickHeight)
                        }
                }
                .overlay {
                    if showInfoOverlay {
                        infoOverlay
                    }
                }
                .onAppear { // <- set default scrollPosition value here
                    scrollPosition = 1
                }
                .border(Color.gray)
                .animation(.smooth, value: scrollPosition)
                
                //Stepper
                HStack {
                    
                    Button("First") {
                        scrollPosition = range.lowerBound
                    }
                    .disabled(scrollPosition == range.lowerBound)
                    
                    Stepper(
                        value: Binding(
                            get: { scrollPosition ?? 0 },  // If the optional is nil, default to 0
                            set: { newValue in
                                withAnimation {
                                    scrollPosition = newValue  // Apply animation when value changes
                                }
                            }),
                        in: range,
                        step: step
                    ) {}
                        .labelsHidden()
                        .fixedSize()
                        .padding()
                    
                    Button("Last") {
                        scrollPosition = range.upperBound
                    }
                    .disabled(scrollPosition == range.upperBound)
                }
                .buttonStyle(.bordered)
                .buttonBorderShape(.roundedRectangle(radius: 12))
                
                //Overlay toggle
                Toggle("Show info overlay", isOn: $showInfoOverlay)
                    .padding()
            }
            .padding()
        }
        
        private var infoOverlay: some View {
            VStack(spacing: 0) {
                Color.indigo
                    .frame(height: margins)
                    .overlay {
                        Text("Top margins/padding")
                    }
                    .opacity(0.5)
                
                Color.yellow.opacity(0.1)
                    .frame(height: tickHeight)
                
                Color.indigo
                    .frame(height: margins)
                    .overlay {
                        Text("Bottom margins/padding")
                    }
                    .opacity(0.5)
            }
        }
    }
    
    #Preview("Explanation") {
        FixedVerticalScrubberDemo()
    }
    

    请注意,上面的代码中没有使用锚点,因为在这个用例中它不是真正需要的,其中刻度标记位于滚动视图的中心。

    回顾一下:

    • 使用根据滚动视图尺寸和单个视图大小调整填充.contentMargins。ScrollView

    • .onAppear设置滚动视图的默认滚动位置值。

    在此处输入图片描述

    • 0

相关问题

  • 将复制活动的序列号添加到 Blob

  • Packer 动态源重复工件

  • 选择每组连续 1 的行

  • 图形 API 调用列表 subscribedSkus 状态权限不足,但已授予权限

  • 根据列值创建单独的 DF 的函数

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve