admin管理员组

文章数量:1355661

I want to use 2 scrollView. One on top of the other. The one below can push over the one above. Here is what i currently have:

Blue rectangle - is an item in scrollView which I can scroll. Bottom(631, Description, Lorem) - is another scrollView, which can be scroll to top. I am using offset to put it bottom, but I cannot seem to get the my bottom ScrollView to slide over the blue rectangle view.

What I want to achieve: Bottom scrollView goes over top scrollView. You can see this behavior in telegram if you open a post with a picture.

Some code to touch:

import SwiftUI

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

struct ScrollmainView: View {
    @State private var scrollOffset = CGFloat(0)
    @State private var offSetOnStart: CGFloat = 0

    // MARK: - UI

    @ViewBuilder
    var body: some View {
        let screenBounds = UIScreen.main.bounds
        ZStack(alignment: .top) {
            ScrollView(.horizontal) {
                LazyHStack {
                    Rectangle()
                        .foregroundColor(.cyan)
                        .frame(width: UIScreen.main.bounds.width - 32, height: 400)
                }
                .padding(.horizontal, 16)
            }
            .background(Color.red)
            .overlay {
                scrollBody.padding(.top, -200)
            }
        }
        .ignoresSafeArea(.all, edges: [.top, .bottom])
    }

    @ViewBuilder
    private var scrollBody: some View {
        let size = UIScreen.main.bounds
        ScrollView {
            LazyVStack {
                ZStack(alignment: .top) {
                    scrollContentBody
                    GeometryReader { proxy in
                        let offset = proxy.frame(in: .named("scroll")).minY
                        Text("\(offset)")
                        Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: offset)
                    }
                }
                Color.clear
                    .frame(width: size.width,
                           height: UIApplication.shared.keyWindow?.safeAreaInsets.bottom)
            }
        }
        .offset(y: 700)
        .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
            scrollOffset = value
            print("offset: \(value)")
            guard offSetOnStart != 0 && scrollOffset > 0 && scrollOffset < 631
            else {
                offSetOnStart = 700
                return
            }
            offSetOnStart = scrollOffset
        }
    }

    private var scrollContentBody: some View {
        LazyVStack(spacing: 0) {
            EntryScrollHeader()
            Color.green.opacity(0.5)
                .frame(height: 600)
        }
    }
}

struct EntryScrollHeader: View {
    var body: some View {
        VStack(spacing: 0) {
            HStack {
                VStack {
                    Text("Description")
                }
                Spacer()

                Button {
                } label: {
                    Image(systemName: "bookmark")
                        .foregroundColor(.black.opacity(0.25))
                        .font(.system(size: 25, weight: .regular, design: .default))
                }
            }
            .padding(.bottom, 4)

            HStack {
                VStack {
                    Text("Lorem")
                    Text("Ipsum")
                }
                Spacer()
            }

        }
        .frame(height: 76)
        .padding([.leading, .trailing], 15)
        .padding(.top, 12)
        .background(Color.white)
    }

}

I want to use 2 scrollView. One on top of the other. The one below can push over the one above. Here is what i currently have:

Blue rectangle - is an item in scrollView which I can scroll. Bottom(631, Description, Lorem) - is another scrollView, which can be scroll to top. I am using offset to put it bottom, but I cannot seem to get the my bottom ScrollView to slide over the blue rectangle view.

What I want to achieve: Bottom scrollView goes over top scrollView. You can see this behavior in telegram if you open a post with a picture.

Some code to touch:

import SwiftUI

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

struct ScrollmainView: View {
    @State private var scrollOffset = CGFloat(0)
    @State private var offSetOnStart: CGFloat = 0

    // MARK: - UI

    @ViewBuilder
    var body: some View {
        let screenBounds = UIScreen.main.bounds
        ZStack(alignment: .top) {
            ScrollView(.horizontal) {
                LazyHStack {
                    Rectangle()
                        .foregroundColor(.cyan)
                        .frame(width: UIScreen.main.bounds.width - 32, height: 400)
                }
                .padding(.horizontal, 16)
            }
            .background(Color.red)
            .overlay {
                scrollBody.padding(.top, -200)
            }
        }
        .ignoresSafeArea(.all, edges: [.top, .bottom])
    }

    @ViewBuilder
    private var scrollBody: some View {
        let size = UIScreen.main.bounds
        ScrollView {
            LazyVStack {
                ZStack(alignment: .top) {
                    scrollContentBody
                    GeometryReader { proxy in
                        let offset = proxy.frame(in: .named("scroll")).minY
                        Text("\(offset)")
                        Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: offset)
                    }
                }
                Color.clear
                    .frame(width: size.width,
                           height: UIApplication.shared.keyWindow?.safeAreaInsets.bottom)
            }
        }
        .offset(y: 700)
        .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
            scrollOffset = value
            print("offset: \(value)")
            guard offSetOnStart != 0 && scrollOffset > 0 && scrollOffset < 631
            else {
                offSetOnStart = 700
                return
            }
            offSetOnStart = scrollOffset
        }
    }

    private var scrollContentBody: some View {
        LazyVStack(spacing: 0) {
            EntryScrollHeader()
            Color.green.opacity(0.5)
                .frame(height: 600)
        }
    }
}

struct EntryScrollHeader: View {
    var body: some View {
        VStack(spacing: 0) {
            HStack {
                VStack {
                    Text("Description")
                }
                Spacer()

                Button {
                } label: {
                    Image(systemName: "bookmark")
                        .foregroundColor(.black.opacity(0.25))
                        .font(.system(size: 25, weight: .regular, design: .default))
                }
            }
            .padding(.bottom, 4)

            HStack {
                VStack {
                    Text("Lorem")
                    Text("Ipsum")
                }
                Spacer()
            }

        }
        .frame(height: 76)
        .padding([.leading, .trailing], 15)
        .padding(.top, 12)
        .background(Color.white)
    }

}
Share Improve this question edited Mar 28 at 6:57 Dmitriy Greh asked Mar 28 at 6:05 Dmitriy Greh Dmitriy Greh 6968 silver badges18 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

If I understand correctly, what you're trying to achieve is to have one row in the scroll view (outer scroll view), that is itself a scroll view (inner scroll view).
If so, you need to limit height of this inner scroll view.
A solution may look like this:

ScrollView(.vertical) {
  LazyVStack {
    ScrollView(.horizontal) {
      YourHorizontallyScrollableContent()
    }
    .frame(height: 200)

    YourVerticallyScrollableContent()
  }
}

UPDATE:

I misunderstood your question, so here's the proper solution

import SwiftUI

struct ContentView: View {
  @State
  private var contentOffset: CGFloat = .zero
  
  var body: some View {
    ObservableScrollView(
      contentOffset: $contentOffset
    ) {
      VStack(spacing: 0) {
        StaticContent()
          .offset(y: -contentOffset)
          .zIndex(1)
        
        ScrollableContent()
          .zIndex(2)
      }
    }
  }
}

struct StaticContent: View {
  var body: some View {
    VStack {
      Button("Press") {
        print("Pressed")
      }
      .buttonStyle(.borderedProminent)
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .aspectRatio(1, contentMode: .fit)
    .background(Color.cyan)
  }
}

struct ScrollableContent: View {
  var body: some View {
    VStack(spacing: 32) {
      Color.purple.frame(height: 50)
      
      Text("Scrollable content")
      
      Button("Action") {
        print("Actioned")
      }
      .buttonStyle(.borderedProminent)
      
      Spacer()
    }
    .frame(maxWidth: .infinity)
    .frame(height: 2000)
    .background(.green)
  }
}

The idea is to apply a negative content offset to whatever view you need to be static on the screen. ObservableScrollView is a custom implementation of a scroll view that reports it's content offset (taken from this article)

本文标签: ios2 ScrollView issue in SwiftUIStack Overflow