SwiftUI聊天应用程序:颠倒列表和上下文菜单的悲哀
我正在SwiftUI中构建一个聊天应用程序。要在聊天中显示消息,我需要一个反向列表,在底部显示最近的条目,并自动滚动到底部。通过翻转列表及其每个条目,我创建了一个反向列表 现在我想在消息中添加上下文菜单。但在长时间按下后,菜单显示消息翻转。我认为这是有道理的,因为它从列表中删除了一条被翻转的消息 你有没有想过如何让它发挥作用SwiftUI聊天应用程序:颠倒列表和上下文菜单的悲哀,swiftui,Swiftui,我正在SwiftUI中构建一个聊天应用程序。要在聊天中显示消息,我需要一个反向列表,在底部显示最近的条目,并自动滚动到底部。通过翻转列表及其每个条目,我创建了一个反向列表 现在我想在消息中添加上下文菜单。但在长时间按下后,菜单显示消息翻转。我认为这是有道理的,因为它从列表中删除了一条被翻转的消息 你有没有想过如何让它发挥作用 如果我理解正确,为什么不在for each循环或previor循环中对数组进行排序呢。那么您根本不必使用任何scaleEffect。稍后,如果您收到消息对象,您可能会指定一
如果我理解正确,为什么不在for each循环或previor循环中对数组进行排序呢。那么您根本不必使用任何scaleEffect。稍后,如果您收到消息对象,您可能会指定一个日期,因此您可以按日期排序。在上述情况下,您可以使用:
ForEach(arr.reverse(), id: \.self) { item in
...
}
它将在顶部打印12ccccc作为第一条消息,1aaaaa作为最后一条消息。如果我理解正确,为什么不在for each循环或之前订购阵列呢。那么您根本不必使用任何scaleEffect。稍后,如果您收到消息对象,您可能会指定一个日期,因此您可以按日期排序。在上述情况下,您可以使用:
ForEach(arr.reverse(), id: \.self) { item in
...
}
它将在顶部打印12ccccc作为第一条消息,1aaaaa作为最后一条消息。您可以为回复创建自定义模式,并在列表的每个元素上长按显示,而不显示上下文菜单 像这样应用它:
ForEach(arr, id: \.self) { item in
VStack {
Text(item)
.height(100)
.scaleEffect(x: 1, y: -1, anchor: .center)
}.gesture(self.longPress)
}
您可以为reply创建一个自定义模式,并在列表的每个元素上长按以显示它,而不显示contextMenu 像这样应用它:
ForEach(arr, id: \.self) { item in
VStack {
Text(item)
.height(100)
.scaleEffect(x: 1, y: -1, anchor: .center)
}.gesture(self.longPress)
}
翻转的问题是,您需要翻转上下文菜单,而SwiftUI没有提供这么多控制 更好的处理方法是访问嵌入式UITableViews,您将在其上拥有更多的控制权,并且无需添加额外的攻击 以下是演示代码:
import SwiftUI
import UIKit
struct TestView: View {
@State var arr = ["1aaaaa","2bbbbb", "3ccccc", "4aaaaa","5bbbbb", "6ccccc", "7aaaaa","8bbbbb", "9ccccc", "10aaaaa","11bbbbb", "12ccccc"]
@State var tableView: UITableView? {
didSet {
self.tableView?.adaptToChatView()
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.tableView?.scrollToBottom(animated: true)
}
}
}
var body: some View {
NavigationView {
List {
UIKitView { (tableView) in
DispatchQueue.main.async {
self.tableView = tableView
}
}
ForEach(arr, id: \.self) { item in
Text(item).contextMenu {
Button(action: {
// change country setting
}) {
Text("Choose Country")
Image(systemName: "globe")
}
Button(action: {
// enable geolocation
}) {
Text("Detect Location")
Image(systemName: "location.circle")
}
}
}
}
.navigationBarTitle(Text("Chat View"), displayMode: .inline)
.navigationBarItems(trailing:
Button("add chat") {
self.arr.append("new Message: \(self.arr.count)")
self.tableView?.adaptToChatView()
DispatchQueue.main.async {
self.tableView?.scrollToBottom(animated: true)
}
})
}
}
}
extension UITableView {
func adaptToChatView() {
let offset = self.contentSize.height - self.visibleSize.height
if offset < self.contentOffset.y {
self.tableHeaderView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.contentSize.width, height: self.contentOffset.y - offset))
}
}
}
extension UIScrollView {
func scrollToBottom(animated:Bool) {
let offset = self.contentSize.height - self.visibleSize.height
if offset > self.contentOffset.y {
self.setContentOffset(CGPoint(x: 0, y: offset), animated: animated)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
final class UIKitView : UIViewRepresentable {
let callback: (UITableView) -> Void //return TableView in CallBack
init(leafViewCB: @escaping ((UITableView) -> Void)) {
callback = leafViewCB
}
func makeUIView(context: Context) -> UIView {
let view = UIView.init(frame: CGRect(x: CGFloat.leastNormalMagnitude,
y: CGFloat.leastNormalMagnitude,
width: CGFloat.leastNormalMagnitude,
height: CGFloat.leastNormalMagnitude))
view.backgroundColor = .clear
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
if let tableView = uiView.next(UITableView.self) {
callback(tableView) //return tableview if find
}
}
}
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
翻转的问题是,您需要翻转上下文菜单,而SwiftUI没有提供这么多控制 更好的处理方法是访问嵌入式UITableViews,您将在其上拥有更多的控制权,并且无需添加额外的攻击 以下是演示代码:
import SwiftUI
import UIKit
struct TestView: View {
@State var arr = ["1aaaaa","2bbbbb", "3ccccc", "4aaaaa","5bbbbb", "6ccccc", "7aaaaa","8bbbbb", "9ccccc", "10aaaaa","11bbbbb", "12ccccc"]
@State var tableView: UITableView? {
didSet {
self.tableView?.adaptToChatView()
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.tableView?.scrollToBottom(animated: true)
}
}
}
var body: some View {
NavigationView {
List {
UIKitView { (tableView) in
DispatchQueue.main.async {
self.tableView = tableView
}
}
ForEach(arr, id: \.self) { item in
Text(item).contextMenu {
Button(action: {
// change country setting
}) {
Text("Choose Country")
Image(systemName: "globe")
}
Button(action: {
// enable geolocation
}) {
Text("Detect Location")
Image(systemName: "location.circle")
}
}
}
}
.navigationBarTitle(Text("Chat View"), displayMode: .inline)
.navigationBarItems(trailing:
Button("add chat") {
self.arr.append("new Message: \(self.arr.count)")
self.tableView?.adaptToChatView()
DispatchQueue.main.async {
self.tableView?.scrollToBottom(animated: true)
}
})
}
}
}
extension UITableView {
func adaptToChatView() {
let offset = self.contentSize.height - self.visibleSize.height
if offset < self.contentOffset.y {
self.tableHeaderView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.contentSize.width, height: self.contentOffset.y - offset))
}
}
}
extension UIScrollView {
func scrollToBottom(animated:Bool) {
let offset = self.contentSize.height - self.visibleSize.height
if offset > self.contentOffset.y {
self.setContentOffset(CGPoint(x: 0, y: offset), animated: animated)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
final class UIKitView : UIViewRepresentable {
let callback: (UITableView) -> Void //return TableView in CallBack
init(leafViewCB: @escaping ((UITableView) -> Void)) {
callback = leafViewCB
}
func makeUIView(context: Context) -> UIView {
let view = UIView.init(frame: CGRect(x: CGFloat.leastNormalMagnitude,
y: CGFloat.leastNormalMagnitude,
width: CGFloat.leastNormalMagnitude,
height: CGFloat.leastNormalMagnitude))
view.backgroundColor = .clear
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
if let tableView = uiView.next(UITableView.self) {
callback(tableView) //return tableview if find
}
}
}
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
谢谢数组已按相反顺序排列。问题在于,无法通过编程方式将ListView滚动到底部,而这正是聊天应用程序所需要的。因此需要翻转。我在帖子中编辑了代码,以明确数组是反向的。谢谢。数组已按相反顺序排列。问题在于,无法通过编程方式将ListView滚动到底部,而这正是聊天应用程序所需要的。因此需要翻转。我在帖子中编辑了代码,以明确数组的顺序是相反的。编辑:将代码明确表示数组的顺序是相反的。您是否尝试将scaleEffect应用于contextMenu?@AlexanderUshakov是,未工作编辑:使代码清楚显示数组的顺序相反。是否尝试将scaleEffect应用于contextMenu?@AlexanderUshakov是的,未工作谢谢。是的,实际上我可以重新实现上下文菜单。我希望不用那样做,谢谢。是的,实际上我可以重新实现上下文菜单。我希望不用那样做,谢谢。这是可行的,在SwiftUI视图中添加一种不可见的嗅探器是一个好主意。我不得不做一些修改以使其不出现故障,特别是修改并为异步调用添加额外的延迟?我删除了它,没有它一切都很好。当你有一些消息,并希望垫顶部而不是底部时,需要调整。如果不需要,你可以忽略。这个想法是使用UITableView。希望我的回答有帮助。干杯,谢谢。这是可行的,在SwiftUI视图中添加一种不可见的嗅探器是一个好主意。我不得不做一些修改以使其不出现故障,特别是修改并为异步调用添加额外的延迟?我删除了它,没有它一切都很好。当你有一些消息,并希望垫顶部而不是底部时,需要调整。如果不需要,你可以忽略。这个想法是使用UITableView。希望我的回答有帮助。干杯