I need to fetch data before view loads and display the data in a button text.
Locations.swift
class GetLocations :ObservableObject{
@Published var arrLocations = NSArray()
func getLocNames(Action:String, Id: String, completion: @escaping (NSArray) -> Void){
//fetch data from server
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error)
in
if let response = response {
print(response)
}
if let data = data {
let parseResult: NSDictionary!
do {
parseResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary
self.arrLocations = (parseResult.value(forKey: "InfoList"))as! NSArray
}catch {
print(error)
}
}
}.resume()
return self.arrLocations
}
}
Now I called the above function in another swiftUI file
GetLocationData.swift
import SwiftUI
struct GetLocationData: View {
@State var arrloc = NSArray()
init() {
let g = GetLocations()
g.getLocNames(Action: "US", Id: "ba6fcd92-3d1e-4fb6-b135-0c47ea1815cd"){locationsarray in
self.arrloc = locationsarray
}
}
var body: some View {
VStack(spacing: 20){
Button(action: {},
label: {
Text("Select Menu"). //Here I need to assign the locationsarray I got from server and assign the valueof key to it (In obj-C we have [array objectAtIndex:0]objectForKey:@"Name"]].
I couldnt find the equivalent of it in swift
.frame(minWidth: 0, maxWidth: 500)
.padding()
.background(Color.clear)
.foregroundColor(Color.black)
.font(.custom("Open Sans", size: 18))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 2)
)
})
}
}
Error in init() : Escaping closure captures mutating 'self' parameter
How can I get it solved and get data before the view loads and assign the data to button text ?
2
Answers
Let’s simplify the problem. You are trying to run an asynchronous function in the
init
, setting the result of that to a property. The problem is that sinceGetLocationData
hasn’t been instantiated yet, there is no instance ofself
to use.Here is a simplified example of the problem:
This gives us the error, like you had:
To fix this, just run this code in the
onAppear
of the view.Fixed example code:
This obviously needs to be converted into your code, but I hope this gives you the idea because of the simplified code.
The end result should look something like this:
Note: you should use
Array
rather thanNSArray
.The first thing you need to clear up is the separation between your model and your view.
GetLocations
is your model class. It is anObservableObject
and it has an@Published
array of your location names. There is no need for the@State
array. You should refer to an instance of your model object directly.Since the model object will update its own
arrLocations
property, there is also no need to pass a completion handler togetLocNames
– This is the cause of your error. You are trying to mutateself
– your view struct – in a closure from its own initialiser. This is not possible as structs are immutable.While we are looking at your model object, let’s get rid of the use of
NSArray
(You should always use properly typed arrays in Swift) and the old-styleJSONSerialization
and useCodable
. I assume that the array you are after is an array of some struct, that has at least aName
property which is a string – If you want other properties you need to add those to the struct. We’ll also addcodingKeys
to map the upper-case property names to camel case.Now, you need to pass an instance of your model object to your view. I suggest using the environment. Add the instance to your environment by using the
.environmentObject
view modifier where you create yourGetLocationData
viewThen you can use it in your content view. Rather than using
init
, I suggest you use.onAppear
to fetch the locations.I couldn’t quite follow what you wanted to do with the buttons, so I have replaced that code with a simple list of the location names – You should be able to work from this