admin管理员组

文章数量:1405194

I'm trying to create a pinned section header in a LazyVStack in SwiftUI while also applying a unified style to the entire section. The problem is that when I apply a style to a Section, the style gets applied separately to the header and content. I could wrap everything in a VStack, but that breaks the pinned header behavior in LazyVStack. Is there a way to keep the pinned header behavior while ensuring the entire section is styled as a single view or do I need an alternative approach to get this layout?

struct ContentView: View {
    var body: some View {
        ScrollView {
            LazyVStack(pinnedViews: [.sectionHeaders]) {
                Section {
                    Text("Content")
                } header: {
                    Text("Header")
                }
                .padding()
                .overlay {
                    RoundedRectangle(cornerRadius: 12)
                        .stroke(.blue, lineWidth: 1)
                }
            }
        }
    }
}

I'm trying to create a pinned section header in a LazyVStack in SwiftUI while also applying a unified style to the entire section. The problem is that when I apply a style to a Section, the style gets applied separately to the header and content. I could wrap everything in a VStack, but that breaks the pinned header behavior in LazyVStack. Is there a way to keep the pinned header behavior while ensuring the entire section is styled as a single view or do I need an alternative approach to get this layout?

struct ContentView: View {
    var body: some View {
        ScrollView {
            LazyVStack(pinnedViews: [.sectionHeaders]) {
                Section {
                    Text("Content")
                } header: {
                    Text("Header")
                }
                .padding()
                .overlay {
                    RoundedRectangle(cornerRadius: 12)
                        .stroke(.blue, lineWidth: 1)
                }
            }
        }
    }
}
Share Improve this question asked Mar 9 at 9:20 Berry BlueBerry Blue 16.6k22 gold badges77 silver badges147 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 1

If you are aiming to add a border around the header + content, then you could try using a container for the content inside the Section. Then:

  • use .onGeometryChange to measure the scroll offset of the container
  • use another .onGeometryChange to measure the scroll offset of the header
  • apply the border as an overlay to the container
  • apply negative padding to the top of the overlay shape, so that the border incorporates the header too
  • the size of the negative padding is computed as the difference between the two scroll offsets.
struct ContentView: View {
    @State private var headerOffset = CGFloat.zero
    @State private var contentOffset = CGFloat.zero

    var body: some View {
        ScrollView {
            LazyVStack(pinnedViews: [.sectionHeaders]) {
                Section {
                    LazyVStack {
                        ForEach(1..<11) { n in
                            Text("Row \(n)")
                        }
                    }
                    .overlay {
                        RoundedRectangle(cornerRadius: 12)
                            .stroke(.blue, lineWidth: 1)
                            .padding(.top, min(0, headerOffset - contentOffset))
                    }
                    .onGeometryChange(for: CGFloat.self) { proxy in
                        proxy.frame(in: .scrollView).minY
                    } action: { minY in
                        contentOffset = minY
                    }
                } header: {
                    Text("Header")
                        .onGeometryChange(for: CGFloat.self) { proxy in
                            proxy.frame(in: .scrollView).minY
                        } action: { minY in
                            headerOffset = minY
                        }
                }
                .padding()
            }
        }
    }
}


Since .onGeometryChange is being used twice, you might find it convenient to create a ViewModifier to perform the work, plus a view extension to apply it:

struct ScrollOffsetReader: ViewModifier {
    @Binding var offset: CGFloat

    func body(content: Content) -> some View {
        content
            .onGeometryChange(for: CGFloat.self) { proxy in
                proxy.frame(in: .scrollView).minY
            } action: { minY in
                offset = minY
            }
    }
}

extension View {
    func scrollOffsetReader(offset: Binding<CGFloat>) -> some View {
        modifier(ScrollOffsetReader(offset: offset))
    }
}

The code now simplifies to the following:

Section {
    LazyVStack {
        // ...
    }
    .overlay {
        // ...
    }
    .scrollOffsetReader(offset: $contentOffset)
} header: {
    Text("Header")
        .scrollOffsetReader(offset: $headerOffset)
}

本文标签: swiftHow to create pinned views in SwiftUIStack Overflow