Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Swift 使用MVVM体系结构的Firestore分页_Swift_Mvvm_Google Cloud Firestore - Fatal编程技术网

Swift 使用MVVM体系结构的Firestore分页

Swift 使用MVVM体系结构的Firestore分页,swift,mvvm,google-cloud-firestore,Swift,Mvvm,Google Cloud Firestore,我不太明白我做错了什么,因为我是MVVM的新手。它在MVC架构中工作。我已经设置了我的虚拟机,能够得到第一组结果,即使这样,也不能正常工作。我得到了4个结果,而不是10个,这是LOADLIMIT设置的结果。我能够让它在MVC架构中工作,没有任何问题。触发查询的VM函数被调用多次(3次),而不是仅调用一次,即在滚动之前 这是我的虚拟机: enum FetchRestaurant { case success case error case location case

我不太明白我做错了什么,因为我是MVVM的新手。它在MVC架构中工作。我已经设置了我的虚拟机,能够得到第一组结果,即使这样,也不能正常工作。我得到了4个结果,而不是10个,这是LOADLIMIT设置的结果。我能够让它在MVC架构中工作,没有任何问题。触发查询的VM函数被调用多次(3次),而不是仅调用一次,即在滚动之前

这是我的虚拟机:

enum FetchRestaurant {
    case success
    case error
    case location
    case end
}
class ListViewModel {
    
    let restaurant: [Restaurant]?
    let db = Firestore.firestore()
    
    var restaurantArray = [Restaurant]()
    var lastDocument: DocumentSnapshot?
    var currentLocation: CLLocation?
    
    typealias fetchRestaurantCallback = (_ restaurants: [Restaurant]?, _ message: String?, _ status: FetchRestaurant) -> Void
    var restaurantFetched: fetchRestaurantCallback?
    
    var fetchRestaurant: FetchRestaurant?
    
    init(restaurant: [Restaurant]) {
        self.restaurant = restaurant
    }
    
    func fetchRestaurantCallback (callback: @escaping fetchRestaurantCallback) {
        self.restaurantFetched = callback
    }
    
    func fetchRestaurants(address: String) {
        print("address received: \(address)")
        getLocation(from: address) { location in
            if let location = location {
                self.currentLocation = location
                self.queryGenerator(at: location)
            } else {
                self.restaurantFetched?(nil, nil, .location)
            }
        }
    }
    
    func queryGenerator(at location: CLLocation) {
        var query: Query!
if restaurantArray.isEmpty {
            query = db.collection("Restaurant_Data").whereField("distributionType", isLessThanOrEqualTo: 2).limit(to: Constants.Mealplan.LOADLIMIT)
        } else {
            print("last document:\(String(describing: lastDocument?.documentID))")
            query = db.collection("Restaurant_Data").whereField("distributionType", isLessThanOrEqualTo: 2).start(afterDocument: lastDocument!).limit(to: Constants.Mealplan.LOADLIMIT)
        }
        batchFetch(query: query)
    }
    
    func batchFetch(query: Query) {
        query.getDocuments { (querySnapshot, error) in
            if let error = error {
                self.restaurantFetched?(nil, error.localizedDescription, .error)
            } else if querySnapshot!.isEmpty {
self.restaurantFetched?(nil, nil, .end)
            } else if !querySnapshot!.isEmpty {
                let queriedRestaurants = querySnapshot?.documents.compactMap { querySnapshot -> Restaurant? in
                    return try? querySnapshot.data(as: Restaurant.self)
                }
                guard let restaurants = queriedRestaurants,
                  let currentLocation = self.currentLocation else {
                self.restaurantFetched?(nil, nil, .end)
                return }
                self.restaurantArray.append(contentsOf: self.applicableRestaurants(allQueriedRestaurants: restaurants, location: currentLocation))
                DispatchQueue.main.asyncAfter(deadline: .now(), execute: {
                    self.restaurantFetched?(self.restaurantArray, nil, .success)
                })
                self.lastDocument = querySnapshot!.documents.last
            }
        }
    }

    func getLocation(from address: String, completionHandler: @escaping (_ location: CLLocation?) -> Void) {
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(address) { (placemarks, error) in
            guard let placemarks = placemarks,
            let location = placemarks.first?.location else {
                completionHandler(nil)
                return
            }
            completionHandler(location)
        }
    }

}
在VC viewDidLoad中:

var fetchMore = false
var reachedEnd = false
let leadingScreensForBatching: CGFloat = 5.0
var searchController = UISearchController(searchResultsController: nil)
var currentAddress : String?

var listViewModel = ListViewModel(restaurant: [Restaurant]())
override func viewDidLoad() {
        super.viewDidLoad()

        listViewModel.fetchRestaurantCallback { (restaurants, error, result) in
            switch result {
            
            case .success :
                self.loadingShimmer.stopShimmering()
                self.loadingShimmer.removeFromSuperview()
                guard let fetchedRestaurants = restaurants else { return }
                self.restaurantArray.append(contentsOf: fetchedRestaurants)
                self.tableView.reloadData()
                self.fetchMore = false
                
            case .location :
                self.showAlert(alertTitle: "No businesses nearby", message: "Try going back and changing the address")
                
  

      case .error :
            guard let error = error else { return }
            self.showAlert(alertTitle: "Error", message: error)
            
        case .end :
            self.fetchMore = false
            self.reachedEnd = true
        }
    }
    
    if let currentAddress = currentAddress {
        listViewModel.fetchRestaurants(address: currentAddress)
    }
}
我非常感谢在Firestore后端的Swift中实现MVVM的链接或资源。我在这里和谷歌上的搜索都不够。甚至试过中等

编辑

  • 您只返回了4个结果而不是10个结果,这并不是由于错误的查询或获取文档请求导致的,因为这些请求的编码正确。解析文档时可能会丢失文档(有些文档初始化失败),
    常量.Mealplan.LOADLIMIT
    错误,或者集合中满足查询的文档不超过4个

  • 查询执行3次而不是一次也不是由于此代码中的任何内容-
    viewDidLoad
    只调用一次,而
    geocodeAddressString
    只返回一次。您正在其他地方发出我们看不到的获取请求

  • batchFetch
    方法中,您有一个从函数中返回的保护,而不调用其完成处理程序。这将使UI处于不确定状态。我建议无论函数返回的原因是什么,始终调用完成处理程序

  • 您永远不会管理文档光标。如果get document return的文档少于加载限制,则最后一个文档光标为零。这样,当您试图获取下一页文档时,请防止出现nil光标,并查看是否还有更多文档需要获取

  • 不需要传入空数组并让函数填充它;只需在
    ListViewModel
    本身中构造并返回一个结果数组

  • 我们看不到你是如何触发分页的。例如,当用户到达底部时,是通过滚动代理还是通过点击按钮?如果它是通过一个滚动委托,那么我现在就禁用它,看看你得到了多少回报——我怀疑是一个,而不是3个


  • 您放弃MVC而选择MVVM的具体原因是什么?使用MVC,只需几行代码就可以完成分页。我认为MVVM对于iOS应用程序来说过于苛刻,除非你有令人信服的理由,否则建议不要使用它。

    一般的建议是,MVVM中的M代表模型,但也代表服务和存储库类等,我认为你应该通过将firebase代码移出视图模型并放入自己的类(或结构)来使用它分开。关注点。也就是说,在我让虚拟机、控制器和视图通过必要的验收测试后,我一直在这样做。顺便说一句,我认为是MVVM的创始人之一说,对于大多数应用程序来说,这种体系结构太过复杂了,尤其是对于像iOS应用程序这样的简单应用程序。好吧,它是专门为微软的工作而设计的。我可以肯定的是,iOS文档几乎完全是MVC。首先感谢您的详细回复。以下是一些评论:1。根据您的反馈,我能够调试并解决它。2.这是由我在上面的编辑中添加的scrollview函数引起的。有趣的是,我在MVC中也有这个,它只被调用过一次。3.我有点理解你在说什么,所以我用完成处理程序更新了这个块,如上面4所示。我不太明白这一点。为什么最后一个文档光标为docsclass ListViewController: UITableViewController { lazy var loadingShimmer: UIImageView = { let image = UIImage(named: "shimmer_background") let imageview = UIImageView(image: image) imageview.contentMode = .top imageview.translatesAutoresizingMaskIntoConstraints = false return imageview }() var restaurantArray = [Restaurant]() var planDictionary = [String: Any]() var fetchMore = false var reachedEnd = false let leadingScreensForBatching: CGFloat = 5.0 var searchController = UISearchController(searchResultsController: nil) var currentAddress : String? var listViewModel = ListViewModel(restaurant: [Restaurant]()) override func viewDidLoad() { super.viewDidLoad() setupTable() } override func viewWillAppear(_ animated: Bool) { clearsSelectionOnViewWillAppear = false } func setupTable() { navigationItem.backBarButtonItem = UIBarButtonItem(title: "Restaurant", style: .plain, target: nil, action: nil) tableView.register(RestaurantCell.self, forCellReuseIdentifier: "Cell") tableView.delegate = self tableView.dataSource = self let navigationBarHeight: CGFloat = self.navigationController!.navigationBar.frame.height tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: -navigationBarHeight, right: 0) tableView.separatorStyle = .none tableView.showsVerticalScrollIndicator = false tableView.addSubview(loadingShimmer) loadingShimmer.topAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.topAnchor).isActive = true loadingShimmer.leadingAnchor.constraint(equalTo: tableView.leadingAnchor).isActive = true loadingShimmer.trailingAnchor.constraint(equalTo: tableView.trailingAnchor).isActive = true loadingShimmer.startShimmering() initialSetup() } func initialSetup() { let addressOne = planDictionary["addressOne"] as! String + ", " let city = planDictionary["city"] as! String + ", " let postalCode = planDictionary["postalCode"] as! String currentAddress = addressOne + city + postalCode setupSearch() listViewModel.fetchRestaurantCallback { (restaurants, error, result) in switch result { case .success : self.loadingShimmer.stopShimmering() self.loadingShimmer.removeFromSuperview() guard let fetchedRestaurants = restaurants else { return } self.restaurantArray.append(contentsOf: fetchedRestaurants) self.tableView.reloadData() self.fetchMore = false case .location : self.showAlert(alertTitle: "No businesses nearby", message: "Try going back and changing the address") case .error : guard let error = error else { return } self.showAlert(alertTitle: "Error", message: error) case .end : self.fetchMore = false self.reachedEnd = true } } if let currentAddress = currentAddress { listViewModel.fetchRestaurants(address: currentAddress) } } override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let off = scrollView.contentOffset.y let off1 = scrollView.contentSize.height if off > off1 - scrollView.frame.height * leadingScreensForBatching { print("\(fetchMore), \(reachedEnd)") if !fetchMore && !reachedEnd { if let address = self.currentAddress { print("address sent: \(address)") listViewModel.fetchRestaurants(address: address) } } } } }