UITableView 刷新问题与 NSURLConnection-asynchronously 加载 NSMutableArray
UITableView refresh problems with an NSURLConnection-asynchronously laden NSMutableArray
我目前正在接近 Swift(我已经在 Objective-C 中编码多年)并且我正在尝试为我的视频监控系统开发一个配套应用程序。
我的应用程序做的是:
- 连接到受密码保护的网站并获取由现有的三个网络摄像机之一上传的最后快照的列表。
- 对于这些镜头中的每一个,我都创建了一个带有临时图片的 PicInfo 对象。然后我尝试异步下载图片并将它们存储在 UIImage 属性.
中
- 从那里开始,我的 MasterViewController 中有原型单元格,其中包含一些细节。触摸一个将使我在 DetailViewController 中看到更大的视图。
我还实现了刷新,我将重新加载完整列表并替换最后 100 个 table 单元格和 NSMutable 数组的内容。
当前问题:
- 初始加载后,Master Table View 显示为空,直到我轻轻触摸它。
- 虽然 NSLog^Wprintln 语句显示列表已成功重新加载,但 table 不会刷新,它仍会显示等待的小部件...
- 我在将 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 以获得一些灵活性。
我目前正在接近 Swift(我已经在 Objective-C 中编码多年)并且我正在尝试为我的视频监控系统开发一个配套应用程序。
我的应用程序做的是:
- 连接到受密码保护的网站并获取由现有的三个网络摄像机之一上传的最后快照的列表。
- 对于这些镜头中的每一个,我都创建了一个带有临时图片的 PicInfo 对象。然后我尝试异步下载图片并将它们存储在 UIImage 属性. 中
- 从那里开始,我的 MasterViewController 中有原型单元格,其中包含一些细节。触摸一个将使我在 DetailViewController 中看到更大的视图。
我还实现了刷新,我将重新加载完整列表并替换最后 100 个 table 单元格和 NSMutable 数组的内容。
当前问题:
- 初始加载后,Master Table View 显示为空,直到我轻轻触摸它。
- 虽然 NSLog^Wprintln 语句显示列表已成功重新加载,但 table 不会刷新,它仍会显示等待的小部件...
- 我在将 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 以获得一些灵活性。