SwiftUI:仅当 ScrollView 超过屏幕高度时才可滚动

rav*_*inx 5 scrollview ios swift swiftui

目前我有一个看起来像这样的视图。

struct StatsView: View {
    var body: some View {
        ScrollView {
            Text("Test1")
            Text("Test2")
            Text("Test3")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这会在滚动视图中呈现一个包含 3 个文本的视图,每当我在屏幕中拖动这些文本中的任何一个时,视图都会移动导致其可滚动,即使这 3 个文本适合屏幕并且有剩余空间。我想要实现的是仅在其内容超过屏幕高度大小时才使 ScrollView 可滚动,否则,我希望视图是静态的并且不移动。我尝试使用 GeometryReader 并将滚动视图框架设置为屏幕宽度和高度,内容也相同,但我继续具有相同的行为,我也尝试设置 minHeight、maxHeight 没有任何运气。

我怎样才能做到这一点?

小智 35

由于某种原因,我无法完成上述任何一项工作,但它确实激励我找到了适合我的情况的解决方案。它不像其他的那么灵活,但可以很容易地适应支持两个滚动轴。

import SwiftUI

struct OverflowContentViewModifier: ViewModifier {
    @State private var contentOverflow: Bool = false
    
    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
            .background(
                GeometryReader { contentGeometry in
                    Color.clear.onAppear {
                        contentOverflow = contentGeometry.size.height > geometry.size.height
                    }
                }
            )
            .wrappedInScrollView(when: contentOverflow)
        }
    }
}

extension View {
    @ViewBuilder
    func wrappedInScrollView(when condition: Bool) -> some View {
        if condition {
            ScrollView {
                self
            }
        } else {
            self
        }
    }
}

extension View {
    func scrollOnOverflow() -> some View {
        modifier(OverflowContentViewModifier())
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

VStack {
   // Your content
}
.scrollOnOverflow()
Run Code Online (Sandbox Code Playgroud)

  • 这对我不起作用。内容永远无法滚动。 (5认同)

pae*_*ebu 25

iOS 16.4 后: 您现在可以使用scrollBounceBehavior(_:axes:)修饰符:

var body: some View {
   ScrollView {
      // your content
   }
   .scrollBounceBehavior(.basedOnSize, axes: [.vertical])
}
Run Code Online (Sandbox Code Playgroud)

iOS16 后: 我会使用 ViewThatFits 的模式匹配特性:

var body: some View {
   ViewThatFits {
      // your content
      ScrollView {
          // same content
      }
   }
}
Run Code Online (Sandbox Code Playgroud)


Nik*_*ner 13

我的解决方案不会禁用内容交互性

struct ScrollViewIfNeeded<Content: View>: View {
    @ViewBuilder let content: () -> Content

    @State private var scrollViewSize: CGSize = .zero
    @State private var contentSize: CGSize = .zero

    var body: some View {
        ScrollView(shouldScroll ? [.vertical] : []) {
            content().readSize($contentSize)
        }
        .readSize($scrollViewSize)
    }

    private var shouldScroll: Bool {
        scrollViewSize.height <= contentSize.height
    }
}

struct SizeReaderModifier: ViewModifier  {
    @Binding var size: CGSize
    
    func body(content: Content) -> some View {
        content.background(
            GeometryReader { geometry in
                Color.clear.onAppear() {
                    DispatchQueue.main.async {
                         size = geometry.size
                    }
                }
            }
        )
    }
}

extension View {
    func readSize(_ size: Binding<CGSize>) -> some View {
        self.modifier(SizeReaderModifier(size: size))
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

struct StatsView: View {
    var body: some View {
        ScrollViewIfNeeded {
            Text("Test1")
            Text("Test2")
            Text("Test3")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 当“shouldScroll”为 false 时,这导致视图对我来说行为不稳定。我通过将 DispatchQueue.main.async 块移动到“Color.clear”上的“.onAppear”修饰符内来修复此问题。 (2认同)

Asp*_*eri 11

这是一个解决方案(使用 Xcode 11.4 / iOS 13.4 测试)

struct StatsView: View {
    @State private var fitInScreen = false
    var body: some View {
        GeometryReader { gp in
            ScrollView {
                VStack {          // container to calculate total height
                    Text("Test1")
                    Text("Test2")
                    Text("Test3")
                    //ForEach(0..<50) { _ in Text("Test") } // uncomment for test
                }
                .background(GeometryReader {
                    // calculate height by consumed background and store in 
                    // view preference
                    Color.clear.preference(key: ViewHeightKey.self,
                        value: $0.frame(in: .local).size.height) })
            }
            .onPreferenceChange(ViewHeightKey.self) {
                 self.fitInScreen = $0 < gp.size.height    // << here !!
            }
            .disabled(self.fitInScreen)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意: ViewHeightKey首选项键取自我的解决方案

  • 让它更通用一些,并将其打包到这个 GitHub 项目中:[ScrollViewIfNeeded](https://github.com/dkk/S​​crollViewIfNeeded) (4认同)
  • 似乎禁用 ScrollView 也会禁用 ScrollView 中的所有其他交互元素。是否有一个修饰符只能禁用滚动功能而不影响 ScrollView 的内容? (3认同)

Lor*_*ngo 8

我为这个问题制作了一个更全面的组件,适用于所有类型的轴集:

代码

struct OverflowScrollView<Content>: View where Content : View {
    
    @State private var axes: Axis.Set
    
    private let showsIndicator: Bool
    
    private let content: Content
    
    init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, @ViewBuilder content: @escaping () -> Content) {
        self._axes = .init(wrappedValue: axes)
        self.showsIndicator = showsIndicators
        self.content = content()
    }

    fileprivate init(scrollView: ScrollView<Content>) {
        self._axes = .init(wrappedValue: scrollView.axes)
        self.showsIndicator = scrollView.showsIndicators
        self.content = scrollView.content
    }

    public var body: some View {
        GeometryReader { geometry in
            ScrollView(axes, showsIndicators: showsIndicator) {
                content
                    .background(ContentSizeReader())
                    .onPreferenceChange(ContentSizeKey.self) {
                        if $0.height <= geometry.size.height {
                            axes.remove(.vertical)
                        }
                        if $0.width <= geometry.size.width {
                            axes.remove(.horizontal)
                        }
                    }
            }
        }
    }
}

private struct ContentSizeReader: View {
    
    var body: some View {
        GeometryReader {
            Color.clear
                .preference(
                    key: ContentSizeKey.self,
                    value: $0.frame(in: .local).size
                )
        }
    }
}

private struct ContentSizeKey: PreferenceKey {
    static var defaultValue: CGSize { .zero }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = CGSize(width: value.width+nextValue().width,
                       height: value.height+nextValue().height)
    }
}

// MARK: - Implementation

extension ScrollView {
    
    public func scrollOnlyOnOverflow() -> some View {
        OverflowScrollView(scrollView: self)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

ScrollView([.vertical, .horizontal]) {
    Text("Ciao")
}
.scrollOnlyOnOverflow()
Run Code Online (Sandbox Code Playgroud)

注意力

此代码在这些情况下无法工作:

  1. 内容大小动态变化
  2. ScrollView 大小动态变化
  3. 设备方向改变


小智 5

ScrollView基于 Asperi 的答案,当我们知道内容即将溢出时,我们可以有条件地用 a 包裹视图。这是您可以创建的 View 的扩展:

extension View {
  func useScrollView(
    when condition: Bool,
    showsIndicators: Bool = true
  ) -> AnyView {
    if condition {
      return AnyView(
        ScrollView(showsIndicators: showsIndicators) {
          self
        }
      )
    } else {
      return AnyView(self)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在主视图中,只需使用您的逻辑检查视图是否太长,也许使用GeometryReader背景颜色技巧:

struct StatsView: View {
    var body: some View {
            VStack {
                Text("Test1")
                Text("Test2")
                Text("Test3")
            }
            .useScrollView(when: <an expression you write to decide if the view fits, maybe using GeometryReader>)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)