I have a flow in UIKit where, when I call a function, I retrieve a response from my API, this response is then used to create a UIViewController
. Then this view controller is presented full screen:
getResponse() { result in
switch result {
case .success(let response):
let viewController = MyViewController(response: response)
presenter.present(viewController) { success in
// etc
}
case .failure:
break
}
}
I want to implement the equivalent of this in SwiftUI using a view modifier to abstract this:
.showMyView(isPresented: $isPresented)
When isPresented
is true, it should get the response then present MyView
. I have bits in my head of what I think I need to use, but am not sure how to piece them together.
I know that I need to use a UIViewControllerRepresentable
to handle the creation of a SwiftUI version of MyViewController
:
struct MyView: UIViewControllerRepresentable {
var response: MyResponse
func makeUIViewController(context: Context) -> MyViewController {
return MyViewController(response: response)
}
}
I'm assuming I can pass the response in. However, how do I handle the asynchronousity of it with respect to the body of the view modifier?
struct ShowMyView: ViewModifier {
@Binding var isPresented: Bool
func body(content: Content) -> some View {
content
.sheet(isPresented: $isPresented) {
// something that returns MyView here?
}
.onReceive(Just(isPresented)) { _ in
getMyResponse()
}
}
func getMyResponse() {
getResponse() { result in
switch result {
case .success(let response):
// Something needs to happen here
case .failure:
break
}
}
}
}
I couldn't figure it out. Any help appreciated!
CodePudding user response:
You need internal state, like (typed here, so some typos/errors might needed to be fixed)
struct ShowMyView: ViewModifier {
@Binding var isPresented: Bool
@State private var result: MyResponse? = nil
func body(content: Content) -> some View {
content
.sheet(item: $result) { // MyResponse needed to be Identifiable
switch $0 {
case .success(let response):
MyView(response: response)
case .failure:
// something appropriate here, e.g. ErrorView()
break
}
}
.onReceive(Just(isPresented)) { _ in
getMyResponse()
}
}
func getMyResponse() {
getResponse() { result in
DispatchQueue.main.async {
self.result = result // update UI on main queue
}
}
}
}
CodePudding user response:
I think this is what you want:
struct ShowMyView: ViewModifier {
@Binding var shouldPresent: Bool
@State private var response: MyResponse? = nil
private var isPresentedBinding: Binding<Bool> {
return Binding(
get: { shouldPresent && response != nil },
set: { shouldPresent = $0 }
)
}
func body(content: Content) -> some View {
content
.sheet(isPresented: isPresentedBinding) {
if let response = response {
MyView(response: response)
}
}
.onChange(of: shouldPresent) {
if $0 && response == nil {
getMyResponse()
}
}
}
func getMyResponse() {
getResponse() { result in
switch result {
case .success(let response):
self.response = response
case .failure:
break
}
}
}
}