Курс #2 : PopularTableViewController : fatal error: Index out of range


#1

Здравствуйте, при обновлении PopularTableViewController столкнулся с ошибкой : Index out of range…
Подскажите как решить,


#2

Добрый вечер!
Распечатайте значения indexPath.row и посмотрите сколько Вы ожидаете. А так да, у Вас что-то с индексами. Раз у Вас выходит ошибка, то Вы получаете индекс, которого нет в массиве. Например, у Вас массив с 3 элементами, а Вы обращаетесь к 6.


#3

Весь код с курса, ничего не добавлял и не исправлял…
Может это зависит от количества добавляемых ресторанов в NewEateryTableViewController и последующей синхронизацией с iCloud?


#4

Попробуйте скачайте проект и запустите его.


#5

Попробую, спасибо Иван.


#6

У меня та же ошибка.
Обнаружил причину ошибки. Если в методе, который загружает данные из iCloud обнулить массив (cloudPlaces = [ ] ) чтобы данные не дублировались - то возникает ошибка. Если этого не делать, то ошибки не будет. Но как же тогда избавиться от дублирования записей?


#7

Я в вашей теме давал ссылку на варианты решения.


#8

А покажите Ваш метод

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return ???????
}

#9

К сожалению я не разобрался как применить их к моему случаю.


#10


#11

Какой тип данных записываться в ваш массив?


#12

var cloudPlaces: [CKRecord] = [ ]


#13

Значит сырые данные из клауда, вы их не извлекаете?

Создаете екстеншн

extension Array {
    func filterDuplicates (includeElement: (_ lhs:Element, _ rhs:Element) -> Bool) -> [Element] {
        var results = [Element]()
        
        forEach { element in
            let existingElements = results.filter {
                return includeElement(element, $0)
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }
        
        return results
    }
}

используете его

    let uniqueElementsArray = cloudPlaces.filterDuplicates { $0.recordID != $1.recordID }

#14

К сожалению не сработало
Printing description of indexPath:
expression produced error: error: /var/folders/wl/wfjhbw6d0dv4xspj2qn5t1xc0000gn/T/./lldb/986/expr7.swift:1:65: error: use of undeclared type 'Foundation’
Swift._DebuggerSupport.stringForPrintObject(Swift.UnsafePointer<Foundation.IndexPath>(bitPattern: 0x102078700)!.pointee)
^~~~~~~~~~

Printing description of indexPath._indexes:
▿ 2 elements

  • 0 : 0
  • 1 : 4
    (lldb)

#15

Я еще новичок, поэтом не на все вопросы могу адекватно ответить. Для лучшего понимания дублирую весь мой код:
import UIKit
import CoreData
import CloudKit

class VeggiePlaceCommunityPlacesTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

var cloudPlaces: [CKRecord] = []
var spinner: UIActivityIndicatorView!
var publicDataBase = CKContainer.default().publicCloudDatabase



@IBAction func close(segue: UIStoryboardSegue) {
    
}


override func viewWillAppear(_ animated: Bool) {
    
    navigationController?.hidesBarsOnSwipe = true
    
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    refreshControl = UIRefreshControl()
    refreshControl?.backgroundColor = .white
    refreshControl?.tintColor = #colorLiteral(red: 0.3586771858, green: 0.8259628539, blue: 0.7560831902, alpha: 1)
    refreshControl?.addTarget(self, action: #selector(getCloudRecords), for: .valueChanged)

    
    
    spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
    spinner.color = #colorLiteral(red: 0.3586771858, green: 0.8259628539, blue: 0.7560831902, alpha: 1)
    spinner.translatesAutoresizingMaskIntoConstraints = false
    spinner.hidesWhenStopped = true
    spinner.startAnimating()
    tableView.addSubview(spinner)
    
    NSLayoutConstraint(item: spinner, attribute: .centerX, relatedBy: .equal, toItem: tableView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: spinner, attribute: .centerY, relatedBy: .equal, toItem: tableView, attribute: .centerY, multiplier: 0.85, constant: 0).isActive = true
    
    
    getCloudRecords()
    
    tableView.estimatedRowHeight = 85
    tableView.rowHeight = UITableViewAutomaticDimension
    
    
    tableView.separatorColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
    self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
    
}

func getCloudRecords() {
    
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "Restaurant", predicate: predicate)
    let sort = NSSortDescriptor(key: "creationDate", ascending: false)
    query.sortDescriptors = [sort]
    
    let queryOperation = CKQueryOperation(query: query)
    queryOperation.desiredKeys = ["name","type","location","phone","image", "webSite"]
    //        queryOperation.resultsLimit = 10
    queryOperation.queuePriority = .veryHigh
    queryOperation.recordFetchedBlock = { (record: CKRecord!) in
        
        if let record = record {
            self.cloudPlaces.append(record)
        }
    }
    
    queryOperation.queryCompletionBlock = { (cursor, error) in
        
        guard error == nil else {
            print("Не удалось получить записи из iCloud: \(String(describing: error?.localizedDescription))")
            return
        }
        print("Записи успешно получены из iCloud")
        DispatchQueue.main.async {
            self.tableView.reloadData()
            self.spinner.stopAnimating()
            self.refreshControl?.endRefreshing()
        }
    }
    
    publicDataBase.add(queryOperation)
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    let userDefaults = UserDefaults.standard
    let wasIntroWatched = userDefaults.bool(forKey: "wasIntroWatched")
    guard !wasIntroWatched else { return }
    
    if let pageViewController = storyboard?.instantiateViewController(withIdentifier: "pageViewController") as? PageViewController {
        present(pageViewController, animated: true, completion: nil)
    }
}

// MARK: - fetch results controller delegate

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.beginUpdates()
}


override func numberOfSections(in tableView: UITableView) -> Int {
    
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    return cloudPlaces.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! VeggiePlaceCommunityPlacesTableViewCell
    
    let cloudPlace = cloudPlaces[indexPath.row]
    
    cell.thumbnailImageView?.image = UIImage(named: "grapes")
    
    
    if let image = cloudPlace.object(forKey: "image") {
        let image = image as! CKAsset
        let data = try? Data(contentsOf: image.fileURL)
        if let data = data {
            cell.thumbnailImageView?.image = UIImage(data: data)
        }
    }
    
    cell.thumbnailImageView.layer.cornerRadius = 32.5
    cell.thumbnailImageView.clipsToBounds = true
    cell.nameLabel.text = cloudPlace.object(forKey: "name") as? String
    if let cloudLocation = cloudPlace.object(forKey: "location") as? String {
        if cloudLocation != "" {
            cell.locationLabel.text = cloudLocation
        } else if let cloudWebSite = cloudPlace.object(forKey: "webSite") as? String {
            if cloudWebSite != "" {
                cell.locationLabel.text = cloudWebSite
            } else { cell.locationLabel.text = "Адрес не указан" }
        }
    }
    cell.typeLabel.text = cloudPlace.object(forKey: "type") as? String
    
    return cell
}


override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    
    let share = UITableViewRowAction(style: .default, title: "Поделиться") { (action, indexPath) in
        
        let cloudPlace = self.cloudPlaces[indexPath.row]
        let shareName = cloudPlace.object(forKey: "name") as! String
        let shareLocation = cloudPlace.object(forKey: "location") as! String
        var shareImage: UIImage!
        
        let defaultText = "Я сейчас в " + shareName + " По адресу: " + shareLocation
        
        if let image = cloudPlace.object(forKey: "image") {
            let image = image as! CKAsset
            let data = try? Data(contentsOf: image.fileURL)
            if let data = data {
                shareImage = UIImage(data: data)
            }
        }
        
        let activityController = UIActivityViewController(activityItems: [shareImage, defaultText], applicationActivities: nil)
        self.present(activityController, animated: true, completion: nil)
    }
    
    share.backgroundColor = #colorLiteral(red: 0.3490196078, green: 0.8, blue: 0.7254901961, alpha: 1)
    
    return[share]
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "detailSegue" {
        if let indexPath = tableView.indexPathForSelectedRow {
            let dvc = segue.destination as! CommunityPlaceDetailViewController
            let cloudPlace = cloudPlaces[indexPath.row]
            dvc.cloudName = cloudPlace.object(forKey: "name") as! String
            if let location = cloudPlace.object(forKey: "location") {
                dvc.cloudLocation = location as! String
                if dvc.cloudLocation == "" {
                    dvc.cloudLocation = "Адрес не указан"
                }
            } else {
                dvc.cloudLocation = "Адрес не указан"
            }
            dvc.cloudType = cloudPlace.object(forKey: "type") as! String
            dvc.cloudPhone = cloudPlace.object(forKey: "phone") as! String
            if let webSite = cloudPlace.object(forKey: "webSite") {
                dvc.cloudWebSite = webSite as! String
                if dvc.cloudWebSite == "" {
                    dvc.cloudWebSite = "Сайт не указан"
                }
            } else {
                dvc.cloudWebSite = "Сайт не указан"
            }
            if let image = cloudPlace.object(forKey: "image") {
                let image = image as! CKAsset
                let data = try? Data(contentsOf: image.fileURL)
                if let data = data {
                    dvc.cloudImage = UIImage(data: data)
                }
            }
        }
    }
}

}


