< Previous: Die grundlegende UI erstellen: UITabBarController | Next: Eine Petition rendern: loadHTMLString > |
JSON - kurz für JavaScript Object Notation - ist eine Art, Daten zu beschreiben. Es ist nicht gerade das lesbarste Format, aber es ist kompakt und kann leicht von Computern geparst werden, was es online beliebt macht, wo Bandbreite kostbar ist.
Bevor wir uns ans Parsen machen, hier ein winziger Ausschnitt der tatsächlichen JSON, welche du empfangen wirst:
{
"metadata":{
"responseInfo":{
"status":200,
"developerMessage":"OK",
}
},
"results":[
{
"title":"Legal immigrants should get freedom before undocumented immigrants – moral, just and fair",
"body":"I am petitioning President Trump's Administration to take a humane view of the plight of legal immigrants. Specifically, legal immigrants in Employment Based (EB) category. I believe, such immigrants were short changed in the recently announced reforms via Executive Action (EA), which was otherwise long due and a welcome announcement.",
"issues":[
{
"id":"28",
"name":"Human Rights"
},
{
"id":"29",
"name":"Immigration"
}
],
"signatureThreshold":100000,
"signatureCount":267,
"signaturesNeeded":99733,
},
{
"title":"National database for police shootings.",
"body":"There is no reliable national data on how many people are shot by police officers each year. In signing this petition, I am urging the President to bring an end to this absence of visibility by creating a federally controlled, publicly accessible database of officer-involved shootings.",
"issues":[
{
"id":"28",
"name":"Human Rights"
}
],
"signatureThreshold":100000,
"signatureCount":17453,
"signaturesNeeded":82547,
}
]
}
Tatsächlich wirst du 2000-3000 Zeilen von diesem Zeug kriegen, die Petitionen von US-Bürgern über alle möglichen politischen Dinge enthalten. Es ist eigentlich gar nicht wichtig (für uns), um was es bei den Petitionen geht, wir interessieren uns nur für die Datenstruktur. Insbesondere:
responseInfo
enthält, der wiederum einen Status-Wert enthält. Mit Status 200 meinen Internet-Entwickler, dass "alles OK" ist.Swift hat eine eingebaute Unterstützung für JSON mit einem Protokoll namens Codable
. Wenn du sagst, "meine Daten genügen Codable
", wird Swift dir erlauben, mit nur wenig Code frei zwischen diesen Daten und JSON zu konvertieren.
Swifts einfache Typen wie String
und Int
genügen automatisch Codable
, und Arrays und Dictionaries genügen ebenfalls Codable
, wens sie Codable
-Objekte enthalten. Das heißt, [String]
genügt Codable
problemlos, da String
selbst Codable
genügt.
Hier benötigen wir allerdings etwas Komplexeres: jede Petition enthält einen Titel, einen Textkörper, die Anzahl der Unterschriften, und mehr. Das heißt, wir müssen unseren eigenen maßgeschneiderten Datentyp definieren anstatt einfach nur String
oder Int
zu verwenden.
Swift hat zwei Wege, um eigene Datentypen zu erzeugen, die als Structs und Klassen bekannt sind. Wir haben Klassen schon verwendet - alle unsere UIViewController
-Subklassen sind Klassen - aber wenn du mit eigenen Daten arbeitest anstatt eine Subklasse zu erzeugen, ist es im Allgemeinen besser, mit Structs zu arbeiten, da sie einfacher sind.
Wir werden ein eigenes Struct Petition
erzeugen, das eine Petition aus unserer JSON enthält, das heißt, es wird den Titel-String, den Textkörper-String, und die Anzahl der Unterschriften, eine Ganzzahl, speichern. Drücke daher Cmd+N und wähle eine neue Swift-Datei mit dem Namen Petition.swift.
struct Petition {
var title: String
var body: String
var signatureCount: Int
}
Dies definiert ein spezielles Struct mit drei Eigenschaften. Einer der Vorteile von Structs in Swift ist, dass sie uns einen elementweisen Initialisierer geben - eine spezielle Funktion, die neue Petition
-Instanzen erzeugen kann, indem Werte für title
, body
und signatureCount
übergeben werden.
Dazu kommen wir gleich, aber zunächst hatte ich das Codable
-Protokoll erwähnt. Unser Petition
Struct enthält zwei Strings und eine Ganzzahl, die alle bereits Codable
genügen, daher können wir Swift sagen, dass der ganze Petition
-Typ Codable
erfüllen soll, und zwar so:
struct Petition: Codable {
var title: String
var body: String
var signatureCount: Int
}
Mit dieser simplen Änderung sind wir beinahe soweit, Instanzen von Petition
aus JSON zu laden.
Ich sage beinahe soweit, denn da ist noch ein kleiner Haken in unserem Plan: wenn du dir das oben gegebene JSON-Beispiel angeschaut hast, ist dir aufgefallen, dass unserer Petitions-Array tatsächlich innerhalb eines Dictionaries namens "results" liegt. Das bedeutet, wenn wir versuchen, Swift den JSON-Code parsen zu lassen, müssen wir zuerst den Schlüssel laden, und dann darin das Array der Petitions-Resulate laden.
Swifts Codable
-Protokoll muss exakt wissen, wo es die Daten findet, was in diesem Falle bedeutet, ein zweites Struct zu machen. Dieses wird eine einzelne Eigenschaft namens results
haben, welches ein Array von unseren Petition
-Structs ist. Das passt dann exakt dazu, wie JSON aussieht: die Haupt-JSON enthält das results
-Array, und in jedem Element in diesem Array ist eine Petition
.
Drücke also erneut Cmd+N, um eine neue Datei zu erzeugen, wähle Swift-Datei und nenne sie Petitions.swift. Gib ihr diesen Inhalt:
struct Petitions: Codable {
var results: [Petition]
}
Ich sehe ein, dass das nach einer Menge Arbeit aussieht, aber vertrau mir: es wird noch viel leichter!
Alles, was wir getan haben, war, die Datenstrukturen zu definieren, in die wir die JSON laden wollen. Der nächste Schritt ist, eine Eigenschaft in ViewController
zu erzeugen, die unser Petitions-Array speichert.
Wie du dich erinnerst, deklarierst du Arrays, indem du einfach den Datentyp in eckigen Klammern schreibst, so wie hier:
var petitions = [String]()
Wir wollen ein Array aus unserem Petition
-Objekt machen. Das sieht dann so aus:
var petitions = [Petition]()
Ersetze damit die aktuelle petitions
-Definition am Anfang von ViewController.swift.
Jetzt ist es an der Zeit, JSON zu parsen, was bedeutet, sie zu verarbeiten und die Inhalte zu prüfen. Wir fangen damit an, die viewDidLoad()
-Methode für ViewController
so anzupassen, dass sie die Daten vom Petitions-Server des Weißen Hauses herunterlädt, sie in ein Swift Data
-Objekt umwandelt, und dann versucht, dieses in ein Array von Petition
-Instanzen zu konvertieren.
Wir haben Data
bisher noch nicht verwendet. Wie String
und Int
ist dies einer der fundamentalen Datentypen von Swift, auch wenn er auf noch niedrigerer Stufe angesiedelt ist - er enthält buchstäblich irgendwelche Binärdaten. Das könnte ein String sein, das könnte der Inhalt einer Zip-Datei sein, or buchstäblich irgendetwas anderes.
Data
und String
haben einige Dinge gemeinsam.
Du hast bereits gesehen, dass ein String
mit Hilfe von contentsOfFile
erzeugt werden kann, um Daten von der Festplatte zu laden, und Data
hat genau den gleichen Initialisierer. Beide können also mit einem contentsOf
-Initialisierer erzeugt werden, der Daten von einer URL (mit URL
spezifiziert) herunterlädt und dir zur Verfügung stellt.
Das ist für unsere Zwecke perfekt - hier ist die neue viewDidLoad
-Methode:
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://api.whitehouse.gov/v1/petitions.json?limit=100"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
// we're OK to parse!
}
}
}
Konzentrieren wir uns auf das Neue:
urlString
verweist auf den Whitehouse.gov Server, um auf das Petitions-System zuzugreifen.if let
, um sicherzustellen, dass die URL
gültig ist, anstatt sie zwangsweise zu entpacken ("force unwrap").Data
-Objekt mit Hilfe der contentsOf
-Methode. Diese liefert den Inhalt einer URL
zurück, kann aber auch einen Fehler werfen (zum Beispiel, wenn keine Internetverbindung besteht), daher müssen wir try?
verwenden.Data
-Objekt erfolgreich erzeugt wurde, erreichen wir die Zeile "we're OK to parse" ("wir sind bereit zum Parsen"). Diese beginnt mit //
, womit eine Kommentarzeile in Swift bezeichnet wird. Kommentarzeilen werden vom Compiler ignoriert; wir schreiben sie als Notizen für uns selbst. Dieser Code ist nicht perfekt, ganz im Gegenteil. Indem wir Daten aus dem Internet in viewDidLoad()
laden, wird faktisch unsere App blockiert, bis alle Daten transferiert wurden. Es gibt dazu Lösungen, aber um es einfach zu halten, behandeln wir diese erst in Projekt 9.
Im Moment wollen wir uns auf das Parsen von JSON konzentrieren. We haben bereits ein petitions
-Array, das bereit ist, ein Array von Petitionen aufzunehmen. Wir wollen Swifts Codable
-System nutzen, um unsere JSON in das Array zu parsen, und sobald das fertig ist, sagen wir unserer Table View, dass sie sich selbst neu laden soll.
Bist du soweit? Denn dieser Code ist erstaunlich einfach für die viele Arbeit, die er erledigt:
func parse(json: Data) {
let decoder = JSONDecoder()
if let jsonPetitions = try? decoder.decode(Petitions.self, from: json) {
petitions = jsonPetitions.results
tableView.reloadData()
}
}
Platziere die Methode direkt unterhalb von der viewDidLoad()
-Methode, dann ersetze die Zeile // we're OK to parse!
in viewDidLoad()
mit dieser:
parse(json: data)
Die neue Methode parse()
macht einige neue und interessante Dinge:
JSONDecoder
, die speziell dafür ausgelegt ist, zwischen JSON und Codable
-Objekten zu konvertieren.decode()
dieses Dekoders auf, um unsere json
-Daten in ein Petitions
-Objekt zu konvertieren. Dieser Aufruf kann einen Fehler werfen, daher verwenden wir try?
, um zu prüfen, ob es funktioniert hat.results
-Array unserer Eigenschaft petitions
zu und laden die Table View neu.Der Teil, den du bisher noch nicht gesehen hast, ist Petitions.self
, was Swifts Art ist, den Petitions
-Typ direkt zu bezeichnen anstatt einer Instanz davon. Das heißt, wir sagen nicht "erzeuge eine neue", sondern spezifizieren ihn stattdessen als Parameter für das Dekodieren, sodass JSONDecoder
weiß, in was er die JSON konvertieren soll.
Du kannst das Programm jetzt laufen lassen, auch wenn es nur "Title goes here" und "Subtitle goes here" immer und immer wieder anzeigt, denn unsere cellForRowAt
-Methode fügt nur Platzhalter-Daten hinzu.
Wir wollen das so modifizieren, dass die Zellen den title
-Wert unseres Petition
-Objekts ausgeben, aber wir wollen auch das Untertitel-Textlabel verwenden, das dazu kam, als wir den Zellentyp im Storyboard von "Basic" zu "Subtitle" geändert haben. Ändere dafür die Methode cellForRowAt
wie folgt:
let petition = petitions[indexPath.row]
cell.textLabel?.text = petition.title
cell.detailTextLabel?.text = petition.body
Wir setzen die Schlüssel title
, body
und sigs
im Objekt, daher können wir sie nun auslesen, um unsere Zelle korrekt zu konfigurieren.
Wenn du jetzt die App laufen lässt, siehst du, wie die Dinge sich ganz nett zusammen fügen - jede Tabellen-Zeile zeigt jetzt den Titel der Petition, und darunter stehen die ersten Worte des Textkörpers der Petition. Der Untertitel zeigt automatisch "..." am Ende an, wenn es nicht genug Platz für den gesamten Text gibt, aber das ist genug, um dem Nutzer einen Eindruck davon zu verschaffen, um was es geht.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.