Ich versuche gleichzeitig, Swift
& die Grundlagen von iOS
dev zu lernen. Ich habe eine TableViewController
, die zunächst eine lokale JSON
-Datei analysiert und sehr einfache Daten in TableViewCell
und SectionHeaderViews rendert. Innerhalb desselben TableViewController
rufe ich einen JSON
-Endpunkt an, der Daten zurückgibt, die ich dann auf Variablen setze, damit ich auf das zugreifen kann, auf das ich eigentlich zugreifen möchte (die API-Struktur ist nicht wünschenswert). Also habe ich endlich die richtigen Daten als self.tableData
eingestellt und dann self.tableView.reloadData()
aufgerufen, aber es passiert nichts. Was gibt?
import UIKit
class BusinessTableViewController: UITableViewController {
var data: NSMutableData = NSMutableData()
var tableData: NSArray = NSArray()
@lazy var Business: NSArray = {
let pathTCT = NSBundle.mainBundle().pathForResource("TCT", ofType: "json")
let data = NSData.dataWithContentsOfFile(pathTCT, options: nil, error: nil)
return NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as NSArray
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = UIImageView(image: UIImage(named: "growler"))
tableView.registerClass(BeerTableViewCell.self, forCellReuseIdentifier: "cell")
tableView.separatorStyle = .None
fetchKimono()
}
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
// return Business.count
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
let biz = Business[section] as NSDictionary
let results = biz["results"] as NSDictionary
let beers = results["collection1"] as NSArray
return beers.count
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as BeerTableViewCell
if let path = indexPath {
let biz = Business[path.section] as NSDictionary
let results = biz["results"] as NSDictionary
let beers = results["collection1"] as NSArray
let beer = beers[path.row] as NSDictionary
cell.titleLabel.text = beer["BeerName"] as String
}
return cell
}
override func tableView(tableView: UITableView!, titleForHeaderInSection section: Int) -> String! {
let biz = Business[section] as NSDictionary
return biz["name"] as String
}
override func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
let biz = Business[section] as NSDictionary
let view = LocationHeaderView()
view.titleLabel.text = (biz["name"] as String).uppercaseString
return view
}
override func tableView(tableView: UITableView!, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func fetchKimono() {
var urlPath = "names have been changed to protect the innocent"
var url: NSURL = NSURL(string: urlPath)
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
connection.start()
}
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
// Recieved a new request, clear out the data object
self.data = NSMutableData()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
// Append the recieved chunk of data to our data object
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
// Request complete, self.data should now hold the resulting info
// Convert the retrieved data in to an object through JSON deserialization
var err: NSError
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var results: NSDictionary = jsonResult["results"] as NSDictionary
var collection: NSArray = results["collection1"] as NSArray
if jsonResult.count>0 && collection.count>0 {
var results: NSArray = collection as NSArray
self.tableData = results
self.tableView.reloadData()
}
}
}
Sie müssen die Tabelle erneut in den Thread UI
laden:
//Swift 2.3
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
//Swift 3
DispatchQueue.main.async{
self.tableView.reloadData()
}
Follow-up: Eine einfachere Alternative zum connection.start()
-Ansatz ist die Verwendung von NSURLConnection.sendAsynchronousRequest(...)
.
//NSOperationQueue.mainQueue() is the main thread
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: url), queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in
//check error
var jsonError: NSError?
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &jsonError)
//check jsonError
self.collectionView?.reloadData()
}
Dies ermöglicht Ihnen jedoch nicht die Flexibilität der Nachverfolgung der Bytes. Beispielsweise möchten Sie den Fortschritt des Downloads über bytesDownloaded/bytesNeeded berechnen
Sie müssen nur eingeben:
Zuerst ein IBOutlet:
@IBOutlet var appsTableView : UITableView
Dann in einer Action-Funktion:
self.appsTableView.reloadData()
Wenn Ihre Verbindung im Hintergrund-Thread ist, sollten Sie die Benutzeroberfläche im Haupt-Thread wie folgt aktualisieren
self.tblMainTable.performSelectorOnMainThread(Selector("reloadData"), withObject: nil, waitUntilDone: true)
Schnell 4:
self.tblMainTable.performSelector(onMainThread: #selector(UICollectionView.reloadData), with: nil, waitUntilDone: true)
In meinem Fall wurde die Tabelle korrekt aktualisiert, aber setNeedsDisplay () wurde nicht für das Image aufgerufen. Ich dachte irrtümlicherweise, dass die Daten nicht neu geladen wurden.
Das Problem war also, dass ich @lazy unangemessen verwenden wollte, was dazu führte, dass meine Business-Variable im Wesentlichen eine Konstante war und daher nicht editierbar war. Statt den lokalen Json zu laden, lade ich jetzt nur die von der API zurückgegebenen Daten.
import UIKit
class BusinessTableViewController: UITableViewController {
var data: NSMutableData = NSMutableData()
var Business: NSMutableArray = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = UIImageView(image: UIImage(named: "growler"))
tableView.registerClass(BeerTableViewCell.self, forCellReuseIdentifier: "cell")
tableView.separatorStyle = .None
fetchKimono()
}
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
return Business.count
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
if (Business.count > 0) {
let biz = Business[section] as NSDictionary
let beers = biz["results"] as NSArray
return beers.count
} else {
return 0;
}
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as BeerTableViewCell
if let path = indexPath {
let biz = Business[path.section] as NSDictionary
let beers = biz["results"] as NSArray
let beer = beers[path.row] as NSDictionary
cell.titleLabel.text = beer["BeerName"] as String
} else {
cell.titleLabel.text = "Loading"
}
return cell
}
override func tableView(tableView: UITableView!, viewForHeaderInSection section: Int) -> UIView! {
let view = LocationHeaderView()
let biz = Business[section] as NSDictionary
if (Business.count > 0) {
let count = "\(Business.count)"
view.titleLabel.text = (biz["name"] as String).uppercaseString
}
return view
}
override func tableView(tableView: UITableView!, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func fetchKimono() {
var urlPath = "names have been removed to protect the innocent"
var url: NSURL = NSURL(string: urlPath)
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
connection.start()
}
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
// Recieved a new request, clear out the data object
self.data = NSMutableData()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
// Append the recieved chunk of data to our data object
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
// Request complete, self.data should now hold the resulting info
// Convert the retrieved data in to an object through JSON deserialization
var err: NSError
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var results: NSDictionary = jsonResult["results"] as NSDictionary
var collection: NSArray = results["collection1"] as NSArray
if jsonResult.count>0 && collection.count>0 {
Business = jsonResult
tableView.reloadData()
}
}
}
Sie müssen eine Lazy-Eigenschaft immer als Variable deklarieren (mit dem Schlüsselwort var), da ihr Anfangswert möglicherweise erst nach Abschluss der Instanzinitialisierung abgerufen wird. Konstante Eigenschaften müssen vor der Initialisierung immer einen Wert haben und können daher nicht als Lazy deklariert werden.
Neben den offensichtlichen reloadData von UI/Main Thread (wie auch immer Apple es bezeichnet) hatte ich in meinem Fall vergessen, auch die SECTIONS-Informationen zu aktualisieren. Daher wurden keine neuen Abschnitte erkannt!
Sie müssen Ihre TableView nur in main thread neu laden. Andernfalls wird Ihre App abgestürzt oder nach einiger Zeit aktualisiert. Für jedes UI-Update wird empfohlen, den Haupt-Thread zu verwenden.
//To update UI only this below code is enough
//If you want to do changes in UI use this
DispatchQueue.main.async(execute: {
//Update UI
self.tableView.reloadData()//Your tableView here
})
//Perform some task and update UI immediately.
DispatchQueue.global(qos: .userInitiated).async {
// Call your function here
DispatchQueue.main.async {
// Update UI
self.tableView.reloadData()
}
}
//To call or execute function after some time and update UI
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
//Here call your function
//If you want to do changes in UI use this
DispatchQueue.main.async(execute: {
//Update UI
self.tableView.reloadData()
})
}
Alle Aufrufe der Benutzeroberfläche sollten asynchron sein. Alles, was Sie auf der Benutzeroberfläche ändern, z. B. das Aktualisieren der Tabelle oder das Ändern der Beschriftung, sollten Sie über den Hauptthread ausführen. Mit DispatchQueue.main wird Ihre Operation der Warteschlange im Hauptthread hinzugefügt.
Schnell 4
DispatchQueue.main.async{
self.tableView.reloadData()
}