如何设置 SwiftUI 应用程序以将输入值作为参数传递给 URL 字符串
How to set up SwiftUI app to pass inputted value as parameter into URL string
我正在尝试构建一个基本的 SwiftUI 天气应用程序。该应用程序允许用户使用 OpenWeatherMap API 按城市名称搜索天气。我将文本字段中输入的城市名称配置为注入 name: "" in WeatherModel,在 viewModel 的 fetchWeather() 函数内。然后我将 OpenWeatherMap URL 字符串配置为将 searchedCity.name 作为参数接收(参见下面的 viewModel)。此设置似乎工作正常,因为我能够按城市名称搜索天气。但是,我想就将 searchCity.name 直接传递到 URL (在 viewModel 中)的做法是否正确征求反馈。关于:
let searchedCity = WeatherModel(...
...我不确定如何处理 WeatherModel 实例中的 CurrentWeather 和 WeatherInfo。由于我只使用“searchedCity”将城市名称传递到 URL,因此“CurrentWeather.init(temp: 123.00)”和“weather: [WeatherInfo.init(description :"")]" 可以设置吗?为 temp 和 description 实现值是否正确,例如 123 和 ""?
下面是我的完整代码:
内容视图
import SwiftUI
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
@StateObject var viewModel = WeatherViewModel()
// @State private var textField = ""
var body: some View {
NavigationView {
VStack {
TextField("Enter City Name", text: $viewModel.enterCityName).textFieldStyle(.roundedBorder)
Button(action: {
viewModel.fetchWeather()
viewModel.enterCityName = ""
}, label: {
Text("Search")
.padding(10)
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
})
Text(viewModel.title)
.font(.system(size: 32))
Text(viewModel.temp)
.font(.system(size: 44))
Text(viewModel.descriptionText)
.font(.system(size: 24))
Spacer()
}
.navigationTitle("Weather MVVM")
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
型号
import Foundation
// Data, Model should mirror the JSON layout
//Codable is the property needed to convert JSON into a struct
struct WeatherModel: Codable {
let name: String
let main: CurrentWeather
let weather: [WeatherInfo]
}
struct CurrentWeather: Codable {
let temp: Float
}
struct WeatherInfo: Codable {
let description: String
}
视图模型
import Foundation
class WeatherViewModel: ObservableObject {
//everytime these properties are updated, any view holding onto an instance of this viewModel will go ahead and updated the respective UI
@Published var title: String = "-"
@Published var temp: String = "-"
@Published var descriptionText: String = "-"
@Published var enterCityName: String = ""
init() {
fetchWeather()
}
func fetchWeather() {
let searchedCity = WeatherModel(name: enterCityName, main: CurrentWeather.init(temp: 123.00), weather: [WeatherInfo.init(description: "")])
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(searchedCity.name)&units=imperial&appid=<myAPIKey>") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
// get data
guard let data = data, error == nil else {
return
}
//convert data to model
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.title = model.name
self.temp = "\(model.main.temp)"
self.descriptionText = model.weather.first?.description ?? "No Description"
}
}
catch {
print(error)
}
}
task.resume()
}
}
I want to seek feedback as to whether or not the practice of passing searchCity.name directly into the URL (in the viewModel) is correct.e
您应该始终避免将假值传递给 object/class,例如 Int(123)。
相反,您应该使用可空结构或 类.
我认为不需要创建一个完整的 WeatherModel 实例来从中读取一个 属性,一个 属性 您已经在视图模型的 enterCityName 属性。只需使用视图模型的 enterCityName 属性 即可。
有很多方法可以完成您的要求,下面的代码只是一种方法。由于您只需要城市名称即可获得结果,因此只需在 url 字符串中使用城市名称即可。此外,在 WeatherViewModel
中使用 WeatherModel
可避免将数据复制到各种中间变量中。
PS:不要 post 你的秘密 appid 密钥在你的 url.
import Foundation
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@StateObject var viewModel = WeatherViewModel()
@State private var cityName = "" // <-- use this to get the city name
var body: some View {
NavigationView {
VStack {
TextField("Enter City Name", text: $cityName).textFieldStyle(.roundedBorder)
Button(action: {
viewModel.fetchWeather(for: cityName) // <-- let the model fetch the results
cityName = ""
}, label: {
Text("Search")
.padding(10)
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
})
// --- display the results ---
Text(viewModel.cityWeather.name).font(.system(size: 32))
Text("\(viewModel.cityWeather.main.temp)").font(.system(size: 44))
Text(viewModel.cityWeather.firstWeatherInfo()).font(.system(size: 24))
Spacer()
}
.navigationTitle("Weather MVVM")
}.navigationViewStyle(.stack)
}
}
class WeatherViewModel: ObservableObject {
// use your WeatherModel that you get from the fetch results
@Published var cityWeather: WeatherModel = WeatherModel()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=YOURKEY") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityWeather = model
}
}
catch {
print(error) // <-- need to deal with errors here
}
}
task.resume()
}
}
struct WeatherModel: Codable {
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Float = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}
我正在尝试构建一个基本的 SwiftUI 天气应用程序。该应用程序允许用户使用 OpenWeatherMap API 按城市名称搜索天气。我将文本字段中输入的城市名称配置为注入 name: "" in WeatherModel,在 viewModel 的 fetchWeather() 函数内。然后我将 OpenWeatherMap URL 字符串配置为将 searchedCity.name 作为参数接收(参见下面的 viewModel)。此设置似乎工作正常,因为我能够按城市名称搜索天气。但是,我想就将 searchCity.name 直接传递到 URL (在 viewModel 中)的做法是否正确征求反馈。关于:
let searchedCity = WeatherModel(...
...我不确定如何处理 WeatherModel 实例中的 CurrentWeather 和 WeatherInfo。由于我只使用“searchedCity”将城市名称传递到 URL,因此“CurrentWeather.init(temp: 123.00)”和“weather: [WeatherInfo.init(description :"")]" 可以设置吗?为 temp 和 description 实现值是否正确,例如 123 和 ""?
下面是我的完整代码:
内容视图
import SwiftUI
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
@StateObject var viewModel = WeatherViewModel()
// @State private var textField = ""
var body: some View {
NavigationView {
VStack {
TextField("Enter City Name", text: $viewModel.enterCityName).textFieldStyle(.roundedBorder)
Button(action: {
viewModel.fetchWeather()
viewModel.enterCityName = ""
}, label: {
Text("Search")
.padding(10)
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
})
Text(viewModel.title)
.font(.system(size: 32))
Text(viewModel.temp)
.font(.system(size: 44))
Text(viewModel.descriptionText)
.font(.system(size: 24))
Spacer()
}
.navigationTitle("Weather MVVM")
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
型号
import Foundation
// Data, Model should mirror the JSON layout
//Codable is the property needed to convert JSON into a struct
struct WeatherModel: Codable {
let name: String
let main: CurrentWeather
let weather: [WeatherInfo]
}
struct CurrentWeather: Codable {
let temp: Float
}
struct WeatherInfo: Codable {
let description: String
}
视图模型
import Foundation
class WeatherViewModel: ObservableObject {
//everytime these properties are updated, any view holding onto an instance of this viewModel will go ahead and updated the respective UI
@Published var title: String = "-"
@Published var temp: String = "-"
@Published var descriptionText: String = "-"
@Published var enterCityName: String = ""
init() {
fetchWeather()
}
func fetchWeather() {
let searchedCity = WeatherModel(name: enterCityName, main: CurrentWeather.init(temp: 123.00), weather: [WeatherInfo.init(description: "")])
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(searchedCity.name)&units=imperial&appid=<myAPIKey>") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
// get data
guard let data = data, error == nil else {
return
}
//convert data to model
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.title = model.name
self.temp = "\(model.main.temp)"
self.descriptionText = model.weather.first?.description ?? "No Description"
}
}
catch {
print(error)
}
}
task.resume()
}
}
I want to seek feedback as to whether or not the practice of passing searchCity.name directly into the URL (in the viewModel) is correct.e
您应该始终避免将假值传递给 object/class,例如 Int(123)。 相反,您应该使用可空结构或 类.
我认为不需要创建一个完整的 WeatherModel 实例来从中读取一个 属性,一个 属性 您已经在视图模型的 enterCityName 属性。只需使用视图模型的 enterCityName 属性 即可。
有很多方法可以完成您的要求,下面的代码只是一种方法。由于您只需要城市名称即可获得结果,因此只需在 url 字符串中使用城市名称即可。此外,在 WeatherViewModel
中使用 WeatherModel
可避免将数据复制到各种中间变量中。
PS:不要 post 你的秘密 appid 密钥在你的 url.
import Foundation
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@StateObject var viewModel = WeatherViewModel()
@State private var cityName = "" // <-- use this to get the city name
var body: some View {
NavigationView {
VStack {
TextField("Enter City Name", text: $cityName).textFieldStyle(.roundedBorder)
Button(action: {
viewModel.fetchWeather(for: cityName) // <-- let the model fetch the results
cityName = ""
}, label: {
Text("Search")
.padding(10)
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
})
// --- display the results ---
Text(viewModel.cityWeather.name).font(.system(size: 32))
Text("\(viewModel.cityWeather.main.temp)").font(.system(size: 44))
Text(viewModel.cityWeather.firstWeatherInfo()).font(.system(size: 24))
Spacer()
}
.navigationTitle("Weather MVVM")
}.navigationViewStyle(.stack)
}
}
class WeatherViewModel: ObservableObject {
// use your WeatherModel that you get from the fetch results
@Published var cityWeather: WeatherModel = WeatherModel()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=YOURKEY") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityWeather = model
}
}
catch {
print(error) // <-- need to deal with errors here
}
}
task.resume()
}
}
struct WeatherModel: Codable {
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Float = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}