簡體   English   中英

使用Coredata學習Swift:為什么我沒有UITableViewCells?

[英]Learning Swift with Coredata: why don't I get any UITableViewCells?

我將我最近的Swift自我學習項目從使用NSMutableArray過渡到了使用CoreData。

我希望我的程序執行的是:

  • 連接到受登錄/密碼保護的頁面,並下載最新100張圖片的列表及其時間戳
  • 為該列表中的每個圖片創建一個NSManagedObject(PicInfo)(如果不存在包含相同圖片網址的對象-尚未實現,我想它與NSPredicate有關)。
  • 在UITableView中顯示按時間戳記降序排列的每個對象

盡管我以前在最初的非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}>

我認為背景獲取有些奇怪:它以系統的“找不到對象”消息開始,而漫畫哈希湯在崩潰時跌倒了。

創建新的托管對象時,我做錯什么了嗎?

您沒有實現正確的表視圖數據源方法。 因為您已將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. 將網絡攝像頭圖片存儲到臨時目錄而不是coredata。 之所以這樣做,是因為在NSManagedObject放下自己的圖片之前,我不知道如何推遲MOC保存操作。
  2. 我還意識到我不是在處理我的PicInfo對象,而是在處理NSManaged對象。 然后,我編寫了一些橋接過程,以便從PI對象獲取MO,反之亦然。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM