Hi I am struggling with getting football data api from rapidapi to work in swift ui . here is the code below
The errors is get are "self.team = decodedTeams" Cannot find 'self' in scope"
and in my content view i get for " $network.getTeams"
Value of type 'EnvironmentObject.Wrapper' has no dynamic member 'getTeams' using key path from root type 'Network'
I have set out what i have in 2 pages of my swiftui code below
any help would be appreciated, I am really struggling with this one
// Network.swift
// Football Scores
//
//
import Foundation
class Network: ObservableObject {
#Published var teams: [Team] = []
}
func getTeams() {
let headers = [
"X-RapidAPI-Key": "MY API KEY",
"X-RapidAPI-Host": "api-football-v1.p.rapidapi.com"
]`
let request = NSMutableURLRequest(url: NSURL(string: "https://api-football-v1.p.rapidapi.com/v3/standings?season=2022&league=39")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
DispatchQueue.main.async {
do {
let decodedTeams = try JSONDecoder().decode([Team].self, from: data)
self.team = decodedTeams
} catch let error {
print("Error decoding: ", error)
}
}
}
})
dataTask.resume()
}
and
//
// Team.swift
// Football Scores
//
//
import Foundation
struct Team: Identifiable, Decodable {
var id: Int
var name: String
var logo: String
var points: String
var goaldif: String
}
and
// Football_ScoresApp.swift
// Football Scores
//
import SwiftUI
import Foundation
#main
struct Football_ScoresApp: App {
var network = Network()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(network)
}
}
}
and
import SwiftUI
import CoreData
struct ContentView: View {
#EnvironmentObject var network: Network
var body: some View {
ScrollView {
Text("All teams")
.font(.title).bold()
}
.onAppear {
network.getTeams()
}
VStack(alignment: .leading) {
ForEach(network.teams) { team in
HStack(alignment:.top) {
Text("\(team.id)")
VStack(alignment: .leading) {
Text(team.name)
.bold()
}
}
.frame(width: 300, alignment: .leading)
.padding()
.background(Color(#colorLiteral(red: 0.6667672396, green: 0.7527905703, blue: 1, alpha: 0.2662717301)))
.cornerRadius(20)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Network())
}
}
To display the teams from your api request, first you need to create a set of struct models that
represent the json data that the server is sending. From the server response, you need to
extract the teams information you want to display. Here is my code
that shows how to do it, works well for me, pay attention to the details:
struct ContentView: View {
#StateObject var network = Network() // <-- for testing
var body: some View {
List {
VStack(alignment: .leading) {
ForEach(network.teams) { team in
HStack(alignment:.top) {
Text("\(team.id)")
Text(team.name).bold()
}
.frame(width: 300, alignment: .leading)
.padding()
.background(Color(#colorLiteral(red: 0.6667672396, green: 0.7527905703, blue: 1, alpha: 0.2662717301)))
.cornerRadius(20)
}
}
}
.onAppear {
network.getTeams()
}
}
}
class Network: ObservableObject {
#Published var teams: [Team] = []
func getTeams() {
let token = "your-key" // <--- here your api key
guard let url = URL(string: "https://api-football-v1.p.rapidapi.com/v3/standings?season=2022&league=39") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "X-RapidAPI-Key")
request.setValue("api-football-v1.p.rapidapi.com", forHTTPHeaderField: "X-RapidAPI-Host")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
do {
let results = try JSONDecoder().decode(FootyResponse.self, from: data)
// extract just the teams
for response in results.response {
for stand in response.league.standings {
for league in stand {
self.teams.append(league.team)
}
}
}
} catch {
print(error) // <-- here important
}
}
}.resume()
}
}
// MARK: - FootyResponse
struct FootyResponse: Codable {
let welcomeGet: String
let parameters: Parameters
let errors: [String]
let results: Int
let paging: Paging
let response: [Response]
enum CodingKeys: String, CodingKey {
case welcomeGet = "get"
case parameters, errors, results, paging, response
}
}
// MARK: - Paging
struct Paging: Codable {
let current, total: Int
}
// MARK: - Parameters
struct Parameters: Codable {
let league, season: String
}
// MARK: - Response
struct Response: Codable {
let league: League
}
// MARK: - League
struct League: Codable {
let id: Int
let name: String
let country: String
let logo: String
let flag: String
let season: Int
let standings: [[Standing]]
}
// MARK: - Standing
struct Standing: Codable {
let rank: Int
let team: Team
let points, goalsDiff: Int
let group: String
let form: String
let status: String
let standingDescription: String?
let all, home, away: All
let update: String // <-- Date
enum CodingKeys: String, CodingKey {
case rank, team, points, goalsDiff, group, form, status
case standingDescription = "description"
case all, home, away, update
}
}
// MARK: - All
struct All: Codable {
let played, win, draw, lose: Int
let goals: Goals
}
// MARK: - Goals
struct Goals: Codable {
let goalsFor, against: Int
enum CodingKeys: String, CodingKey {
case goalsFor = "for"
case against
}
}
// MARK: - Team
struct Team: Identifiable, Codable {
let id: Int
let name: String
let logo: String
}
EDIT-1:
to get the points of each team, you need to use the Standing struct. Here is an example code to do that.
struct ContentView: View {
#StateObject var network = Network()
var body: some View {
List {
VStack(alignment: .leading) {
ForEach(network.stand) { stand in // <-- here
HStack(alignment:.top) {
Text(stand.team.name).bold() // <-- here
Text("\(stand.points) points") // <-- here
}
.frame(width: 300, alignment: .leading)
.padding()
.background(Color(#colorLiteral(red: 0.6667672396, green: 0.7527905703, blue: 1, alpha: 0.2662717301)))
.cornerRadius(20)
}
}
}
.onAppear {
network.getTeams()
}
}
}
class Network: ObservableObject {
#Published var teams: [Team] = []
#Published var stand: [Standing] = [] // <-- here
func getTeams() {
let token = "your-key" // <--- here your api key
guard let url = URL(string: "https://api-football-v1.p.rapidapi.com/v3/standings?season=2022&league=39") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "X-RapidAPI-Key")
request.setValue("api-football-v1.p.rapidapi.com", forHTTPHeaderField: "X-RapidAPI-Host")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
do {
let results = try JSONDecoder().decode(FootyResponse.self, from: data)
// extract the teams and the standings
results.response.forEach{ response in
response.league.standings.forEach{ stand in
self.stand = stand // <--- here
stand.forEach{ league in
self.teams.append(league.team)
}
}
}
} catch {
print(error) // <-- here important
}
}
}.resume()
}
}
// MARK: - Standing
struct Standing: Identifiable, Codable {
let id = UUID() // <-- here
let rank: Int
let team: Team
let points, goalsDiff: Int
let group: String
let form: String
let status: String
let standingDescription: String?
let all, home, away: All
let update: String // <-- Date
enum CodingKeys: String, CodingKey {
case rank, team, points, goalsDiff, group, form, status
case standingDescription = "description"
case all, home, away, update
}
}
Related
Hey all I have been trying to fix this issue for 2 days now and just can't seem to get what I am lookinhg for.
My working code:
struct User: Decodable {
let name: String
let description: String
let isOn: Bool
}
struct ContentView: View {
#State var users = [User]()
var body: some View {
List{
ForEach(users, id: \.name) { item in
HStack {
Toggle(isOn: .constant(true)) {
Label {
Text(item.description)
} icon: {
//list.img
}
}
}.padding(7)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://-----.---/jsontest.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, error in
if let data = data {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let decodedResponse = try?
decoder.decode([User].self, from: data) {
DispatchQueue.main.async {
self.users = decodedResponse
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
the Json its pulling looks like this:
{
"name": "J",
"description": "Echo Show",
"isOn": true
},...
Like I said above this code works as-is. Where the problem I am having comes into play is the toggle part. It doesn't seem to be happens with the item.isOn for seeing if its true or false. The only thing it likes is the .constant(). Naturally this will not work for my case due to me needing to change the toggles to either true or false.
If it's anything other than .constant it has the error of:
Cannot convert value of type 'Bool' to expected argument type 'Binding'
I can perhaps if I do this
#State var ison = false
var body: some View {
List{
ForEach(users, id: \.name) { item in
HStack {
Toggle(isOn: $ison)) {
That seems to take that error away.... but how can it get the value thats looping for isOn that way?
What am I missing here?
try this approach, making isOn a var in User, and using bindings, the $ in the ForEach and Toggle, works for me:
struct User: Decodable {
let name: String
let description: String
var isOn: Bool // <-- here
}
struct ContentView: View {
#State var users = [User]()
var body: some View {
List{
ForEach($users, id: \.name) { $item in // <-- here
HStack {
Toggle(isOn: $item.isOn) { // <-- here
Label {
Text(item.description)
} icon: {
//list.img
}
}
}.padding(7)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://-----.---/jsontest.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, error in
if let data = data {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let decodedResponse = try?
decoder.decode([User].self, from: data) {
DispatchQueue.main.async {
self.users = decodedResponse
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
Hi following is my code for the News Reader app exercise in swiftui for dummies, I'm getting an error "Cannot find Resize in Scope" any help will be appreciated
Code:
"
import SwiftUI
import URLImage
import URLImageStore
struct Result: Codable {
var articles: [Article]
}
struct Article: Codable {
var url: String
var title: String
var description: String?
var urlToImage: String?
}
struct ContentView: View {
private let url = "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=49d5bfa113c34ec0af781fab38395996"
#State private var articles = [Article]()
func fetchData() {
guard let url = URL(string: url) else {
print("URL is Not Valid")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data {
if let decodedResult = try?
JSONDecoder().decode(
Result.self, from: data) {
DispatchQueue.main.async {
self.articles = decodedResult.articles
}
return
}
}
print ("Error: \(error?.localizedDescription ?? "Unknown Error")")
}.resume()
}
var body: some View {
List(articles, id: \.url) { item in
HStack(alignment: .top) {
U**RLImage(
(( URL(string:item.urlToImage ?? "https://picsum.photos/100")
?? nil
)!),
delay: 0.25,
processors:
[Resize(size: CGSize(width: 100.0, height: 100.0), scale: UIScreen.main.scale)],
content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fit)
.clipped()
}
).frame(width: 100.0, height: 100.0)**
VStack(alignment: .leading) {
Text(item.title)
.font(.headline)
Text(item.description ?? "")
.font(.footnote)
}
}
}.onAppear(perform: fetchData)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
"
Please help in resolving my problem the problem is I'm getting cannot find "resize" in scope. so the question is: how can I mitigate this problem?
the error is telling you that you are missing the class or struct called Resize. Find where that is in the excercise and put it in a file in your project or in your code above. Most likely at the error is at:
"processors:
[Resize(size: ....." // <--- here
Note there is something strange about the name " U**RLImage". It should probably be "URLImage"
I want to add an object to a local server and then see the updated list in my view.
I have a ViewModelClass that handles the REST requests:
class CupcakeViewModel : ObservableObject {
let objectWillChange = PassthroughSubject<CupcakeViewModel,Never>()
init() {
get()
}
var cupcakes : [Cupcake] = [Cupcake]() {
didSet {
objectWillChange.send(self)
}
}
static let url = URL(string: "http://localhost:1337/cupcakes")!
func get() {
URLSession.shared.dataTask(with: CupcakeViewModel.url) { (data, response, error) in
if let data = data {
do {
print(data)
let cupcakes = try JSONDecoder().decode([Cupcake].self, from: data)
DispatchQueue.main.async {
self.cupcakes = cupcakes
}
} catch {
print("ERROR")
}
}
}.resume()
}
func post(cupcake : Cupcake) {
AF.request(CupcakeViewModel.url, method: .post, parameters: cupcake, encoder: JSONParameterEncoder.default).responseDecodable { (response: DataResponse<Cupcake, AFError>) in
if let value = response.value {
DispatchQueue.main.async {
self.cupcakes.append(value)
}
}
}
}
}
and in my MainView I have:
struct CupcakesView: View {
#ObservedObject var VM = CupcakeViewModel()
#State var showed = false
var body: some View {
NavigationView {
List(VM.cupcakes,id:\.self) {cupcake in
Text(cupcake.gusto)
}.padding(.top,1)
.sheet(isPresented: $showed, content: {
AggiungiCupcake(showed: self.$showed)
})
.navigationBarItems(trailing:
Button(action: {
self.showed = true
}) {
Text("ADD")
}
)
}
}
}
struct AggiungiCupcake : View {
#State var gusto = ""
#State var prezzo : Float = 0
#Binding var showed : Bool
#ObservedObject var VM = CupcakeViewModel()
var body : some View {
VStack {
TextField("Gusto", text: $gusto)
TextField("Prezzo", value: $prezzo, formatter: NumberFormatter())
Button(action: {
let c = Cupcake(gusto: self.gusto, prezzo: self.prezzo)
self.VM.post(cupcake: c)
self.showed = false
}) {
Text("ADD")
}
}.padding(30)
}
}
Both the get and the post requests go fine and on my server everything is updated, but my view does not add the new Object in the list. I use AlamoFire (AF) for the post request.
Anyone can help?
I think the issue is probably that you are calling send on your publisher in a didSet observer, rather than in a willSet observer. As the publisher name implies, you need to do this before your changes are made.
Try changing:
didSet {
objectWillChange.send(self)
}
to this:
willSet {
objectWillChange.send(self)
}
I trying to learn the new SwiftUI coding technique. I would like to click a button that will add elements to an array that is a #State variable. The button is the buttonclick function. The array is the push_group_row / push_group_array. I get an error in the append statement.
Eventually the buttonclick will access a database to build an array with more row, but for now I just trying to add one row.
Code:
import SwiftUI
import Combine
var gordon: String = "xxxxxx"
struct Result: Codable {
let trackId: Int
let trackName: String
let collectionName: String
}
struct Response: Codable {
var results: [Result]
}
struct Pokemon: Identifiable {
let id: Int
let name: String
let type: String
let color: Color
}
struct push_group_row {
let id: Int
let code: String
let title: String
}
struct ContentView: View
{
#State private var results = [Result]()
#State var pokemonList = [
Pokemon(id: 0, name: "Charmander", type: "Fire", color: .red),
Pokemon(id: 1, name: "Squirtle", type: "Water", color: .blue),
Pokemon(id: 2, name: "Bulbasaur", type: "Grass", color: .green),
Pokemon(id: 3, name: "Pikachu", type: "Electric", color: .yellow),]
#State var push_group_array = [push_group_row(id: 0, code: "code12", title: "POAFire")]
var body: some View
{
NavigationView
{
VStack(alignment: . leading){
Button(action: {
// What to perform
self.buttonclick()
}) {
// How the button looks like
Text("clickme")
.background(Color.purple)
.foregroundColor(.white)
}
List(results, id: \.trackId)
{item in
NavigationLink(destination: DetailView(lm: String(item.trackId)))
{
VStack(alignment: .leading)
{
Text(String(item.trackId))
Text(item.trackName)
.font(.headline)
Text(item.collectionName)
Text(gordon)
}
}
}
List(self.pokemonList)
{ pokemon in
HStack
{
Text(pokemon.name)
Text(pokemon.type).foregroundColor(pokemon.color)
}
}
List(push_group_array, id: \.id)
{ pg_item in
HStack
{
Text(String(pg_item.id))
Text(pg_item.code)
}
}
.onAppear(perform: self.loaddata)
}
.navigationBarTitle("x")
.navigationBarItems(
trailing: Button(action: addPokemon, label: { Text("Add") }))
Spacer()
}
}
func addPokemon() {
if let randomPokemon = pokemonList.randomElement() {
pokemonList.append(randomPokemon)
}
}
// *************************** below is the add arrat code
func buttonclick() {
let newCode = "First"
let newTitle = "Second"
push_group_array.append(id: 1, code: newCode, title: newTitle)
}
func loaddata()
{
print("loaddata")
guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song")
else
{
print("Invalid URL")
return
}
var urlData: NSData?
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data
{
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
{
DispatchQueue.main.async
{
urlData = data as NSData?
self.results = decodedResponse.results
print(self.results)
print(urlData ?? "urlData_Defaultvalue")
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You need to push the object rather than 3 values
push_group_array.append(push_group_row(id: 1, code: newCode, title: newTitle))
I created a TabView with two tabs. One is Home and the other loads text and an image from NASA pic of the day API. When I change to the NASA pic of the day, I see "Loading data" until the data loads. Once the data is loaded, for some reason the tab switches back to the "Home" tab. After this bug happens, I can switch back and forth between the two tabs normally and everything is loaded. Why does the tab get switched back to the home tab? Thank you!!
APIImageView Code:
import SwiftUI
struct ApiImageView: View {
#ObservedObject var apiImage = ApiImage()
var body: some View {
Group {
if apiImage.dataHasLoaded {
VStack {
Text(apiImage.title!)
.font(.largeTitle)
Image(uiImage: apiImage.image!).resizable()
.cornerRadius(10)
.padding()
ScrollView(.vertical, showsIndicators: false) {
Text(apiImage.explanation!)
.font(.subheadline)
.padding()
}
}
} else {
Text("Loading Data")
}
}.onAppear {
self.apiImage.loadImageFromApi(urlString: "https://api.nasa.gov/planetary/apod?api_key=eaRYg7fgTemadUv1bQawGRqCWBgktMjolYwiRrHK")
}
}
}
struct ApiImageView_Previews: PreviewProvider {
static var previews: some View {
ApiImageView()
}
}
APIImage Code:
import SwiftUI
class ApiImage: ObservableObject {
#Published var dataHasLoaded = false
#Published var image: UIImage? = nil
#Published var title: String? = nil
#Published var explanation: String? = nil
}
extension ApiImage {
func loadImageFromApi(urlString: String) {
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: parseJsonObject)
task.resume()
}
func parseJsonObject(data: Data?, urlResponse: URLResponse?, error: Error?) {
guard error == nil else {
print("\(error!)")
return
}
guard let content = data else {
print("No data")
return
}
let json = try! JSONSerialization.jsonObject(with: content)
let jsonmap = json as! [String : Any]
let titleText = jsonmap["title"] as! String
let explanationText = jsonmap["explanation"] as! String
let urlString = jsonmap["url"] as! String
print("\(urlString)")
print("\(titleText)")
print("\(explanationText)")
DispatchQueue.main.async {
self.title = titleText
self.explanation = explanationText
}
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: setImageFromData)
task.resume()
}
func setImageFromData(data: Data?, urlResponse: URLResponse?, error: Error?) {
guard error == nil else {
print("\(error!)")
return
}
guard let content = data else {
print("No data")
return
}
DispatchQueue.main.async {
self.image = UIImage(data: content)
self.dataHasLoaded = true
}
}
}
MainTabView Code:
import SwiftUI
struct MainTabView: View {
var body: some View {
TabView {
CategoryHome()
.tabItem {
Image(systemName: "house.fill")
Text("Landmarks")
.tag(0)
}
ApiImageView()
.tabItem {
Image(systemName: "flame.fill")
Text("NASA Pic")
//.tag(1)
}
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Maybe not directly a solution, but also important: it seems that the MainTabView is not entirely correct (the .tag() should be outside the .tabItem closure). This would be a correct version:
import SwiftUI
struct MainTabView: View {
var body: some View {
TabView {
CategoryHome()
.tabItem {
Image(systemName: "house.fill")
Text("Landmarks")
}.tag(0)
ApiImageView()
.tabItem {
Image(systemName: "flame.fill")
Text("NASA Pic")
}.tag(1)
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Maybe this is already the solution; if not, I hope it is still helpful! :)