Post bearbeiten
Was muss ich diesmal lernen?
- Wie macht man einen weiteren Screen?
- Wie funktioniert
[navigationController pushViewController:controller]
in SwiftUI? - Wie baue ich ein Formular und befülle es mit Daten, die ich aus der API lade?
- Wie mache ich einen PATCH-Request mit JSON-Payload?
Na dann mal keine Zeit verlieren!
Weitere Screens anlegen
Ein weiterer Screen ist einfach nur ein View. Theoretisch kann man auch einfach Text("Hello World")
auf den Navigation Stack pushen. Okay.
Des Weiteren finde ich es sehr erfreulich, dass man bei Swift auch erstmal alles in eine Datei hauen kann. Für’s schnelle Prototypen, wie ich es gerne mag, aufräumen kann ich, wenn ich tot bin (oder wenn ich mich dafür entschieden habe, dass ich diese App wirklich bauen will), ist sowas immer sehr hilfreich.
struct EditPost: View {
var postId: String
var body: some View {
VStack() {
Text("Detailseite: \(postId)")
}
.navigationBarTitle("Detail")
.navigationBarItems(trailing:
Button(action: {
print("Save")
}) {
Text("Speichern")
}
)
}
}
Ungefähr so. Ich hab auch schon einen Speichern-Button oben rechts hingemacht. Das war auch relativ einfach, wie man sieht. Ich bin mir noch nicht so richtig sicher, wie ich das finde, dass man alles mögliche einfach in solchen gechainten Methodenaufrufen macht. Aber letztendlich ist das halt wohl einfach die SwiftUI DSL.
Navigation
Das Navigieren gestaltet sich auch erstmal einfach. Man wrappt seinen VStack
, der die UITableViewRow darstellt einfach in einen NavigationLink
:
NavigationLink(destination: EditPost(postId: item.id)) {
// VStack, …
}
Schon geht das Formular auf! Jetzt muss es nur noch was anzeigen.
Formular
Erstmal Titel und Text, was braucht ein Blogpost sonst noch so? (/me schielt auf seine overengineerten Blueprints). Also ein TextField
für den Titel rein, schön. Der Text bekommt auch ein TextField
, da muss ich sicher nur irgendwie numberOfLines
auf 0
setzen, oder auf -1
, wer weiß.
Moment mal, die Property gibt es gar nicht? Gibt es vielleicht einen ganz anderen View dafür? TextArea
? Pustekuchen. Naaaja. Google erzählte mir schnell, dass der aktuelle Way to Go wohl das Wrappen eines UITextView
ist, aber auch, dass es in iOS14 einen TextEditor
gibt. Juchu! Bis dahin kopierte schonmal so einen UITextView
-Wrapper von StackOverflow und fügte ihn ein. Vielleicht doch mal Catalina und Xcode Beta installieren?
VStack() {
TextField("Titel", text: $title)
TextView(text: $text)
}
Sehr übersichtlich bisher auf jeden Fall!
Formular befüllen
Hier kommen wir zu dem Teil, mit dem ich wohl am meisten Zeit verbrauchte. Wie bekomme ich die Daten aus dem API-Request für die Page-Details in das Formular.
Ich erstellte nun also noch ein ObservableObject
um die Detail-Daten zu laden und band es als @ObservedObject var fetch = FetchSinglePost()
in den View ein.
Das Problem hierbei: Ich kann bei TextField
und TextView
nicht fetch.post.title
übergeben, weil er irgendwie ein <Binding>
erwartet. Ich muss also den Kram aus fetch.post.title
irgendwie in self.title
, die @State
-Variable bekommen.
Kein Problem, dachte ich, der FetchSinglePost-Kram ist ja extra observierbar, dann observiere ich also mal.
.onAppear {
fetch.$post.sink { (post) in
print("changed \(post?.title)")
if(post?.title != nil) {
self.title = post?.title ?? ""
self.text = post?.content?.text ?? ""
}
}
}
Der erste Versuch klappte schon einmal nicht. Wenn ich das Fetch oben als Property von der View-Struct definiere, wird der Request sofort ausgeführt, und die Daten sind da, bevor onAppear
passiert und ich los-sink
e.
Mein erster Versuch war also das var fetch = FetchSinglePost(postId: self.postId)
mit ins onAppear
zu ziehen, damit ich mich direkt dranhängen kann. Funktionierte schonmal besser, ich bekam immerhin ein changed nil
auf die Konsole! Toll!
Der Request ging aber durch, Daten kamen an und wurden geparsed, warum kommt kein weiteres Log-Statement?
Des Rätsels Lösung, ist wieder sowas, was man sicher gelernt hätte, würde man nicht durch Google-Trial-And-Error lernen, sondern ein Buch lesen. sink
liefert ein Cancellable
-Dingsbums zurück. Wenn man das ignoriert und nicht irgendwo speichert, wird es direkt wieder aufgesammelt und entsorgt, und niemand sagt einem mehr Bescheid, dass sich etwas geändert hat. Also schnell
@State var cancellable: Any = nil
Oben rein, den sink
-Rückgabewert rein und alles ist gut. Ich bekomme mitgeteilt, wenn sich was ändert und kann es in den State schreiben, juchu!
Insgesamt war mir das jetzt aber doch etwas kompliziert, ich möchte also nicht ausschließen, dass ich nicht etwas komplett falsch gemacht habe — mein googlen lieferte jedenfalls kein besseres Ergebnis.
Request
Nachdem das bauen des Formulars überstanden war, war der Request eigentlich wieder ein Kinderspiel.
Durch ein StackOverflow-Snippet wurde ich darauf aufmerksam, dass man auch einfach “dumme” Dictionaries bauen kann und die zu JSON serialisieren kann, ohne vorher alles in struct
s zu gießen und mit dem JSONDecoder
zu arbeiten.
var json = [String:Any]()
json["text"] = self.text
let data = try JSONSerialization.data(withJSONObject: json, options: [])
var request = URLRequest(url: url)
request.httpMethod = "PATCH"
request.httpBody = data
request.setValue("Basic xxx", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request).resume()
Es speichert, was für ein schönes Gefühl!
Aktueller Nerv-Status:
- Swift 6 / 10
- Kirby 2 / 10
- Warum mache ich das Projekt eigentlich? 4 / 10
Ich glaube am meisten nervt mich am mobilen Kirby-Panel, wie fummelig klein der Bild-Upload-Button ist. Also versuchen wir doch noch einen Bild-Upload einzubauen!