#16

Не нашел в вашем коде фильтрации дупликатов.
И вы не всю ошибку скопировали.


#17

Я удалил уже, сейчас вставлю и продублирую


#18

import UIKit
import CoreData
import CloudKit

class VeggiePlaceCommunityPlacesTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

var cloudPlaces: [CKRecord] = []
var spinner: UIActivityIndicatorView!
var publicDataBase = CKContainer.default().publicCloudDatabase



@IBAction func close(segue: UIStoryboardSegue) {
    
}


override func viewWillAppear(_ animated: Bool) {
    
    navigationController?.hidesBarsOnSwipe = true
    
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    refreshControl = UIRefreshControl()
    refreshControl?.backgroundColor = .white
    refreshControl?.tintColor = #colorLiteral(red: 0.3586771858, green: 0.8259628539, blue: 0.7560831902, alpha: 1)
    refreshControl?.addTarget(self, action: #selector(getCloudRecords), for: .valueChanged)

    
    
    spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
    spinner.color = #colorLiteral(red: 0.3586771858, green: 0.8259628539, blue: 0.7560831902, alpha: 1)
    spinner.translatesAutoresizingMaskIntoConstraints = false
    spinner.hidesWhenStopped = true
    spinner.startAnimating()
    tableView.addSubview(spinner)
    
    NSLayoutConstraint(item: spinner, attribute: .centerX, relatedBy: .equal, toItem: tableView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: spinner, attribute: .centerY, relatedBy: .equal, toItem: tableView, attribute: .centerY, multiplier: 0.85, constant: 0).isActive = true
    
    
    getCloudRecords()
    
    tableView.estimatedRowHeight = 85
    tableView.rowHeight = UITableViewAutomaticDimension
    
    
    tableView.separatorColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
    self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
    
}

func getCloudRecords() {
    
    let uniqueElementsArray = cloudPlaces.filterDuplicates { $0.recordID != $1.recordID }
    cloudPlaces = uniqueElementsArray
    
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "Restaurant", predicate: predicate)
    let sort = NSSortDescriptor(key: "creationDate", ascending: false)
    query.sortDescriptors = [sort]
    
    let queryOperation = CKQueryOperation(query: query)
    queryOperation.desiredKeys = ["name","type","location","phone","image", "webSite"]
    //        queryOperation.resultsLimit = 10
    queryOperation.queuePriority = .veryHigh
    queryOperation.recordFetchedBlock = { (record: CKRecord!) in
        
        if let record = record {
            self.cloudPlaces.append(record)
        }
    }
    
    queryOperation.queryCompletionBlock = { (cursor, error) in
        
        guard error == nil else {
            print("Не удалось получить записи из iCloud: \(String(describing: error?.localizedDescription))")
            return
        }
        print("Записи успешно получены из iCloud")
        DispatchQueue.main.async {
            self.tableView.reloadData()
            self.spinner.stopAnimating()
            self.refreshControl?.endRefreshing()
        }
    }
    
    publicDataBase.add(queryOperation)
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    let userDefaults = UserDefaults.standard
    let wasIntroWatched = userDefaults.bool(forKey: "wasIntroWatched")
    guard !wasIntroWatched else { return }
    
    if let pageViewController = storyboard?.instantiateViewController(withIdentifier: "pageViewController") as? PageViewController {
        present(pageViewController, animated: true, completion: nil)
    }
}

// MARK: - fetch results controller delegate

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.beginUpdates()
}


