UITableView 刷新问题与 NSURLConnection-asynchronously 加载 NSMutableArray

UITableView refresh problems with an NSURLConnection-asynchronously laden NSMutableArray

我目前正在接近 Swift(我已经在 Objective-C 中编码多年)并且我正在尝试为我的视频监控系统开发一个配套应用程序。

我的应用程序做的是:

  1. 连接到受密码保护的网站并获取由现有的三个网络摄像机之一上传的最后快照的列表。
  2. 对于这些镜头中的每一个,我都创建了一个带有临时图片的 PicInfo 对象。然后我尝试异步下载图片并将它们存储在 UIImage 属性.
  3. 从那里开始,我的 MasterViewController 中有原型单元格,其中包含一些细节。触摸一个将使我在 DetailViewController 中看到更大的视图。

我还实现了刷新,我将重新加载完整列表并替换最后 100 个 table 单元格和 NSMutable 数组的内容。

当前问题:

  1. 初始加载后,Master Table View 显示为空,直到我轻轻触摸它。
  2. 虽然 NSLog^Wprintln 语句显示列表已成功重新加载,但 table 不会刷新,它仍会显示等待的小部件...
  3. 我在将 NSData 放入我的 UIImages 时遇到一些不美观的 "bad packing" 错误(或者我认为:消息没有更明确),否则显示正常。

我做错了什么?

PicInfo.swift

import Foundation
import UIKit

class PicInfo {
    var index: Int = 0
    var description: 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("found: \(picInfos[0]): [\(picInfos[2])] taken at \(picInfos[1])")
        self.index = picInfos[0].toInt()!
        self.url = baseUrlString + "/" + picInfos[2]
        self.stamp = picInfos[1]
        description = ""
        if (picInfos[2].rangeOfString("HALLCAM") != nil) {
            description="Hall"
        } else if (picInfos[2].rangeOfString("STAIRSCAM") != nil) {
            description="Garden"
        } else {
            description="Cats"
        }
        self.image = UIImage(named: "nothingyet")
        self.loadImage()
    }

    func URL() -> NSURL {
        var URL=NSURL(string: self.url)
        return URL!
    }

    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: data)
                    self.downloaded = true
                }
            }
            else {
                println("Error: \(error.localizedDescription)")
            }
            self.downloading = false
        })
    }
}

MasterViewController.swift

import UIKit

let baseUrlString = "http://my.domain.net"
let username = "OoOoOoOoO"
let password = "xXxXxXxXx"

class MasterViewController: UITableViewController,NSURLConnectionDataDelegate{

    var detailViewController: DetailViewController!
    var objects = NSMutableArray()
    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()
        if let split = self.splitViewController {
            let controllers = split.viewControllers
            self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
            self.detailViewController.picInfos = self.objects;
        }
        self.refreshControl?.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
        self.loadPictureList()
    }

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

    func insertNewObject(data: String) {
        var picInfo=PicInfo(data: data)
        objects.insertObject(picInfo, atIndex: picInfo.index)
        let indexPath = NSIndexPath(forRow: picInfo.index, inSection: 0)
        self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
    }

    // MARK: - Segues

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "showDetail" {
            if let indexPath = self.tableView.indexPathForSelectedRow() {
                let object = objects[indexPath.row] as PicInfo
                println("following segue: \(indexPath.row) => \(object.index)")
                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 numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

        if let object = objects[indexPath.row] as? PicInfo {
            var todo=""
            if (object.downloading || !object.downloaded) {
                todo="(*)"
            }
            cell.textLabel!.text = "\(object.index): \(object.description)\(todo)"
            cell.detailTextLabel?.text = object.stamp
            if (!object.downloaded) {
                object.loadImage()
            }
            cell.imageView?.image = object.image
        }
        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
    }

    // MARK: - Refresh
    func refresh(sender:AnyObject)
    {
        // Updating your data here...
        self.loadPictureList()
    }

    // 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")
            self.tableView.beginUpdates()
            for o in objects {
                let p = o as PicInfo
                var i=NSIndexPath(forRow: p.index, inSection: 0)
                self.tableView.deleteRowsAtIndexPaths([i], withRowAnimation: UITableViewRowAnimation.Fade)
                objects.removeObject(o)

            }
            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(picInfo)
                }
            }
            self.loading = false
            self.tableView.endUpdates()
            self.tableView.reloadData()
            println("Refresh is over")
        }
        return
    }
}

DetailViewController.swift

import UIKit

class DetailViewController: UIViewController {

    @IBOutlet weak var detailDescriptionLabel: UILabel!
    @IBOutlet weak var pictureView: UIImageView!
    @IBOutlet weak var progressView: UIProgressView!

    var picInfos: NSMutableArray!

    var detailItem: AnyObject? {
        didSet {
            // Update the view.
            if let detail = detailItem as? PicInfo {
            println("Preparing to display object#\(detail.index)")
            } else {
                println("Bizarre?!")
            }
            self.configureView()
        }
    }

    func configureView() {
        // Update the user interface for the detail item.
        if let detail = detailItem as? PicInfo {
            if let label = self.detailDescriptionLabel {
                var todo=""
                if (detail.downloading || !detail.downloaded) {
                    todo="(*)"
                }
                label.text = "\(detail.index): \(detail.description)\(todo)"
            }
            self.title = detail.stamp
            if let proVi = self.progressView {
                if let pi = self.picInfos {
                var total = pi.count
                    if ( total==0 ) {
                        total=1
                    }
                    proVi.setProgress( Float(Double(detail.index) / Double(total)), animated: true)
                }
            }
            if let img = self.pictureView {
                if (!detail.downloaded) {
                    detail.loadImage()
                }
                img.image = detail.image
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.configureView()
    }

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

在此先感谢您的帮助。

我切换到 CoreData 以获得一些灵活性。