Text not displaying value of Var. Swift - swift

I'm a beginner, but I'm trying to display the "valueWeWantToGrab" to a text field but is not showing the result.
this is my code to display someone's Instagram follower amount
struct ContentView: View {
#State var valueWeWantToGrab = ""
var body: some View {
let baseUrl = "http://www.instagram.com/"
let username = ""
let url = URL(string: baseUrl + username)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
print("data was nil")
return
}
guard let htmlString = String(data: data, encoding: .utf8) else {
print("couldn't cast data into String")
return
}
print(htmlString)
let leftSideString = """
edge_followed_by":{"count":
"""
let rightSideString = """
},"followed_by_viewer
"""
guard
let leftSideRange = htmlString.range(of: leftSideString)
else {
print("couldn't find left range")
return
}
guard
let rightSideRange = htmlString.range(of: rightSideString)
else {
print("couldn't find right range")
return
}
let rangeOfTheData = leftSideRange.upperBound..<rightSideRange.lowerBound
var valueWeWantToGrab = htmlString[rangeOfTheData]
print(valueWeWantToGrab) // prints the follower count: 19093
}
Text(valueWeWantToGrab)
}
}

The issue is that you declare the variable valueWeWantToGrab twice: Once at the top annotated with #State and once towards the bottom within the getter function of the body variable.
What you actually want to do at the bottom is assigning to the variable declared at the top instead of declaring a new variable by removing var from the line var valueWeWantToGrab = htmlString[rangeOfTheData]. Doing that, you also have to wrap htmlString[rangeOfTheData] with a String initializer (String()) as htmlString[rangeOfTheData] gives you a value of type Substring, not a value of type String. This is the result:
...
let rangeOfTheData = leftSideRange.upperBound..<rightSideRange.lowerBound
valueWeWantToGrab = String(htmlString[rangeOfTheData])
print(valueWeWantToGrab) // prints the follower count: 19093
...
Also, to make your code a little cleaner (though not required to make it work), it might be a good idea to move the code that loads the data to a dedicated method instead of putting it in the getter of the body variable (as a general rule of thumb, try to only put UI elements in the body of SwiftUI Views and do the business logic somewhere else - that will help you to keep your code clean and readable). You can then call the method in the initializer of your ContentView. This comes with the benefit that you can then reuse that method elsewhere - for example to implement a refresh button in the future. This is what the final result might look like:
struct ContentView: View {
#State var valueWeWantToGrab = ""
var body: some View {
Text(valueWeWantToGrab)
}
init() {
loadFollowerCount()
}
private func loadFollowerCount() {
let baseUrl = "http://www.instagram.com/"
let username = ""
let url = URL(string: baseUrl + username)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
print("data was nil")
return
}
guard let htmlString = String(data: data, encoding: .utf8) else {
print("couldn't cast data into String")
return
}
print(htmlString)
let leftSideString = """
edge_followed_by":{"count":
"""
let rightSideString = """
},"followed_by_viewer
"""
guard
let leftSideRange = htmlString.range(of: leftSideString)
else {
print("couldn't find left range")
return
}
guard
let rightSideRange = htmlString.range(of: rightSideString)
else {
print("couldn't find right range")
return
}
let rangeOfTheData = leftSideRange.upperBound..<rightSideRange.lowerBound
valueWeWantToGrab = String(htmlString[rangeOfTheData])
print(valueWeWantToGrab) // prints the follower count: 19093
}
}
}
One final tip in case you come from a web development background (which is what I am guessing): vars in Swift are different from vars in JavaScript. In JavaScript, the redeclaration would have overridden the previous value of valueWeWantToGrab and your code would have worked. However, in Swift, this actually declares a new, different variable in the local scope of the getter function. Have a look at the Swift docs to find out more about how variable declarations work in Swift.

Related

Having trouble adding values to core data outside of a View

