我将最近的Swift-self-learning项目从使用NSMutableArray转换为使用CoreData。
我希望我的程序执行的是:
虽然我曾经有一个little issues的初始非CoreData(我不得不触摸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
}
}
PicInfo
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}>
我认为背景提取有些奇怪:它始于系统性的&#34;未找到的对象&#34;当消息崩溃时,消息和漫画哈希汤最终落下。
我在创建新的托管对象时是否做错了什么?
答案 0 :(得分: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
这有点不太可怕。
答案 1 :(得分:0)
我从昨天开始对我的代码做了很多事情,现在它有点起作用了: