【问题标题】:CollectionView vs TableView (Swift)CollectionView 与 TableView (Swift)
【发布时间】:2018-03-30 13:38:39
【问题描述】:

我有两个类做完全相同的事情,加载靠近用户位置的所选类别的位置,但一个使用 collectionView,另一个使用 tableView(和特定动画)。

我只添加了解决问题的代码部分,虽然有些名称不同,但所有与故事板和其他类的链接都是相同的,因为您可以看到这两个类之间的大部分代码非常相似。

我的问题是带有collectionView的类工作完美,但是使用tableView的类没有显示tableView的单元格(好像它没有正确加载它)如果有人能看到是否有错误,我会感激不尽。

这是使用 collectionView 的类:

import UIKit
import CoreLocation
import AVFoundation

private let reuseIdentifier = "PlacesCell"

extension UIViewController {
    func present(viewController : UIViewController, completion : (() -> ())? = nil ){
        if let presented = self.presentedViewController {
            presented.removeFromParentViewController()
        }
        self.present(viewController, animated: true, completion: completion)
    }
}

class NearbyPlacesViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    var locationManager:CLLocationManager?

    let minimumSpacing : CGFloat = 15 //CGFloat(MAXFLOAT)
    let cellWidth: CGFloat = 250
    let radius = 5000 // 5km
    var category : QCategory?
    var currentLocation : CLLocationCoordinate2D?
    var places: [QPlace] = []

    var isLoading = false
    var response : QNearbyPlacesResponse?

    override func viewDidLoad() {
        super.viewDidLoad()

        //self.collectionView
        self.title = category?.name

        collectionView?.contentInset = UIEdgeInsets.init(top: 0, left: minimumSpacing, bottom: 0, right: minimumSpacing)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        determineMyCurrentLocation()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        category?.markView()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func canLoadMore() -> Bool {
        if isLoading {
            return false
        }

        if let response = self.response {
            if (!response.canLoadMore()) {
                return false
            }
        }
        return true
    }

    func loadPlaces(_ force:Bool) {

        if !force {
            if !canLoadMore() {
                return
            }
        }

        print("load more")
        isLoading = true
        NearbyPlacesController.getNearbyPlaces(by: category?.name ?? "food", coordinates: currentLocation!, radius: radius, token: self.response?.nextPageToken, completion: didReceiveResponse)
    }

    func didReceiveResponse(response:QNearbyPlacesResponse?, error : Error?) -> Void {
        if let error = error {
            let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
            let actionDismiss = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
            let actionRetry = UIAlertAction(title: "Retry", style: .default, handler: { (action) in
                DispatchQueue.main.async {
                    self.loadPlaces(true)
                }
            })
            alertController.addAction(actionRetry)
            alertController.addAction(actionDismiss)
            DispatchQueue.main.async {
                self.present(viewController: alertController)
            }
        }
        if let response = response {
            self.response = response
            if response.status == "OK" {
                if let placesDownloaded = response.places {
                    places.append(contentsOf: placesDownloaded)
                }

                self.collectionView?.reloadData()
            } else {
                let alert = UIAlertController.init(title: "Error", message: response.status, preferredStyle: .alert)
                alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
                alert.addAction(UIAlertAction.init(title: "Retry", style: .default, handler: { (action) in
                    DispatchQueue.main.async {
                        self.loadPlaces(true)
                    }
                }))
                self.present(viewController: alert)
            }
            isLoading = false
        }
        else {
            print("response is nil")
        }
    }

    // MARK: UICollectionViewDataSource

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return places.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PlacesCell

        let place = places[indexPath.row]
        cell.update(place: place)

        if indexPath.row == places.count - 1 {
            loadPlaces(false)
        }

        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        performSegue(withIdentifier: "maps-vc", sender: indexPath)
    }

    // MARK: UICollectionViewDelegateFlowLayoutDelegate

    public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return minimumSpacing
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        let cellPadding: CGFloat = 20.0
        let columnWidth:CGFloat = cellWidth
        let imageWidth = columnWidth
        let labelWidth = columnWidth - cellPadding * 2

        let photoHeight = heightForPhotoAtIndexPath(indexPath: indexPath, withWidth: imageWidth)
        let annotationHeight = heightForAnnotationAtIndexPath(indexPath: indexPath, withWidth: labelWidth)
        let height = photoHeight + annotationHeight

        return CGSize.init(width: columnWidth, height: height)
    }

    // Calculates the height of photo
    func heightForPhotoAtIndexPath(indexPath: IndexPath,
                                   withWidth width: CGFloat) -> CGFloat {

        var size = CGSize.init(width: CGFloat(MAXFLOAT), height: 1)
        let place = places[indexPath.row]

        guard let photo = place.photos?.first, place.photos?.first?.photoRef != nil else {
            return 0
        }

        size = CGSize.init(width: CGFloat(photo.width!), height: CGFloat(photo.height!))

        let boundingRect =  CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
        let rect  = AVMakeRect(aspectRatio: size, insideRect: boundingRect)

        return rect.size.height
    }

    // Calculates the height label
    func heightForAnnotationAtIndexPath(indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat {

        let place = places[indexPath.row]
        let annotationPadding = CGFloat(5)

        let font = UIFont.systemFont(ofSize: 15)
        let commentHeight = place.heightForComment(font, width: width)

        let height = annotationPadding + commentHeight + annotationPadding

        return height
    }

    // did receive location

    func didReceiveUserLocation(_ userLocation:CLLocation) {
        currentLocation = userLocation.coordinate

        loadPlaces(true)
    }        
     // MARK: - Navigation

     // In a storyboard-based application, you will often want to do a little preparation before navigation
     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "maps-vc" && sender is IndexPath {
            let dvc = segue.destination as! MapViewController
            dvc.index = (sender as! IndexPath).row
            dvc.places = places
            dvc.userLocation = currentLocation
        }
     }
}

这是使用 tableView 的类:

import UIKit
import MapKit
import CoreLocation
import GoogleMaps
import GooglePlaces
import Social
import AVFoundation

private let resueIdentifier = "MyTableViewCell"

extension UIViewController {
    func present(viewController : UIViewController, completion : (() -> ())? = nil ){
        if let presented = self.presentedViewController {
            presented.dismiss(animated: true, completion: {
                self.present(viewController, animated: true, completion: completion)
            })
        } else {
            self.present(viewController, animated: true, completion: completion)
        }
    }
}

class CourseClass2: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    var locationManager:CLLocationManager?
    let minimumSpacing : CGFloat = 15 //CGFloat(MAXFLOAT)
    let cellWidth: CGFloat = 250
    let radius = 5000 // 5km
    var category : QCategoryy?
    var currentLocation : CLLocationCoordinate2D?
    var places: [QPlace] = []
    var isLoading = false
    var response : QNearbyPlacesResponse?
    var rows = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = category?.name
}

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        determineMyCurrentLocation()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        rows = 0
        insertRowsMode3()
        tableView.reloadData()
        category?.markView()
}
    @IBAction func refreshTapped(_ sender: Any) {
        rows = 0
        insertRowsMode3()
        tableView.reloadData()
    }

    func canLoadMore() -> Bool {
        if isLoading {
            return false
        }
        if let response = self.response {
            if (!response.canLoadMore()) {
                return false
            }
        }
        return true
    }

    func loadPlaces(_ force:Bool) {

        if !force {
            if !canLoadMore() {
                return
            }
        }

        print("load more")
        isLoading = true
        NearbyPlaces.getNearbyPlaces(by: category?.name ?? "food", coordinates: currentLocation!, radius: radius, token: self.response?.nextPageToken, completion: didReceiveResponse)
    }

    func didReceiveResponse(response:QNearbyPlacesResponse?, error : Error?) -> Void {
        if let error = error {
            let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
            let actionDismiss = UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)
            let actionRetry = UIAlertAction(title: "Retry", style: .default, handler: { (action) in
                DispatchQueue.main.async {
                    self.loadPlaces(true)
                }
            })
            alertController.addAction(actionRetry)
            alertController.addAction(actionDismiss)
            DispatchQueue.main.async {
                self.present(viewController: alertController)

            }
        }
        if let response = response {
            self.response = response
            if response.status == "OK" {
                if let placesDownloaded = response.places {
                    places.append(contentsOf: placesDownloaded)
                }

                self.tableView?.reloadData()
            } else {
                let alert = UIAlertController.init(title: "Error", message: response.status, preferredStyle: .alert)
                alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
                alert.addAction(UIAlertAction.init(title: "Retry", style: .default, handler: { (action) in
                    DispatchQueue.main.async {
                        self.loadPlaces(true)
                    }
                }))
                 self.present(viewController: alert)
            }
            isLoading = false
        }
        else {
            print("response is nil")
        }
    }
    func insertRowsMode2() {

        tableView.beginUpdates()
        for i in 0..<places.count {
            insertRowMode2(ind: i, usr: places[i])
        }
        tableView.endUpdates()
    }

    func insertRowMode2(ind:Int,usr:QPlace) {
        tableView.beginUpdates()
        let indPath = IndexPath(row: ind, section: 0)

        rows = ind + 1
      tableView.insertRows(at: [indPath], with: .right)
       tableView.endUpdates()
    }

    func insertRowsMode3() {
        tableView.beginUpdates()
        rows = 0

        insertRowMode3(ind: 0)
        tableView.endUpdates()
    }
    func insertRowMode3(ind:Int) {
        tableView.beginUpdates()
        let indPath = IndexPath(row: ind, section: 0)
        rows = ind + 1
        tableView.insertRows(at: [indPath], with: .right)

        guard ind < places.count-1 else { return }
        DispatchQueue.main.asyncAfter(deadline: .now()+0.20) {

            self.insertRowMode3(ind: ind+1)
        }
        tableView.endUpdates()
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return  places.count    /*  rows   */
    }
    public  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell

        let place = places[indexPath.row]
        cell.update(place: place)

        if indexPath.row == places.count - 1 {
            loadPlaces(false)
        }

        return (cell)
    }

     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        tableView.deselectRow(at: indexPath, animated: true)

        UIView.animate(withDuration: 0.2, animations: {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
    })

        performSegue(withIdentifier: "goToLast" , sender: indexPath.row)
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

        if editingStyle == UITableViewCellEditingStyle.delete {

            places.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)

        }
    }
    func didReceiveUserLocation(_ userLocation:CLLocation) {
        currentLocation = userLocation.coordinate

        loadPlaces(true)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "goToLast" && sender is IndexPath {

            let dvc = segue.destination as! FinalClass
            dvc.index = (sender as! IndexPath).row
            dvc.places = places
            dvc.userLocation = currentLocation

            }
        }

    @IBAction func IndTapped(_ sender: Any) {
    dismiss(animated: true, completion: nil)

    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

【问题讨论】:

  • 好吧,您将 ruseIdentifier 定义为“MyTableViewCell”,而在您的 cellForRowAt 中定义为“cell”,也许有您的问题?您是否正确设置了 DataSource?
  • @DennisWeidmann "cell" 是表格视图单元格的标识符,"MyTableViewCell" 是表格视图单元格的自定义类
  • 如果您向某些表视图数据源方法添加断点,它们会受到影响吗? tableView 连接了吗?如果您使用 Xcode 9 构建它,是否在方案中打开了主线程检查器选项?如果主线程检查器已打开,您是否会收到有关从主线程访问 UI 的任何控制台消息?
  • @theMikeSwan 是的 tableView 已连接,我没有从主线程检查器收到任何控制台消息,你能建议我在哪里尝试添加断点吗?
  • 我会添加断点,这些断点在评估后自动继续到 numberOfSectionsnumberOfRowsInSectioncellForRowAt,所有这些都带有一个以方法名称记录消息的操作。这样你就知道他们是否被叫了。如果它们是,那么看看这些方法中的最后两个返回的是什么(当该方法由于某种原因被调用或单元格为空时,places.count 可能是 0

标签: ios swift xcode uitableview swift3


【解决方案1】:

您是否将 CourseClass2 类设置为 tableView 的委托/数据源?

尝试添加

tableView.dataSource = self
tableView.delegate = self

到您的 viewDidLoad 方法。

几乎每次我遇到像你这样的问题都是因为我忘记设置委托或数据源。

编辑:我之前只在我的答案中添加了代表,感谢丹尼斯提醒我数据源。

【讨论】:

  • 我猜你的意思是 tableView.dataSource 不是 tableView.delegate 对吧?
【解决方案2】:

我看到您正在进行异步更新,但同步的 reloadData...

您必须在 asyncAfter 中进行更新,或者在您的 insertRowMode3 中添加一个闭包并在该闭包中重新加载数据...

你能试试这个吗?

func insertRowMode3(ind:Int) {
    DispatchQueue.main.asyncAfter(deadline: .now()+0.20) {
        tableView.reloadData()
    }
}

【讨论】:

  • 没用,奇怪的是连动画都没有启动,理论上加载页面时tableView的单元格应该一个接一个出现,具体取决于结果的数量和相反,我发现 tableView 的单元格都已经是额外的并且是空的
  • 您也已经在 viewDidLoad 中添加了上面回答的数据源吗?在 reloadingData 之前你的数组中有数据吗?您能否在更新前检查您的阵列数量?
  • 是的,我可以尝试添加一些断点?你建议我在哪里添加断点?
猜你喜欢
  • 1970-01-01
  • 2020-12-11
  • 1970-01-01
  • 1970-01-01
  • 2016-12-12
  • 1970-01-01
  • 2015-10-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多