I'm trying to load data into a CoreData entity "Articles" from a function I would like to call in an init() {} call when my app starts which means I'm not doing this from within a view.
I get the message "Accessing Environment's value outside of being installed on a View. This will always read the default value and will not update."
and would like to work around that. I'm using Xcode 14.2
I do have a standard PersistenceController setup and so on
Here is where I run into the issue "let section = SectionsDB(context: managedObjectContext)"
#main
struct ArticlesExampleApp: App {
let persistanceController = PersistanceController.shared
init() {
let x = Articles.loadSections()
}
var body: some Scene {
WindowGroup {
MasterView()
.environment(\.managedObjectContext, persistanceController.container.viewContext)
}
}
class Articles {
class func loadSections() -> Int {
#Environment(\.managedObjectContext) var managedObjectContext
// Initialize some variables
let myPath = Bundle.main.path(forResource: "Articles", ofType: "json")
// Initialize some counters
var sectionsCount = 0
do {
let myData = try Data(contentsOf: URL(fileURLWithPath: myPath!), options: .alwaysMapped)
// Decode the json
let decoded = try JSONDecoder().decode(ArticlesJSON.self, from: myData)
// **here is where I run into the error on this statement**
let section = SectionsDB(context: managedObjectContext)
while sectionsCount < decoded.sections.count {
print("\(decoded.sections[sectionsCount].section_name) : \(decoded.sections[sectionsCount].section_desc)")
section.name = decoded.sections[sectionsCount].section_name
section.desc = decoded.sections[sectionsCount].section_desc
sectionsCount+=1
}
PersistanceController.shared.save()
} catch {
print("Error: \(error)")
}
return sectionsCount
}
}
Since you are already using a singleton, you can just use that singleton in your loadSections function:
let section = SectionsDB(context: PersistanceController.shared.container.viewContext)
And, remove the #Environment(\.managedObjectContext) var managedObjectContext line

Passing data to another view using a model - SwiftUI

I'm trying to pass the data retrieved from the API to a View, but I'm getting the following error:
Class 'ApiManagerViewModel' has no initializers
This is how the ViewModel looks:
class ApiManagerViewModel: ObservableObject {
#Published var blockchainData: ApiDataClass
func callAPI() {
guard let url = URL(string: "myapiurl") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url, timeoutInterval: Double.infinity)
let callAPI = URLSession.shared.dataTask(with: request) { data, responce, error in
do {
if let data = data {
let decodedResponse = try JSONDecoder().decode(APIResponce.self, from: data)
DispatchQueue.main.async {
// update our UI
self.blockchainData = (decodedResponse.data)
}
// Everything is good, so we can exit
return
}
} catch {
print("Unexpected error while fetchign API: \(error).")
return
}
}
callAPI.resume()
}
This is the model:
// MARK: - APIResponce
struct APIResponce: Codable {
let data: ApiDataClass
let error: Bool
}
// MARK: - DataClass
struct ApiDataClass: Codable {
let address, quote_currency: String
let chain_id: Int
let items: [ApiItems]
}
// MARK: - Item
struct ApiItems: Codable {
let contract_decimals: Int32
let contract_name, contract_ticker_symbol, contract_address, logo_url, type, balance: String
let supports_erc: [String]?
let quote_rate: Double?
let quote: Double
}
I've tried initializing it but it's no bueno:
init() {
let address = 0, quote_currency = 0
let chain_id = 0
let items: [ApiItems]
}
If I initialize it like that I get the error, and I also don't want to repeat the same thing the model has:
Return from initializer without initializing all stored properties
I also tried with the variable like:
#Published var blockchainData = []
and I get the error on this line: self.blockchainData = (decodedResponse.data):
Cannot assign value of type 'ApiDataClass' to type '[Any]'
How can I make the variable blockchainData have the value coming from decodedResponse.data so I can pass it to another view?
Thanks
You're getting that error because you've declared var blockchainData: ApiDataClass, but haven't given it an initial value (your attempt at providing an initializer for ApiDataClass didn't help because the problem is ApiManagerViewModel).
The easiest solution to this is to turn it into an optional:
#Published var blockchainData: ApiDataClass?
Then, in your View, you'll probably want to check if it's available. Something like:
if let blockchainData = viewModel.blockchainData {
//code that depends on blockchainData
}
(assuming your instance of ApiManagerViewModel is called viewModel)

