代码之家  ›  专栏  ›  技术社区  ›  ravelinx

SwuiftUI:使ScrollView仅在超过屏幕高度时才可滚动

  •  0
  • ravelinx  · 技术社区  · 4 年前

    目前我有一个这样的视图。

    struct StatsView: View {
        var body: some View {
            ScrollView {
                Text("Test1")
                Text("Test2")
                Text("Test3")
            }
        }
    }
    

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

    我怎样才能做到这一点?

    0 回复  |  直到 4 年前
        1
  •  9
  •   Asperi    4 年前

    下面是一个解决方案(使用Xcode 11.4/ios13.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)
            }
        }
    }
    

    ViewHeightKey 首选项键取自 this my solution

        2
  •  0
  •   Simone Pistecchia    4 年前

    以下解决方案允许您使用内部按钮:

    基于@Asperi解决方案

    特殊视图:

    /// Scrollview disabled if smaller then content view
    public struct SpecialScrollView<Content> : View where Content : View {
    
        let content: Content
    
        @State private var fitInScreen = false
    
        public init(@ViewBuilder content: () -> Content) {
            self.content = content()
        }
        
        public var body: some View {
            if fitInScreen == true {
                ZStack (alignment: .topLeading) {
                    content
                        .background(GeometryReader {
                                        Color.clear.preference(key: SpecialViewHeightKey.self,
                                                               value: $0.frame(in: .local).size.height)})
                        .fixedSize()
                    Rectangle()
                        .foregroundColor(.clear)
                        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                }
            }
            else {
                GeometryReader { gp in
                    ScrollView {
                        content
                            .background(GeometryReader {
                                            Color.clear.preference(key: SpecialViewHeightKey.self,
                                                                   value: $0.frame(in: .local).size.height)})
                    }
                    .onPreferenceChange(SpecialViewHeightKey.self) {
                         self.fitInScreen = $0 < gp.size.height
                    }
                }
            }
        }
    }
    
    struct SpecialViewHeightKey: PreferenceKey {
        static var defaultValue: CGFloat { 0 }
        static func reduce(value: inout Value, nextValue: () -> Value) {
            value = value + nextValue()
        }
    }
    

    用途:

    struct SwiftUIView6: View {
            
    @State private var fitInScreen = false
        var body: some View {
            
            VStack {
                Text("\(fitInScreen ? "true":"false")")
                SpecialScrollView {
                    ExtractedView()
                }
            }
        }
    }
    
    
    
    struct SwiftUIView6_Previews: PreviewProvider {
        static var previews: some View {
            SwiftUIView6()
        }
    }
    
    struct ExtractedView: View {
        @State var text:String = "Text"
        var body: some View {
            VStack {          // container to calculate total height
                Text(text)
                    .onTapGesture {text = text == "TextModified" ? "Text":"TextModified"}
                Text(text)
                    .onTapGesture {text = text == "TextModified" ? "Text":"TextModified"}
                Text(text)
                    .onTapGesture {text = text == "TextModified" ? "Text":"TextModified"}
                Spacer()
                //ForEach(0..<50) { _ in Text(text).onTapGesture {text = text == "TextModified" ? "Text":"TextModified"} } // uncomment for test
            }
        }
    }
    
        3
  •  0
  •   Lorenzo Fiamingo    4 年前

    代码

    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)
        }
    }
    

    ScrollView([.vertical, .horizontal]) {
        Text("Ciao")
    }
    .scrollOnlyOnOverflow()
    

    敬告

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

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