SwiftUI 将部分列表固定到底部

Posted

技术标签:

【中文标题】SwiftUI 将部分列表固定到底部【英文标题】:SwiftUI pin partial list to bottom 【发布时间】:2021-05-19 01:41:38 【问题描述】:

我有一个聊天消息列表,随着新消息添加到最后,它会向上滚动。但如果视图中的消息太少,它们会固定在顶部。

有没有办法把内容固定在底部?

请注意,在这种情况下,视图位于 UIHostingController 内。

struct OverlayChatView: View 
    @ObservedObject public  var stream: ChatStream
    
    var body: some View 
        ScrollViewReader  scrollView in
            ScrollView 
                LazyVStack(alignment: .leading, spacing: 0.0) 
                    ForEach(self.stream.messages)  inMsg in
                        ChatMessageCell(message: inMsg)
                    
                    .padding(8)
                
            
            .onAppear 
                if self.stream.messages.count > 0
                
                    scrollView.scrollTo(self.stream.messages.last!.id, anchor: .bottom)
                
            
            .onChange(of: self.stream.messages, perform:  inMessages in
                if inMessages.count < 1  return 
                
                withAnimation(.linear(duration: 0.25)) 
                    scrollView.scrollTo(inMessages.last!.id, anchor: .bottom)
                
            )
        
    

【问题讨论】:

更新帖子以显示代码。 【参考方案1】:

您可以根据内容高度与整个滚动视图高度之间的差异使用动态大小的Spacer。如果内容较小,则显示Spacer -- 如果不是,则仅显示内容。


struct ContentView: View 
    @State private var contentHeight : CGFloat = 0
    @State private var totalHeight : CGFloat = 0
    
    var body: some View 
        ScrollViewReader  scroller in
            ScrollView 
                if contentHeight < totalHeight 
                    Spacer().frame(height: totalHeight - contentHeight)
                
                VStack 
                    Text("My content")
                    Text("More content")
                
                .frame(maxWidth: .infinity)
                .background(GeometryReader 
                    Color.clear.preference(key: ContentHeight.self,
                                           value: $0.frame(in: .local).size.height)
                )
                .onPreferenceChange(ContentHeight.self)  contentHeight = $0 
            
            .background(GeometryReader 
                Color.clear.preference(key: TotalHeight.self,
                                       value: $0.frame(in: .local).size.height)
            )
            .border(Color.red)
            .onPreferenceChange(TotalHeight.self)  totalHeight = $0 
        
    
    
    struct TotalHeight : PreferenceKey 
        static var defaultValue: CGFloat  0 
        static func reduce(value: inout Value, nextValue: () -> Value) 
            value = value + nextValue()
        
    

    struct ContentHeight : PreferenceKey 
        static var defaultValue: CGFloat  0 
        static func reduce(value: inout Value, nextValue: () -> Value) 
            value = value + nextValue()
        
    


【讨论】:

这回答了你的问题吗? 还没有机会尝试,但我会尝试的!不过,我想我会提交一个错误,请求对此提供内置支持,因为这是一项相当多的工作! 好的。如果答案有帮助,点赞总是好的。【参考方案2】:

我对这个答案并不完全满意,因为它需要一些硬编码,并且不像 ScrollView 中的直接支持那样优雅,只允许滚动到内容的末尾,但它相当简单。

@jnpdx 的答案在正确的轨道上。

在我发布这个问题后的几周内,我学到了更多的 SwiftUI 技术。在这种情况下,您可以在 ScrollView 内部和外部添加几乎任意的填充和视图,包括列表/堆栈/任何内容(但不能在 ForEach 内部,这不会做你想要的!)。

我将ScrollViewReader 包裹在GeometryReader 中,以使整体聊天视图高度可用于内部视图,然后我在LazyVStack 顶部添加了等于高度减去硬编码量等于的填充到一行聊天的高度(这是我不喜欢的硬编码部分)。

struct OverlayChatView: View 
    @ObservedObject public  var stream: ChatStream
    
    var body: some View 
        GeometryReader
         inChatView in
            ScrollViewReader  scrollView in
                ScrollView 
                    LazyVStack(alignment: .leading, spacing: 0.0) 
                        ForEach(self.stream.messages)  inMsg in
                            ChatMessageCell(message: inMsg)
                        
                        .padding(.top, inChatView.size.height - 26.0)
                        .padding([.horizontal, .bottom], 8)
                    
                
                .onAppear 
                    if self.stream.messages.count > 0
                    
                        scrollView.scrollTo(self.stream.messages.last!.id, anchor: .bottom)
                    
                
                .onChange(of: self.stream.messages, perform:  inMessages in
                    if inMessages.count < 1  return 
                
                    withAnimation(.linear(duration: 0.25)) 
                        scrollView.scrollTo(inMessages.last!.id, anchor: .bottom)
                    
                )
            
        
    

这提供了ScrollView 需要让您一直向下滚动的额外内容。

【讨论】:

以上是关于SwiftUI 将部分列表固定到底部的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 向视图添加 -y 偏移量,但将视图拉伸到底部

SwiftUI:在横向列表中点击导航栏不会将列表滚动到顶部

SwiftUI - ScrollView 没有一直滚动到底部

SwiftUI不借助ScrollViewReader和ScrollViewProxy实现List自动滚动到底部

SwiftUI不借助ScrollViewReader和ScrollViewProxy实现List自动滚动到底部

SwiftUI 无法添加 onChange 事件