SwiftUI 将部分列表固定到底部
SwiftUI pin partial list to bottom
我有一个聊天消息列表,当在末尾添加新消息时,该列表会向上滚动。但如果视图中的消息太少,它们会固定在顶部。
有没有办法把内容固定在底部?
请注意,在这种情况下,视图位于 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)
}
})
}
}
}
您可以根据内容高度与整个滚动视图高度之间的差异使用动态大小 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: [=10=].frame(in: .local).size.height)
})
.onPreferenceChange(ContentHeight.self) { contentHeight = [=10=] }
}
.background(GeometryReader {
Color.clear.preference(key: TotalHeight.self,
value: [=10=].frame(in: .local).size.height)
})
.border(Color.red)
.onPreferenceChange(TotalHeight.self) { totalHeight = [=10=] }
}
}
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()
}
}
}
我对这个答案不是很满意,因为它需要一些硬编码,而且不像 ScrollView
中的直接支持那样优雅,只允许滚动到内容的末尾,但它是相当简单。
@jnpdx 的上述回答是正确的。
在我发布这个问题后的几周里,我学到了更多的 SwiftUI 技术。在这种情况下,您可以在 ScrollView
中添加几乎任意的填充和视图,在包含的 list/stack/whatever 内部和外部(但不在 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
让您一直向下滚动所需的额外内容。
我有一个聊天消息列表,当在末尾添加新消息时,该列表会向上滚动。但如果视图中的消息太少,它们会固定在顶部。
有没有办法把内容固定在底部?
请注意,在这种情况下,视图位于 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)
}
})
}
}
}
您可以根据内容高度与整个滚动视图高度之间的差异使用动态大小 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: [=10=].frame(in: .local).size.height)
})
.onPreferenceChange(ContentHeight.self) { contentHeight = [=10=] }
}
.background(GeometryReader {
Color.clear.preference(key: TotalHeight.self,
value: [=10=].frame(in: .local).size.height)
})
.border(Color.red)
.onPreferenceChange(TotalHeight.self) { totalHeight = [=10=] }
}
}
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()
}
}
}
我对这个答案不是很满意,因为它需要一些硬编码,而且不像 ScrollView
中的直接支持那样优雅,只允许滚动到内容的末尾,但它是相当简单。
@jnpdx 的上述回答是正确的。
在我发布这个问题后的几周里,我学到了更多的 SwiftUI 技术。在这种情况下,您可以在 ScrollView
中添加几乎任意的填充和视图,在包含的 list/stack/whatever 内部和外部(但不在 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
让您一直向下滚动所需的额外内容。