You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于滚动状态动态显示SwiftUI按钮的实现问题

解决方案

要实现你需要的效果,需要同时判断内容是否超出ScrollView可显示范围,以及当前滚动位置是否到底部,以下是修改后的代码:

import SwiftUI

struct ContentHeightKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct TestView: View {
    @State private var contentHeight: CGFloat = 0
    @State private var scrollPosition: CGFloat = 0
    @State private var showBottom: Bool = false
    
    var body: some View {
        VStack {
            Text("Lorem ipsum in my TestView")
                .font(.title)
                .padding(.bottom, 20)
            
            ScrollViewReader { proxy in
                GeometryReader { scrollViewGeo in
                    ScrollView {
                        VStack {
                            Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed accumsan mi elementum metus eleifend fermentum. Vestibulum a ligula laoreet libero rhoncus pharetra. Vivamus ac erat non odio tempus iaculis. Phasellus tincidunt dapibus ultricies. Aliquam erat volutpat. Maecenas aliquam est felis. Etiam consectetur viverra velit. Phasellus ligula metus, congue quis est vel, fermentum euismod ligula. Nunc pulvinar luctus semper. Nunc non fringilla ligula. Morbi scelerisque, augue non tempus hendrerit, tellus lectus sagittis ex, nec volutpat mi lacus maximus elit.")
                                .padding()
                            Text("").padding(.bottom, 10)
                                .id("end")
                        }
                        .background(
                            GeometryReader { contentGeo in
                                Color.clear.preference(key: ContentHeightKey.self, value: contentGeo.size.height)
                            }
                        )
                        .onPreferenceChange(ContentHeightKey.self) { newHeight in
                            contentHeight = newHeight
                            // 初始判断:内容高度大于ScrollView高度时才显示按钮
                            showBottom = contentHeight > scrollViewGeo.size.height
                        }
                        .background(
                            GeometryReader { innerGeo in
                                Color.clear.onAppear {
                                    scrollPosition = innerGeo.frame(in: .named("scrollView")).minY
                                }
                                .onChange(of: innerGeo.frame(in: .named("scrollView")).minY) { newMinY in
                                    let offset = newMinY - scrollPosition
                                    scrollPosition = newMinY
                                    
                                    // 计算是否滚动到底部:加入10容错值避免边界判断过于敏感
                                    let isAtBottom = abs(scrollPosition) >= (contentHeight - scrollViewGeo.size.height) - 10
                                    
                                    if isAtBottom {
                                        showBottom = false
                                    } else if contentHeight > scrollViewGeo.size.height {
                                        showBottom = true
                                    }
                                }
                            }
                        )
                    }
                    .coordinateSpace(name: "scrollView")
                    .frame(maxWidth: 300, maxHeight: 100)
                    .padding()
                    .background(.quaternary)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                }
                
                if showBottom {
                    Button{
                        withAnimation {
                            proxy.scrollTo("end")
                        }
                    } label: {
                        Image(systemName: "chevron.down.circle.fill")
                            .font(.title)
                    }
                    .padding(.top, 8)
                }
            }
        }
        .padding()
    }
}

关键逻辑说明:

  • 判断内容是否需要滚动:
    定义ContentHeightKey偏好键获取ScrollView内部内容总高度,通过onPreferenceChange对比ScrollView的显示高度,只有内容超出时才让按钮具备显示条件。
  • 监听滚动位置控制显隐:
    给ScrollView设置专属坐标空间,通过GeometryReader读取内容滚动偏移量,计算是否接近底部(加入容错值避免误判),滚动到底部时隐藏按钮,向上滚动且内容仍需滚动时重新显示。
  • 优化按钮点击逻辑:
    点击按钮滚动到底部后,无需手动切换按钮状态,滚动监听逻辑会自动隐藏按钮。

内容的提问来源于stack exchange,提问作者torfeuzarre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 18:50:46