SwiftUI聊天应用程序:反向列表和上下文菜单的困扰

时间:2020-05-11 09:29:23

标签: swiftui

我正在SwiftUI中构建聊天应用程序。要在聊天中显示消息,我需要一个反向列表(该列表在底部显示最新条目,并在底部自动滚动)。通过翻转列表及其每个条目(the standard way of doing it),我制作了一个反向列表。

现在,我想在消息中添加上下文菜单。但是长按后,菜单显示消息翻转。我认为这是有道理的,因为它从列表中删除了一条翻转的消息。

关于如何使它起作用的任何想法?

enter image description here

import SwiftUI

struct TestView: View {
    var arr = ["1aaaaa","2bbbbb", "3ccccc", "4aaaaa","5bbbbb", "6ccccc", "7aaaaa","8bbbbb", "9ccccc", "10aaaaa","11bbbbb", "12ccccc"]

    var body: some View {
        List {
            ForEach(arr.reversed(), id: \.self) { item in
                VStack {
                    Text(item)
                        .height(100)
                        .scaleEffect(x: 1, y: -1, anchor: .center)
                }
                .contextMenu {
                    Button(action: { }) {
                        Text("Reply")
                    }
                }
            }
        }
        .scaleEffect(x: 1, y: -1, anchor: .center)

    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

3 个答案:

答案 0 :(得分:1)

您可以创建一个自定义模式来进行回复,并在列表的每个元素上长按即可显示它,而无需显示contextMenu

@State var showYourCustomReplyModal = false
@GestureState var isDetectingLongPress = false
var longPress: some Gesture {
    LongPressGesture(minimumDuration: 0.5)
        .updating($isDetectingLongPress) { currentstate, gestureState,
                transaction in
            gestureState = currentstate
        }
        .onEnded { finished in
            self.showYourCustomReplyModal = true
        }
}

像这样应用它:

        ForEach(arr, id: \.self) { item in
            VStack {
                Text(item)
                    .height(100)
                    .scaleEffect(x: 1, y: -1, anchor: .center)
            }.gesture(self.longPress)
        }

答案 1 :(得分:1)

翻转的问题在于您需要翻转上下文菜单,而SwiftUI却没有提供太多控制权。

处理此问题的更好方法是访问嵌入式UITableView(将对其具有更多控制权),而无需添加其他技巧。

enter image description here

这是演示代码:

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)
    }
}

答案 2 :(得分:0)

如果我对它的理解正确,为什么不为每个循环或之前的命令在数组中排序。然后,您根本不必使用任何scaleEffect。稍后,如果您获得了消息对象,则可能已将日期与它关联,因此可以按日期对其进行排序。在上述情况下,您可以使用:

ForEach(arr.reverse(), id: \.self) { item in

...
}

哪个将12ccccc打印为顶部的第一条消息,并将1aaaaa打印为最后的消息。