使用 Coredata 学习 Swift:为啥我没有得到任何 UITableViewCells?
Posted
技术标签:
【中文标题】使用 Coredata 学习 Swift:为啥我没有得到任何 UITableViewCells?【英文标题】:Learning Swift with Coredata: why don't I get any UITableViewCells?使用 Coredata 学习 Swift:为什么我没有得到任何 UITableViewCells? 【发布时间】:2015-02-25 07:44:07 【问题描述】:我将我最近的 Swift 自学项目从使用 NSMutableArray 转换为使用 CoreData。
我希望我的程序执行的是:
连接到受登录名/密码保护的页面并下载最近 100 张图片及其时间戳的列表 对于此列表中的每张图片,创建一个 NSManagedObject (PicInfo)(如果不存在包含相同图片 url 的现有对象 - 尚未实现,我猜它与 NSPredicate 有关)。 在 UITableView 中按时间戳降序显示这些对象中的每一个虽然我曾经有一个带有初始非 CoreData 的 little issues(我不得不触摸 UITableView 让它显示任何内容并且刷新挂起应用程序,但 CoreData 变得更糟:UITableView 仍然是空的,尽管我知道我在存储对象:
Got data!
Refresh will start
MVC::insertNewObject beginning
PicInfo init: found 0: [viewz/CAM_HALL_1_20150225074135_4324.jpg] taken at 2015/02/25 07:41:35
MVC::insertNewObject about to insert Object 0 - http://my.webcam.site.perso/CAM_HALL_1_20150225074135_4324.jpg
2015-02-25 07:41:55.497 spEye[21867:2544527] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.16.14/UITableView.m:1377
2015-02-25 07:41:55.498 spEye[21867:2544527] CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update with userInfo (null)
MVC::insertNewObject successfully inserted Object 0 - http://my.webcam.site.perso/CAM_HALL_1_20150225074135_4324.jpg
MVC::insertNewObject beginning
PicInfo init: found 1: [viewz/CAM_HALL_1_20150225074129_4323.jpg] taken at 2015/02/25 07:41:30
MVC::insertNewObject about to insert Object 1 - http://my.webcam.site.perso/CAM_HALL_1_20150225074129_4323.jpg
MVC::insertNewObject successfully inserted Object 1 - http://my.webcam.site.perso/CAM_HALL_1_20150225074129_4323.jpg
MVC::insertNewObject beginning
PicInfo init: found 2: [viewz/CAM_HALL_1_20150225074124_4322.jpg] taken at 2015/02/25 07:41:24
MVC::insertNewObject about to insert Object 2 - http://my.webcam.site.perso/CAM_HALL_1_20150225074124_4322.jpg
MVC::insertNewObject successfully inserted Object 2 - http://my.webcam.site.perso/CAM_HALL_1_20150225074124_4322.jpg
...
Refresh completed
所以我真的被困住了,害怕失去耐心并回到 Objective-C。有人会好心看看我的代码并告诉我我做错了什么吗?
代码如下:
MasterViewController
import UIKit
import CoreData
let baseUrlString = "http://my.webcam.site.perso"
let username = "mylogin"
let password = "v3rys3cr3tp4s$"
class MasterViewController: UITableViewController,NSFetchedResultsControllerDelegate,NSURLConnectionDataDelegate
var detailViewController: DetailViewController? = nil
var managedObjectContext: NSManagedObjectContext? = nil
var loading: Bool = false;
override func awakeFromNib()
super.awakeFromNib()
if UIDevice.currentDevice().userInterfaceIdiom == .Pad
self.clearsSelectionOnViewWillAppear = false
self.preferredContentSize = CGSize(width: 320.0, height: 600.0)
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let split = self.splitViewController
let controllers = split.viewControllers
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
self.refreshControl?.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.refresh(self)
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
func insertNewObject(sender: AnyObject, details: NSString)
println("MVC::insertNewObject beginning")
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
var picInfo=PicInfo(data: details)
println("MVC::insertNewObject about to insert Object \(picInfo.index) - \(picInfo.url)")
let newManagedObject = picInfo.managedObject(entity, context:context)
// Save the context.
var error: NSError? = nil
if !context.save(&error)
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
println("MVC::insertNewObject *could not* insert Object \(picInfo.index) - \(picInfo.url)")
abort()
println("MVC::insertNewObject successfully inserted Object \(picInfo.index) - \(picInfo.url)")
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
if segue.identifier == "showDetail"
if let indexPath = self.tableView.indexPathForSelectedRow()
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
let controller = (segue.destinationViewController as UINavigationController).topViewController as DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
// MARK: - Table View
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return (self.fetchedResultsController.sections?[section] as? NSFetchedResultsSectionInfo)?.numberOfObjects ?? 0
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
self.configureCell(cell, atIndexPath: indexPath)
return cell
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool
// Return false if you do not want the specified item to be editable.
return false
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath)
if let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as? PicInfo
println("configureCell: \(object.index)")
var todo=""
if (object.downloading || !object.downloaded)
todo="(*)"
cell.textLabel!.text = "\(object.index): \(object.desc)\(todo)"
cell.detailTextLabel?.text = object.stamp
if (!object.downloaded)
object.loadImage()
if var img = object.image
cell.imageView?.image = img
else
println("configureCell: object#\(indexPath.row) not found !?")
// MARK: - Fetched results controller
var fetchedResultsController: NSFetchedResultsController
if _fetchedResultsController != nil
return _fetchedResultsController!
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("PicInfo", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 100
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "stamp", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error)
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
println("Unresolved error \(error), \(error?.description)")
abort()
return _fetchedResultsController!
var _fetchedResultsController: NSFetchedResultsController? = nil
func controllerWillChangeContent(controller: NSFetchedResultsController)
self.tableView.beginUpdates()
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
switch type
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
if let thecell=tableView.cellForRowAtIndexPath(indexPath!)
println("MVC::didChangeObject: cell exists at \(indexPath?.row)")
self.configureCell(thecell, atIndexPath: indexPath!)
else
println("MVC::didChangeObject: nil cell at \(indexPath?.row)\n\(tableView.description)")
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
default:
return
func controllerDidChangeContent(controller: NSFetchedResultsController)
self.tableView.endUpdates()
/*
// Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed.
func controllerDidChangeContent(controller: NSFetchedResultsController)
// In the simplest, most efficient, case, reload the table view.
self.tableView.reloadData()
*/
// MARK: - Refresh
func refresh(sender:AnyObject)
self.tableView.beginUpdates()
self.loadPictureList()
self.tableView.endUpdates()
// MARK: Internet stuff
func loadPictureList ()
// set up the base64-encoded credentials
if (self.loading)
return
self.loading = true
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let loginString = NSString(format: "%@:%@", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(nil)
let authString = "Basic \(base64LoginString)"
config.HTTPAdditionalHeaders = ["Authorization" : authString]
let session = NSURLSession(configuration: config)
// create the request
let url = NSURL(string: baseUrlString + "/latest.php")
let request = NSMutableURLRequest(URL: url!)
// fire off the request
var task = session.dataTaskWithURL(url!)
(data, response, error) -> Void in
if error != nil
self.handlePictureList("", encounteredProblem:"\(error.localizedDescription)\nurl:\(url)")
else
var result = NSString(data: data, encoding: NSASCIIStringEncoding)!
self.handlePictureList(result, encounteredProblem:"")
task.resume()
func handlePictureList (data: NSString, encounteredProblem error: NSString)
if (error.length>0)
println ("Had error: [\(error)]")
else
println ("Got data!")
println("Refresh will start")
var pixArr = data.componentsSeparatedByString("\n")
for unparsedPicInfo in pixArr
if (unparsedPicInfo.hasPrefix("<tr>"))
var picInfo=unparsedPicInfo.stringByReplacingOccurrencesOfString("<tr><td>", withString: "")
picInfo=picInfo.stringByReplacingOccurrencesOfString("</td></tr>", withString: "")
picInfo=picInfo.stringByReplacingOccurrencesOfString("</td><td>", withString: "\t")
self.insertNewObject(self, details:picInfo)
self.loading = false
println("Refresh is over")
return
图片信息
import Foundation
import UIKit
import CoreData
class PicInfo
var index: Int = 0
var desc: String = ""
var stamp: String = ""
var url: String = ""
var image: UIImage!
var downloaded: Bool = false;
var downloading: Bool = false;
init(data: String)
var picInfos=data.componentsSeparatedByString("\t")
println("PicInfo init: found \(picInfos[0]): [\(picInfos[2])] taken at \(picInfos[1])")
self.index = picInfos[0].toInt()!
self.url = baseUrlString + "/" + picInfos[2]
self.stamp = picInfos[1]
desc = ""
if (picInfos[2].rangeOfString("CAM_HALL") != nil)
desc="Hall"
else if (picInfos[2].rangeOfString("CAM_STAIRS") != nil)
desc="Stairs"
else
desc="Cats"
self.image = UIImage(named: "nothingtosee")
self.loadImage()
func URL() -> NSURL
var URL=NSURL(string: self.url)
return URL!
func managedObject(entity: NSEntityDescription, context: NSManagedObjectContext) -> NSManagedObject
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as NSManagedObject
newManagedObject.setValue(index, forKey: "index")
newManagedObject.setValue(desc, forKey: "desc")
newManagedObject.setValue(stamp, forKey: "stamp")
newManagedObject.setValue(url, forKey: "url")
newManagedObject.setValue(UIImageJPEGRepresentation(image,80), forKey: "image")
newManagedObject.setValue(downloaded, forKey: "downloaded")
newManagedObject.setValue(downloading, forKey: "downloading")
return newManagedObject;
func loadImage()
if (self.downloading)
return
let url = self.URL()
let request = NSMutableURLRequest(URL: url)
let loginString = NSString(format: "%@:%@", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(nil)
// create the request
request.HTTPMethod = "POST"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
self.downloading = true
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: (response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil
if let blob = data
self.image = UIImage(data: blob)
self.downloaded = true
else
println("Error: \(error.localizedDescription)")
self.downloading = false
)
编辑 1 添加 numberOfRowsInSection 方法。
现在 UITableView 显示记录(默认值),但在我可以交互之前崩溃(并清空 TableView)。
日志给出:
Got data!
Refresh will start
MVC::insertNewObject beginning
PicInfo init: found 0: [viewz/00D6FB01ABB1()_1_20150225175206_7382.jpg] taken at 2015/02/25 17:52:05
MVC::insertNewObject about to insert Object 0 - http://my.site.perso/00D6FB01ABB1()_1_20150225175206_7382.jpg
configureCell: object#0 not found !?
MVC::insertNewObject successfully inserted Object 0 - http://my.site.perso/00D6FB01ABB1()_1_20150225175206_7382.jpg
MVC::insertNewObject beginning
PicInfo init: found 1: [viewz/00D6FB01ABB1()_1_20150225175200_7381.jpg] taken at 2015/02/25 17:52:00
MVC::insertNewObject about to insert Object 1 - http://my.site.perso/00D6FB01ABB1()_1_20150225175200_7381.jpg
configureCell: object#1 not found !?
MVC::insertNewObject successfully inserted Object 1 - http://my.site.perso/00D6FB01ABB1()_1_20150225175200_7381.jpg
MVC::insertNewObject beginning
PicInfo init: found 2: [viewz/00D6FB01ABB1()_1_20150225175155_7380.jpg] taken at 2015/02/25 17:51:55
....snip....
MVC::insertNewObject beginning
PicInfo init: found 11: [viewz/00D6FB01ABB1()_1_20150225175109_7371.jpg] taken at 2015/02/25 17:51:08
MVC::insertNewObject about to insert Object 11 - http://my.site.perso/00D6FB01ABB1()_1_20150225175109_7371.jpg
configureCell: object#11 not found !?
MVC::insertNewObject successfully inserted Object 11 - http://my.site.perso/00D6FB01ABB1()_1_20150225175109_7371.jpg
MVC::insertNewObject beginning
PicInfo bad packing
init: found 12: [viewz/00626E423E6A()_1_20150225175018_4683.jpg] taken at 2015/02/25 17:50:19
bad packing
bad packing
MVC::insertNewObject about to insert Object 12 - http://my.site.perso/00626E423E6A()_1_20150225175018_4683.jpg
bad packing
MVC::didChangeObject: nil cell at Optional(12)
<UITableView: 0x7fea05030000; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fea0333c180>; layer = <CALayer: 0x7fea03337d90>; contentOffset: 0, -64; contentSize: 320, 572>
MVC::insertNewObject successfully inserted Object 12 - http://my.site.perso/00626E423E6A()_1_20150225175018_4683.jpg
MVC::insertNewObject beginning
PicInfo init: found 13: [viewz/00626E423E6A()_1_20150225175007_4682.jpg] taken at 2015/02/25 17:50:18
MVC::insertNewObject about to insert Object 13 - http://my.site.perso/00626E423E6A()_1_20150225175007_4682.jpg
MVC::insertNewObject successfully inserted Object 13 - http://my.site.perso/00626E423E6A()_1_20150225175007_4bad packing
682.jpg
MVC::insertNewObject beginning
PicInfo init: found 14: [viewz/00626E423E6A()_1_20150225175002_4681.jpg] taken at 2015/02/25 17:50:03
MVC::insertNewObject about to insert Object 14 - http://my.site.perso/00626E423E6A()_1_20150225175002_4681.jpg
MVC::insertNewObject successfully inserted Object 14 - http://my.site.perso/00626E423E6A()_1_20150225175002_4681.jpg
MVC::insertNewObject beginning
PicInfo init: found 15: [viewz/00626E423E6A()_1_20150225174957_4680.jpg] taken at 2015/02/25 17:49:58
MVC::insertNewObject about to insert Object 15 - http://my.site.perso/00626E423E6A()_1_20150225174957_4680.jpg
MVC::insertNewObject successfully inserted Object 15 - http://my.site.perso/00626E423E6A()_1_20150225174957_4680.jpg
MVC::insertNewObject beginning
PicInfo init: found 16: [viewz/00626E423E6A()_1_20150225174952_4679.jpg] taken at 2015/02/25 17:49:52
MVC::insertNewObject about to insert Object 16 - http://my.site.perso/00626E423E6A()_1_20150225174952_4679.jpg
bad packing
MVC::insertNewObject successfully inserted Object 16 - http://my.site.perso/00626E423E6A()_1_20150225174952_4679.jpg
MVC::insertNewObject beginning
bad packing
PicInfo init: found 17: [viewz/00626E423E6A()_1_20150225174946_4678.jpbad packing
g] taken at 2015/02/25 17:49:47
bad packing
bad packing
bad packing
bad packing
MVC::insertNewObject about to insert Object 17 - http://my.site.perso/00626E423E6A()_1_20150225174946_4678.jpg
MVC::didChangeObject: nil cell at Optional(17)
<UITableView: 0x7fea05030000; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fea0333c180>; layer = <CALayer: 0x7fea03337d90>; contentOffset: 0, -64; contentSize: 320, 572>
MVC::didChangeObject: nil cell at Optional(17)
<UITableView: 0x7fea05030000; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fea0333c180>; layer = <CALayer: 0x7fea03337d90>; contentOffset: 0, -64; contentSize: 320, 792>
MVC::insertNewObject successfully inserted Object 17 - http://my.site.perso/00626E423E6A()_1_20150225174946_4678.jpg
MVC::insertNewObject beginning
PicInfo init: found 18: [viewz/00626E423E6A()_1_20150225174941_4677.jpg] taken at 2015/02/25 17:49:42
....snip....
MVC::insertNewObject beginning
PicInfo init: found 23: [viewz/00626E423E6A()_1_20150225174920_4673.jpg] taken at 2015/02/25 17:49:21
MVC::insertNewObject about to insert Object 23 - http://my.site.perso/00626E423E6A()_1_20150225174920_4673.jpg
MVC::insertNewObject successfully inserted Object 23 - http://my.site.perso/00626E423E6A()_1_20150225174920_4673.jpg
MVC::insertNewObject beginning
PicInfo init: found 24: [viewz/QOQA_CAM_UPLOADX_2015022517492001.jpg] taken at 2015/02/25 17:49:20
MVC::insertNewObject about to insert Object 24 - http://my.site.perso/QOQA_CAM_UPLOADX_2015022517492001.jpg
MVC::insertNewObject successfully inserted Object 24 - http://my.site.perso/QOQA_CAM_UPLOADX_2015022517492001.jpg
MVC::insertNewObject beginning
PicInfo init: found 25: [viewz/00D6FB01ABB1()_1_20150225174857_7370.jpg] taken at 2015/02/25 17:48:57
MVC::insertNewObject about to insert Object 25 - http://my.site.perso/00D6FB01ABB1()_1_20150225174857_7370.jpg
MVC::didChangeObject: nil cell at Optional(25)
<UITableView: 0x7fea05030000; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fea0333c180>; layer = <CALayer: 0x7fea03337d90>; contentOffset: 0, -64; contentSize: 320, 1012>
我认为后台获取有点奇怪:它以系统的“找不到对象”消息开始,而漫画哈希汤在最后崩溃时掉落。
我在创建新的托管对象时做错了吗?
【问题讨论】:
您的 tableView 数据源方法在哪里?他们应该从您的获取结果控制器返回行数和节数 感谢您的回答。这是个好问题。我记得删除了关于 sectioncount 的那个(我首先修改为返回 1,但它并没有改变任何结果和错误记录)但我认为 xcode 生成的 mvc swift 文件中没有另一个存在。 在 Interface Builder 中,您能否检查您的UITableView
的 delegate
和 dataSource
属性是否已连接到您的 MasterViewController
?
您好,感谢您的回复。是的。
这仍然不是崩溃日志。当这停止时,您应该在控制台中看到某种错误。如果您使用的是后台线程,请注意您不能跨线程使用托管对象,但这是另一个问题
【参考方案1】:
您没有实现正确的表视图数据源方法。因为您已对具有方法的基本实现的 UITableViewController
进行了子类化,所以您不会看到警告,但无论您持有什么数据,您都将返回一个部分和零行。
看起来你只有一个部分,所以你可以留下它,但你需要这个方法:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return return (fetchedResultsController?.sections?[section] as? NSFetchedResultsSectionInfo)?.numberOfObjects ?? 0
这给出了一个部分的行数。如果你知道你只会有一个部分,你可以使用
return fetchedResultsController?.fetchedObjects?.count ?? 0
这不那么可怕了。
【讨论】:
IT 崩溃:我明白了: ;W +lHa;y egr =从昨天开始我对我的代码做了很多事情,现在它有点工作了:
-
将网络摄像头图片存储到临时目录而不是 coredata。我这样做是因为我不知道如何推迟 MOC 保存操作,直到 NSManagedObject 下载了它自己的图片。
我还意识到我不是在处理我的 PicInfo 对象,而是在处理 NSManaged 对象。然后我编写了一些桥接程序,以便从 PI 对象中获取 MO,反之亦然。
【讨论】:
以上是关于使用 Coredata 学习 Swift:为啥我没有得到任何 UITableViewCells?的主要内容,如果未能解决你的问题,请参考以下文章