override func numberOfSections(in tableView: UITableView) -> Int {
    
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    return cloudPlaces.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! VeggiePlaceCommunityPlacesTableViewCell
    
    let cloudPlace = cloudPlaces[indexPath.row]
    
    cell.thumbnailImageView?.image = UIImage(named: "grapes")
    
    
    if let image = cloudPlace.object(forKey: "image") {
        let image = image as! CKAsset
        let data = try? Data(contentsOf: image.fileURL)
        if let data = data {
            cell.thumbnailImageView?.image = UIImage(data: data)
        }
    }
    
    cell.thumbnailImageView.layer.cornerRadius = 32.5
    cell.thumbnailImageView.clipsToBounds = true
    cell.nameLabel.text = cloudPlace.object(forKey: "name") as? String
    if let cloudLocation = cloudPlace.object(forKey: "location") as? String {
        if cloudLocation != "" {
            cell.locationLabel.text = cloudLocation
        } else if let cloudWebSite = cloudPlace.object(forKey: "webSite") as? String {
            if cloudWebSite != "" {
                cell.locationLabel.text = cloudWebSite
            } else { cell.locationLabel.text = "Адрес не указан" }
        }
    }
    cell.typeLabel.text = cloudPlace.object(forKey: "type") as? String
    
    return cell
}


override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    
    let share = UITableViewRowAction(style: .default, title: "Поделиться") { (action, indexPath) in
        
        let cloudPlace = self.cloudPlaces[indexPath.row]
        let shareName = cloudPlace.object(forKey: "name") as! String
        let shareLocation = cloudPlace.object(forKey: "location") as! String
        var shareImage: UIImage!
        
        let defaultText = "Я сейчас в " + shareName + " По адресу: " + shareLocation
        
        if let image = cloudPlace.object(forKey: "image") {
            let image = image as! CKAsset
            let data = try? Data(contentsOf: image.fileURL)
            if let data = data {
                shareImage = UIImage(data: data)
            }
        }
        
        let activityController = UIActivityViewController(activityItems: [shareImage, defaultText], applicationActivities: nil)
        self.present(activityController, animated: true, completion: nil)
    }
    
    share.backgroundColor = #colorLiteral(red: 0.3490196078, green: 0.8, blue: 0.7254901961, alpha: 1)
    
    return[share]
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "detailSegue" {
        if let indexPath = tableView.indexPathForSelectedRow {
            let dvc = segue.destination as! CommunityPlaceDetailViewController
            let cloudPlace = cloudPlaces[indexPath.row]
            dvc.cloudName = cloudPlace.object(forKey: "name") as! String
            if let location = cloudPlace.object(forKey: "location") {
                dvc.cloudLocation = location as! String
                if dvc.cloudLocation == "" {
                    dvc.cloudLocation = "Адрес не указан"
                }
            } else {
                dvc.cloudLocation = "Адрес не указан"
            }
            dvc.cloudType = cloudPlace.object(forKey: "type") as! String
            dvc.cloudPhone = cloudPlace.object(forKey: "phone") as! String
            if let webSite = cloudPlace.object(forKey: "webSite") {
                dvc.cloudWebSite = webSite as! String
                if dvc.cloudWebSite == "" {
                    dvc.cloudWebSite = "Сайт не указан"
                }
            } else {
                dvc.cloudWebSite = "Сайт не указан"
            }
            if let image = cloudPlace.object(forKey: "image") {
                let image = image as! CKAsset
                let data = try? Data(contentsOf: image.fileURL)
                if let data = data {
                    dvc.cloudImage = UIImage(data: data)
                }
            }
        }
    }
}

}

extension Array {
func filterDuplicates (includeElement: (_ lhs:Element, _ rhs:Element) -> Bool) -> [Element] {
var results = Element

    forEach { element in
        let existingElements = results.filter {
            return includeElement(element, $0)
        }
        if existingElements.count == 0 {
            results.append(element)
        }
    }
    
    return results
}

}


#19

Это нужно делать после того, как вы получили данные

let uniqueElementsArray = cloudPlaces.filterDuplicates { $0.recordID != $1.recordID }
cloudPlaces = uniqueElementsArray

И можно заменить на

cloudPlaces = cloudPlaces.filterDuplicates { $0.recordID != $1.recordID }

#20

если пишу в конце метода, то сразу выдает ошибку