I have a function which calls a REST API and returns the result via a completionHandler.
I want to call this function when pressing a NavigationLink but use the result as the object passed into the destination. Can't quite figure out how to do this, or if it's even possible. Here's my current code:
The REST function
func postProgramme(programmeName: String, programmeDays: Int, programmeDescription: String, completeionHandler: @escaping ProgrammeCompletionHandler) {
struct PostRoutineData: Codable {
let programmeName: String
let programmeDays: Int
let programmeDescription: String
}
let postProgrammeData = PostRoutineData(programmeName: programmeName, programmeDays: programmeDays, programmeDescription: programmeDescription)
do {
let jsonData = try JSONEncoder().encode(postProgrammeData)
let jsonString = String(data: jsonData, encoding: .utf8)!
let request = RESTRequest(path: "/workout/programme", body: jsonString.data(using: .utf8))
Amplify.API.post(request: request) { result in
switch result {
case .success(let data):
do {
//let str = String(decoding: data, as: UTF8.self)
let programme = try self.decoder.decode(Programme.self, from: data)
completeionHandler(programme)
} catch {
print("[ERROR] Error within postProgrammes()", error)
}
case .failure(let error):
print("[ERROR] Error within postProgrammes()", error)
}
}
} catch {
print("[ERROR] Error within postProgramme()", error)
}
}
The NavigationLink, and my attempt to figure the problem out:
NavigationLink(destination: ProgrammeDetailView(), isActive: $shouldTransit) {
Text("Create Programme")
.onTapGesture {
self.createNewProgramme()
self.shouldTransit = true
}
}
func createNewProgramme() -> ProgrammeDetailView {
sessionManager.postProgramme(programmeName: programmeName, programmeDays: programmeDays, programmeDescription: programmeDescription, completeionHandler: {(programme) -> ProgrammeDetailView in
return ProgrammeDetailView(programme: programme)}
}
Assuming it's an iOS app, if you can deploy for iOS 15, you can use the recent async/ await environment, by using an async function and returning a Programme
, instead of using a completion handler.
Programme
: func postProgramme(programmeName: String, programmeDays: Int, programmeDescription: String) async -> Programme? {
// ...
case .success(let data):
do {
let programme = try self.decoder.decode(Programme.self, from: data)
// No completion handler: return a Programme
// completionHandler(programme)
return programme
} catch {
print("[ERROR] Error within postProgrammes()", error)
// Return nil everywhere else
return nil
}
@State
var of type Programme?
, that will be binding to another variable in ProgrammeDetailView
.The function createNewProgramme()
will update the state variable.
@State private var programme: Programme? = nil
var body: some View {
NavigationView {
// Pass the binding to ProgrammeDetailView
NavigationLink(destination: ProgrammeDetailView(programme: $programme, content: { programme in
// A customised view
Text(programme?.name ?? "")
}), isActive: $shouldTransit) {
Text("Create Programme")
.onTapGesture {
self.createNewProgramme()
self.shouldTransit = true
}
}
}
func createNewProgramme() {
// Task will allow working with async functions
Task {
let programme = await sessionManager.postProgramme(programmeName: programmeName, programmeDays: programmeDays, programmeDescription: programmeDescription)
// Back to main thread to update the UI
DispatchQueue.main.async {
self.programme = programme
}
}
}
@Binding
variable in ProgrammeDetailView
. The example below can receive any view as a parameter, but because the REST API will take some time to respond, you need to handle the case where programme == nil
:struct ProgrammeDetailView<V: View> : View {
// The binding with the parent view
@Binding var programme: Programme?
// Your customised view that receives a Programme as a parameter
let content: (Programme)->V
var body: some View {
if programme == nil {
ProgressView()
} else {
content(programme!)
}
}
}