SwiftUI + 解码 Yelp API 响应
SwiftUI + Decoding Yelp API Response
我是 Swift 和 JSON 的新手,我会尽我所能描述我想要完成的工作。
我正在尝试访问 Yelp API 服务和 return 并解码 JSON 结果并在列表中显示结果。
我已经成功地能够点击 API 并将结果记录到控制台,但是我无法将结果映射到 UI 元素以在视图中显示.
下面是结果的结构和我试图在其中显示结果的视图。我return在视图加载后从我的加载函数中获取错误。
Data.swift
import SwiftUI
struct BusinessesResponse: Codable {
let restaurants: [RestaurantResponse]
}
struct RestaurantResponse: Codable, Identifiable {
let id: String
var name: String
var coordinates: [longlat]
var is_closed: Bool
var category: String
var imageURL: URL
var url: URL
var review_count: Int
var rating: Double
var display_phone: String
var distance: Double
}
内容视图
import SwiftUI
import YelpAPI
import Combine
import CoreLocation
struct ContentView: View {
@ObservedObject private var locationManager = LocationManager()
@ObservedObject var fetcher = RestaurantFetcher()
var body: some View {
VStack {
List(fetcher.businesses) { restaurant in
VStack (alignment: .leading) {
Text(restaurant.name)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
public class RestaurantFetcher: ObservableObject {
@Published var businesses = [RestaurantResponse]()
init() {
load(latitude: 28.4293403, longitude: -81.6241764)
}
func load(latitude: Double, longitude: Double) {
let apikey = "API-KEY-HERE"
let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)")!
var request = URLRequest(url: url)
request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([RestaurantResponse].self, from: d)
DispatchQueue.main.async {
self.businesses = decodedLists
}
} else {
print("No Data")
}
} catch {
print ("Caught")
}
}.resume()
}
}
JSON 来自 Yelp 的 API
的回应
{
"businesses": [
{
"id": "ZTgp2l3XbADwmOMM5rpWZg",
"alias": "disneys-oak-trail-golf-course-lake-buena-vista",
"name": "Disney's Oak Trail Golf Course",
"image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/G3oE_KJJ53H1iweD-j83yQ/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/disneys-oak-trail-golf-course-lake-buena-vista?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
"review_count": 12,
"categories": [
{
"alias": "golf",
"title": "Golf"
}
],
"rating": 3.5,
"coordinates": {
"latitude": 28.4055855,
"longitude": -81.5956011
},
"transactions": [],
"location": {
"address1": "1950 W Magnolia Palm Dr",
"address2": "",
"address3": "",
"city": "Lake Buena Vista",
"zip_code": "32836",
"country": "US",
"state": "FL",
"display_address": [
"1950 W Magnolia Palm Dr",
"Lake Buena Vista, FL 32836"
]
},
"phone": "+14079394653",
"display_phone": "(407) 939-4653",
"distance": 3845.3340908128034
},
{
"id": "VVF9h1jhhOVXIvxe-MDK8g",
"alias": "panther-lake-golf-course-winter-garden",
"name": "Panther Lake Golf Course",
"image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/ff47f9jXs56s3Cf7obIapA/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/panther-lake-golf-course-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
"review_count": 1,
"categories": [
{
"alias": "hotels",
"title": "Hotels"
},
{
"alias": "golf",
"title": "Golf"
}
],
"rating": 4.0,
"coordinates": {
"latitude": 28.4419223,
"longitude": -81.6303836
},
"transactions": [],
"location": {
"address1": "16301 Phil Ritson Way",
"address2": "",
"address3": "",
"city": "Winter Garden",
"zip_code": "34787",
"country": "US",
"state": "FL",
"display_address": [
"16301 Phil Ritson Way",
"Winter Garden, FL 34787"
]
},
"phone": "+14076562626",
"display_phone": "(407) 656-2626",
"distance": 1620.1533458028462
},
{
"id": "UdqKnhBDg4b04e38qFcjEA",
"alias": "orange-83-pub-and-grill-winter-garden",
"name": "Orange 83 Pub And Grill",
"image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/KjWvn26iBv13GnIUCW7z9Q/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/orange-83-pub-and-grill-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
"review_count": 1,
"categories": [
{
"alias": "pubs",
"title": "Pubs"
}
],
"rating": 4.0,
"coordinates": {
"latitude": 28.4419223,
"longitude": -81.6303836
},
"transactions": [],
"location": {
"address1": "16301 Phil Ritson Way",
"address2": null,
"address3": "Orange County National Golf Center & Lodge",
"city": "Winter Garden",
"zip_code": "34787",
"country": "US",
"state": "FL",
"display_address": [
"16301 Phil Ritson Way",
"Orange County National Golf Center & Lodge",
"Winter Garden, FL 34787"
]
},
"phone": "+14076562626",
"display_phone": "(407) 656-2626",
"distance": 1620.1533458028462
}
],
"total": 3,
"region": {
"center": {
"longitude": -81.6241764,
"latitude": 28.4293403
}
}
}
首先,请注意在您的 JSON 响应的顶层有一个对象 { }
而不是数组 [ ]
.
这意味着你需要解码 BusinessesResponse
而不是 [RestaurantResponse]
:
let response = try JSONDecoder().decode(BusinessesResponse.self, from: d)
self.businesses = response.restaurants
另请注意,您正在尝试解码 restaurants
,并且在 JSON 响应中您有 businesses
。您可以将 BusinessesResponse
中的字段重命名为 restaurants
或者,这可能 更好 ,使用 CodingKeys
:
struct BusinessesResponse: Codable {
enum CodingKeys: String, CodingKey {
case restaurants = "businesses"
}
let restaurants: [RestaurantResponse]
}
另请注意,JSON 响应中不存在 category
和 imageUrl
字段。 JSON 响应中的 categories
字段是 数组 的 objects.
相反你可以这样做:
struct RestaurantResponse: Codable, Identifiable {
enum CodingKeys: String, CodingKey {
case id, name, is_closed, categories, url, review_count, rating, display_phone, distance
case imageURL = "image_url"
}
...
var categories: [RestaurantCategory]
var imageURL: URL
...
}
struct RestaurantCategory: Codable {
var alias: String
var title: String
}
如果您决定使用 CodingKeys
,那么您还可以更改其他变量:is_closed
、review_count
到 camelCase.
或者,如果模型中的所有变量都是 camelCase 等效于 snake_case 中的键 JSON 您可以使用的响应:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
我是 Swift 和 JSON 的新手,我会尽我所能描述我想要完成的工作。
我正在尝试访问 Yelp API 服务和 return 并解码 JSON 结果并在列表中显示结果。
我已经成功地能够点击 API 并将结果记录到控制台,但是我无法将结果映射到 UI 元素以在视图中显示.
下面是结果的结构和我试图在其中显示结果的视图。我return在视图加载后从我的加载函数中获取错误。
Data.swift
import SwiftUI
struct BusinessesResponse: Codable {
let restaurants: [RestaurantResponse]
}
struct RestaurantResponse: Codable, Identifiable {
let id: String
var name: String
var coordinates: [longlat]
var is_closed: Bool
var category: String
var imageURL: URL
var url: URL
var review_count: Int
var rating: Double
var display_phone: String
var distance: Double
}
内容视图
import SwiftUI
import YelpAPI
import Combine
import CoreLocation
struct ContentView: View {
@ObservedObject private var locationManager = LocationManager()
@ObservedObject var fetcher = RestaurantFetcher()
var body: some View {
VStack {
List(fetcher.businesses) { restaurant in
VStack (alignment: .leading) {
Text(restaurant.name)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
public class RestaurantFetcher: ObservableObject {
@Published var businesses = [RestaurantResponse]()
init() {
load(latitude: 28.4293403, longitude: -81.6241764)
}
func load(latitude: Double, longitude: Double) {
let apikey = "API-KEY-HERE"
let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)")!
var request = URLRequest(url: url)
request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([RestaurantResponse].self, from: d)
DispatchQueue.main.async {
self.businesses = decodedLists
}
} else {
print("No Data")
}
} catch {
print ("Caught")
}
}.resume()
}
}
JSON 来自 Yelp 的 API
的回应{
"businesses": [
{
"id": "ZTgp2l3XbADwmOMM5rpWZg",
"alias": "disneys-oak-trail-golf-course-lake-buena-vista",
"name": "Disney's Oak Trail Golf Course",
"image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/G3oE_KJJ53H1iweD-j83yQ/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/disneys-oak-trail-golf-course-lake-buena-vista?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
"review_count": 12,
"categories": [
{
"alias": "golf",
"title": "Golf"
}
],
"rating": 3.5,
"coordinates": {
"latitude": 28.4055855,
"longitude": -81.5956011
},
"transactions": [],
"location": {
"address1": "1950 W Magnolia Palm Dr",
"address2": "",
"address3": "",
"city": "Lake Buena Vista",
"zip_code": "32836",
"country": "US",
"state": "FL",
"display_address": [
"1950 W Magnolia Palm Dr",
"Lake Buena Vista, FL 32836"
]
},
"phone": "+14079394653",
"display_phone": "(407) 939-4653",
"distance": 3845.3340908128034
},
{
"id": "VVF9h1jhhOVXIvxe-MDK8g",
"alias": "panther-lake-golf-course-winter-garden",
"name": "Panther Lake Golf Course",
"image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/ff47f9jXs56s3Cf7obIapA/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/panther-lake-golf-course-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
"review_count": 1,
"categories": [
{
"alias": "hotels",
"title": "Hotels"
},
{
"alias": "golf",
"title": "Golf"
}
],
"rating": 4.0,
"coordinates": {
"latitude": 28.4419223,
"longitude": -81.6303836
},
"transactions": [],
"location": {
"address1": "16301 Phil Ritson Way",
"address2": "",
"address3": "",
"city": "Winter Garden",
"zip_code": "34787",
"country": "US",
"state": "FL",
"display_address": [
"16301 Phil Ritson Way",
"Winter Garden, FL 34787"
]
},
"phone": "+14076562626",
"display_phone": "(407) 656-2626",
"distance": 1620.1533458028462
},
{
"id": "UdqKnhBDg4b04e38qFcjEA",
"alias": "orange-83-pub-and-grill-winter-garden",
"name": "Orange 83 Pub And Grill",
"image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/KjWvn26iBv13GnIUCW7z9Q/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/orange-83-pub-and-grill-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
"review_count": 1,
"categories": [
{
"alias": "pubs",
"title": "Pubs"
}
],
"rating": 4.0,
"coordinates": {
"latitude": 28.4419223,
"longitude": -81.6303836
},
"transactions": [],
"location": {
"address1": "16301 Phil Ritson Way",
"address2": null,
"address3": "Orange County National Golf Center & Lodge",
"city": "Winter Garden",
"zip_code": "34787",
"country": "US",
"state": "FL",
"display_address": [
"16301 Phil Ritson Way",
"Orange County National Golf Center & Lodge",
"Winter Garden, FL 34787"
]
},
"phone": "+14076562626",
"display_phone": "(407) 656-2626",
"distance": 1620.1533458028462
}
],
"total": 3,
"region": {
"center": {
"longitude": -81.6241764,
"latitude": 28.4293403
}
}
}
首先,请注意在您的 JSON 响应的顶层有一个对象 { }
而不是数组 [ ]
.
这意味着你需要解码 BusinessesResponse
而不是 [RestaurantResponse]
:
let response = try JSONDecoder().decode(BusinessesResponse.self, from: d)
self.businesses = response.restaurants
另请注意,您正在尝试解码 restaurants
,并且在 JSON 响应中您有 businesses
。您可以将 BusinessesResponse
中的字段重命名为 restaurants
或者,这可能 更好 ,使用 CodingKeys
:
struct BusinessesResponse: Codable {
enum CodingKeys: String, CodingKey {
case restaurants = "businesses"
}
let restaurants: [RestaurantResponse]
}
另请注意,JSON 响应中不存在 category
和 imageUrl
字段。 JSON 响应中的 categories
字段是 数组 的 objects.
相反你可以这样做:
struct RestaurantResponse: Codable, Identifiable {
enum CodingKeys: String, CodingKey {
case id, name, is_closed, categories, url, review_count, rating, display_phone, distance
case imageURL = "image_url"
}
...
var categories: [RestaurantCategory]
var imageURL: URL
...
}
struct RestaurantCategory: Codable {
var alias: String
var title: String
}
如果您决定使用 CodingKeys
,那么您还可以更改其他变量:is_closed
、review_count
到 camelCase.
或者,如果模型中的所有变量都是 camelCase 等效于 snake_case 中的键 JSON 您可以使用的响应:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase