Hello guys I'm trying to make a simple and re-usable Swift Network Layer
Maybe it's not the best way to loop returned data in view but after I tried to get the returned Api data to Loop it in SwiftUI view I'm getting error :
Escaping closure captures mutating 'self' parameter
And don't know where or what i missed in this lesson
and here's a picture of the file
ContentView.swift
struct ContentView: View {
var emptyDataArr: [CollectionItem] = []
init() {
ServiceLayer.request(router: Router.getSources) { (result: Result<[String : [CollectionItem]], Error>) in
switch result {
case .success(let data):
print(data)
self.emptyDataArr = data["custom_collections"]!
case .failure:
print(result)
}
}
}
var body: some View {
VStack (alignment: .leading) {
Text("No thing yet")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ServiceLayer Class ServiceLayer.swift
class ServiceLayer {
// 1.
class func request<T: Codable>(router: Router, completion: @escaping (Result<[String: [T]], Error>) -> ()) {
// 2.
var components = URLComponents()
components.scheme = router.scheme
components.host = router.host
components.path = router.path
components.queryItems = router.parameters
// 3.
guard let url = components.url else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = router.method
// 4.
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: urlRequest) { data, response, error in
// 5.
guard error == nil else {
completion(.failure(error!))
print(error?.localizedDescription)
return
}
guard response != nil else {
return
}
guard let data = data else {
return
}
print(data)
// 6.
let responseObject = try! JSONDecoder().decode([String: [T]].self, from: data)
// 7.
DispatchQueue.main.async {
// 8.
completion(.success(responseObject))
}
}
dataTask.resume()
}
}
The problem is that ContentView
is a struct, which means it's a value type. You can't pass that to a closure and mutate it. If you did, nothing would change, because the closure would have its own independent copy of the struct.
Your problem is that you've mixed your View and your Model. There can be many, many copies of a given View (every time it's passed to a function, a copy is made). You wouldn't want every one of those copies to initiate a request. Instead move this request logic into a Model object and just let the View observe it.
I ran into the same problem today and found this post, and then I followed @Rob Napier's suggestion, and finally made a working example.
Hope the following code can help (Note: it does not directly answer your question, but I think it will help as an example):
import Combine
import SwiftUI
import PlaygroundSupport
let url = URL(string: "https://source.unsplash.com/random")!
// performs a network request to fetch a random image from Unsplash’s public API
func imagePub() -> AnyPublisher<Image?, Never> {
URLSession.shared
.dataTaskPublisher(for: url)
.map { data, _ in Image(uiImage: UIImage(data: data)!)}
.print("image")
.replaceError(with: nil)
.eraseToAnyPublisher()
}
class ViewModel: ObservableObject {
// model
@Published var image: Image?
// simulate user taps on a button
let taps = PassthroughSubject<Void, Never>()
var subscriptions = Set<AnyCancellable>()
init() {
taps
// ⭐️ map the tap to a new network request
.map { _ in imagePub() }
// ⭐️ accept only the latest tap
.switchToLatest()
.assign(to: \.image, on: self)
.store(in: &subscriptions)
}
func getImage() {
taps.send()
}
}
struct ContentView: View {
// view model
@ObservedObject var viewModel = ViewModel()
var body: some View {
VStack {
viewModel.image?
.resizable().scaledToFit().frame(height: 400).border(Color.black)
Button(action: {
self.viewModel.getImage()
}, label: {
Text("Tap")
.padding().foregroundColor(.white)
.background(Color.pink).cornerRadius(12)
})
}.padding().background(Color.gray)
}
}
PlaygroundPage.current.setLiveView(ContentView())
Before tapping the button, the ContentView
looks like this:
After tapping the button (and wait for a few seconds), it looks like this:
So I know it's working ^_^
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With