SwiftUI How can I pass in data into an ObservedObject function

I am new to SwiftUI and Swift . I got a Search Bar and a Listview whenever a user types something in the searchbar I do an http request and new data comes in . The issue is that the list is not updating with the new data and I think I know why . I need to pass my SearchBar response into the ObservedObject variable . I was reading this swiftui ObservedObject function call in all view however I still didn't find my answer . This is my code
struct RegistrationView: View {
#State private var searchTerm: String = ""
#State var txt = "" // Txt has the SearchBar text
#ObservedObject var getData = datas(location: "") // I need to pass it here
var body: some View {
VStack {
Text("Registration")
searchView(txt: $txt)
// datas(location: txt)
NavigationView {
List(getData.jsonData.filter{ txt == "" ? true : $0.name.localizedCaseInsensitiveContains(txt)}) { i in
ListRow(name: i.name,statelong: i.statelong)
}
}
.padding(.top, 5.0)
}
}
}
class datas: ObservableObject
{
#Published var jsonData = [datatype]()
init(location: String) {
let session = URLSession(configuration: .default)
if location == "" {
return
}
let parameter = "location=\(location)"
if location == "" {
return
}
let url = URL(string:"url")!
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
session.dataTask(with:request, completionHandler: {(data, response, error) in
do
{
if data != nil
{
let fetch = try JSONDecoder().decode([datatype].self, from: data!)
DispatchQueue.main.async {
self.jsonData = fetch
print(fetch)
}
}
}
catch
{
print(error.localizedDescription)
}
}).resume()
}
}
In the above code I want to pass in the txt variable into the getData variable or do something like this #ObservedObject var getData = datas(location: txt) . When the SearchBar is updated then txt gets whatever is inserted into the SearchBar .
If I do something like this
#ObservedObject var getData = datas(location: "Me")
Then the list will update and correctly have everything that starts with Me my only issue is getting the SearchBar value inside datas so I don't have to hardcode things . As stated before I need to pass in txt to datas . Any help would be great
You don't need to init the class with that variable. You can just make a function for that and fetch it when ever you need. It could be just once.
class datas: ObservableObject {
#Published var jsonData = [datatype]()
func get(location: String) {
let session = URLSession(configuration: .default)
guard !location.isEmpty else { return }
let parameter = "location=\(location)"
let url = URL(string:"url")!
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
session.dataTask(with:request, completionHandler: {(data, response, error) in
do {
guard data != nil else { return }
let fetch = try JSONDecoder().decode([datatype].self, from: data!)
DispatchQueue.main.async {
self.jsonData = fetch
print(fetch)
}
} catch {
print(error.localizedDescription)
}
}).resume()
}
}

Can't extract var from firebase reading method

How can I get the numberOfMarkers out of the reading method of Firebase in Swift?
if I use the function in the {} this will save and I will be can use it not in the {}?
docRef = Firestore.firestore().document("Markol/Markers")
docRef.getDocument{ (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int ?? 0
}
//Here i want to get the let numberOfMarkers
var markerArrayList = [GMSMarker]()
func makeAMarker(_ Latitude:Double , _ Longitude:Double , _ Title:String,Snippet:String) -> GMSMarker{
let GmMarker = GMSMarker()
GmMarker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees(Latitude), longitude: CLLocationDegrees(Longitude))
GmMarker.title = Title
GmMarker.snippet = Snippet
GmMarker.icon = UIImage(named: "smallStoreIcon")
return GmMarker
}
getDocument is an asynchronous task, so numberOfMarkers is only accessible before the closing }.
Do whatever you want with numberOfMarkers inside the getDocument listener, you may need to refactor your existing code to accommodate this. For example:
docRef = Firestore.firestore().document("Markol/Markers")
docRef.getDocument{ (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int ?? 0
processMarkers(numberOfMarkers, myData)
}
If this approach isn't clear, try posting more of your code in your question so others can help you restructure.
No you can't. Variable/constant is always visible just inside scope where is declared, between curly braces {...}.
What you probably want to do is to get this value to return it or use somewhere else. Don't do it, since getting data from Firestore is asynchronus task, use completion handler instead and return value (or nil if you don’t have value) as completion's parameter when you have it
func call(completion: #escaping (Int?) -> Void) {
...
docRef.getDocument{ docSnapshot, error in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {
completion(nil)
return
}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int
completion(numberOfMarkers)
}
}
then when you need to call it
call { numberOfMarkers in // code inside this closure is called with parameter of type `Int?` when you receive data and call completion from inside `call`
if let number = numberOfMarkers {
... // do something with it
}
}
... here you can use it for next purpose

How does one change the value of a global variable in a function to be used later on?

I am trying to create a weather application, and in order to do that I need to parse out JSON. This works, (which I know because I tested it by printing), but I cannot change the value of the data I get to an already initialized global variable. A similar question was asked before, but even using the provided answers, I was not able to solve my own problem.
The following lines are where I initialize my global variables:
var currentTemperature = 88
var currentTime = 789
And the following Lines are where I parse the JSON and test by printing in my viewDidLoad function:
let url = URL(string: "https://api.darksky.net/forecast/c269e5928cbdadde5e9d4040a5bd4833/42.1784,-87.9979")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("error")
}
else {
if let content = data {
do {
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let jsonCurrently = json["currently"] as? NSDictionary {
if let jsonCurrentTime = jsonCurrently["time"] {
currentTime = jsonCurrentTime as! Int
print(currentTime)
}
if let jsonCurrentTemperature = jsonCurrently["temperature"] {
currentTemperature = jsonCurrentTemperature as! Int
print(currentTemperature)
}
}
} catch {
}
}
}
}
task.resume()
I use the global variables when setting the text of a label in a different class: (however, only the initial value I set to the variable shows up, not the one from the parsed JSON)
let currentTemperatureLabel: UILabel = {
//label of the current temperature
let label = UILabel()
label.text = String(currentTemperature) + "°"
label.textColor = UIColor(red: 150/255, green: 15/255, blue: 15/255, alpha: 0.8)
label.textAlignment = NSTextAlignment.center
label.font = UIFont(name: "Damascus", size: 130)
label.font = UIFont.systemFont(ofSize: 130, weight: UIFontWeightLight)
return label
}()
The JSON example request can be found here: https://darksky.net/dev/docs#api-request-types
No matter what I do, I am not able to use the data from the JSON when I attempt to access the two global variables mentioned before.
var currentTemperature : Double = 88
var currentTime = 789
//...
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://api.darksky.net/forecast/c269e5928cbdadde5e9d4040a5bd4833/42.1784,-87.9979")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("error")
}
else {
if let content = data {
do {
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let jsonCurrently = json["currently"] as? NSDictionary {
if let jsonCurrentTime = jsonCurrently["time"] as? Int {
currentTime = jsonCurrentTime
print(currentTime)
}
if let jsonCurrentTemperature = jsonCurrently["temperature"] as? Double {
currentTemperature = jsonCurrentTemperature
print(currentTemperature)
}
}
} catch {
}
}
}
}
task.resume()
}
I did these edits above to your code:
Changed your currentTemperature to be Double (Look at your JSON response to see what kind of response you get and what kind of data type it can be)
When trying to get "time" and "temperature" added optional wrapping to get the data correctly from the response with correct data type so that when assigning to your variables you wont need to do explicit unwrapping
EDIT:
Updated answer based on your comment about the global variables not being part of the class