I have a weather app that uses the OpenWeatherMap API to collect its weather data. Everything works properly in the app, but I'm not sure exactly how to pass some of the information over to a widget.
Here is the weather data struct from the app:
struct WeatherData: Codable {
let name: String
let main: Main
let weather: [Weather]
let wind: Wind
let visibility: Int
let sys: Sys
let timezone: Int
let coord: Coord
}
struct Main: Codable {
let temp: Double
let pressure: Int
let feels_like: Double
let temp_min: Double
let temp_max: Double
let humidity: Int
}
struct Weather: Codable {
let id: Int
}
struct Wind: Codable {
let speed: Double
}
struct Sys: Codable {
let sunrise: Double
let sunset: Double
let country: String
}
struct Coord: Codable {
let lon: Double
let lat: Double
}
I understand that basics of widget timeline entries, but I'm not sure how it would work with a struct referencing another struct. Like I would want temperature, but that is within the Main struct instead of just WeatherData.
struct WeatherEntry: TimelineEntry {
let date: Date
let name: String
let temperature: Main
}
If I have the timeline entry set up like this, then all the instances of the entry in the timeline provider would want an init for every constant in Main, not just temp. I feel like I'm missing a step.
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> WeatherEntry {
WeatherEntry(date: Date(), name: "", temperature: Main(temp: <#T##Double#>, pressure: <#T##Int#>, feels_like: <#T##Double#>, temp_min: <#T##Double#>, temp_max: <#T##Double#>, humidity: <#T##Int#>))
}
Also a potentially separate issue, I have both celsius and fahrenheit temperatures that I would want to pass through from functions in a WeatherModel file from the JSON to see both at the same time. Would I be able to pass those through the same way?
struct WeatherModel {
let conditionId: Int
let cityName: String
let temperature: Double
let feels_like: Double
let visibility: Int
let humidity: Int
let wind: Double
let sunrise: Double
let sunset: Double
let timezone: Int
let country: String
let latitude: Double
let longitude: Double
var temperatureString: String {
return String (format: "%.0f", temperature)
}
}
struct WeatherModelFahrenheit {
let conditionId: Int
let cityName: String
let temperatureFahrenheit: Double
let feels_likeFahrenheit: Double
let visibilityImperial: Int
let windImperial: Double
var temperatureStringFahrenheit: String {
return String (format: "%.0f", temperatureFahrenheit)
}
}
If possible, I'd rather pass through the two temperature strings instead of the temperature from Main, but I feel like these are two completely different procedures. If I can accomplish that without having to rewrite any of the app code and just the widget code, that would be preferable, although I'm not sure if that would work that way. I've looked into App Groups for sharing information but I'm not sure if that would be better (or work) for what I'm trying to do.
Hopefully what I want to do is understandable from all that.
Related
I've been stuck trying to debug this for a while so i thought i would ask for some advice as i've got to the point where i've really confused myself. I'm trying to get data from a local api that i've created and use mapkit to display the events i get from my api on the map.
In my apps entry point i have this error "Type CommunityEventsApp does not conform to protocol 'App', it appears on the same line as struct CommunityEventsApp: App. I've trying adding an initialiser above my body in this file as this is what the error suggested as the fix however this didn't resolve the error. I have no other errors in my app. This is the code:
struct CommunityEventsApp: App {
#StateObject var viewModel: ContentViewModel
var event: Event
var body: some Scene {
WindowGroup {
TabView {
//rest of the tab view code
}
.environmentObject(viewModel)
}
}
}
I'm trying to get data from a local api i've made here in my ContentViewModel:
var eventsList: [Event]
var primary: Event
init() {
self.eventsList = []
self.primary = eventsList[0]
}
func getEvents() async throws {
guard let url = URL(string: "http://localhost:5172/events") else {fatalError("Missing URL")}
let urlRequest = URLRequest(url:url)
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data")}
eventsList = try JSONDecoder().decode([Event].self, from:data)
print("Async decodedEvent", eventsList)
}
}
This is my Event struct
struct Event: Decodable, Identifiable {
let id: String
let title: String
let date: String
let time: String
let location: String
let latitude: Double
let longitude: Double
let price: Double
let description: String
let link: String?
let imageUrl: String
init(id: String,
title: String,
date: String,
time: String,
location: String,
latitude: Double,
longitude: Double,
price: Double,
description: String,
link: String,
imageUrl: String) {
self.id = id
self.title = title
self.date = date
self.time = time
self.location = location
self.latitude = latitude
self.longitude = longitude
self.price = price
self.description = description
self.link = link
self.imageUrl = imageUrl
}
}
I call getEvents inside the onAppear in the ContentView:
let event: Event
let viewModel: ContentViewModel
var body: some View {
// UI formatting here
.onAppear {
Task {
do {
try await viewModel.getEvents()
} catch {
print("Error", error)
}
}
}
}
.navigationTitle("Event Information")
}
}
}
MapView where i use my api data to display events on the map:
#EnvironmentObject var events: ContentViewModel
#State var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 53.483959, longitude: -2.244644),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
var body: some View {
Map(coordinateRegion: $region,
annotationItems: events.eventsList) {
event in
MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: event.latitude, longitude: event.longitude)) {
NavigationLink(destination: ContentView(event: event, viewModel: ContentViewModel())) {
Image(systemName: "pin.fill")
.onHover { hover in
print(event.title)
}
}
}
}
.navigationTitle("Events Near You")
}
}
I don't have anyone else that i can ask for help so any help or information would be greatly appreciated! I'm still a beginner with Swift development and don't really feel comfortable with it yet. I'm using xcode version 13 and swift version 5
Your properties need default values.
The App struct is the entry point in your application. So every property needs to be properly initialized.
Especially your #StateObject var.
I am trying to read a plist and having it displayed as a List on SwiftUI. It compiles with no errors or warnings, but nothing gets displayed. I am not sure what I am doing wrong or the mistake I am making here. I've tried multiple things, but I still get a blank display.
import Foundation
struct PresidentModel: Decodable{
var Name: String
var Number: Int
var StartDate: String
var EndDate: String
var Nickname: String
var PoliticalParty: String
enum CodingKeys: String, CodingKey{
case Name = "Name"
case Number = "Number"
case StartDate = "Start Date"
case EndDate = "End Date"
case Nickname = "Nickname"
case PoliticalParty = "Political Party"
}
}
class PresidentViewModel: ObservableObject{
#Published var PresArray: [PresidentModel] = []
#Published var name: String = ""
#Published var number: Int = 0
#Published var startDate: String = ""
#Published var endDate: String = ""
#Published var nickname: String = ""
#Published var politicalParty: String = ""
func loadProperityListData(){
guard let path = Bundle.main.path(forResource: "presidents", ofType: "plist"), let xml = FileManager.default.contents(atPath: path) else {
fatalError("Unable to access property list states.plist")
}
do{
PresArray = try PropertyListDecoder().decode([PresidentModel].self, from: xml)
name = PresArray[0].Name
number = PresArray[0].Number
startDate = PresArray[0].StartDate
endDate = PresArray[0].EndDate
nickname = PresArray[0].Nickname
politicalParty = PresArray[0].PoliticalParty
}
catch {
fatalError("Unable to decode property list states.plist")
}
}//func
}//class
Where the plist will be displayed as a List :
import SwiftUI
struct ContentView: View {
let presidents = PresidentViewModel()
var body: some View {
List(presidents.PresArray.indices, id: \.self){ president in
Text(presidents.PresArray[].Name)
}
}//Body
}//View
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
First of all please conform to the naming convention and declare the struct member names with starting lowercase letters and add an id property
struct PresidentModel: Decodable, Identifiable {
let id = UUID()
let name: String
let number: Int
let startDate: String
let endDate: String
let nickname: String
let politicalParty: String
private enum CodingKeys: String, CodingKey {
case name = "Name"
case number = "Number"
case startDate = "Start Date"
case endDate = "End Date"
case nickname = "Nickname"
case politicalParty = "Political Party"
}
}
The main problem is that you don't load the data, a good place is the init method of the observable class. The properties are not needed because the array contains all data
class PresidentViewModel: ObservableObject{
#Published var presArray: [PresidentModel] = []
init() {
loadProperityListData()
}
func loadProperityListData(){
guard let url = Bundle.main.url(forResource: "presidents", withExtension: "plist"),
let data = try? Data(contentsOf: url) else {
fatalError("Unable to access property list states.plist")
}
do {
presArray = try PropertyListDecoder().decode([PresidentModel].self, from: data)
} catch {
print(error)
presArray = []
}
}//func
}//class
The second main problem is that PresidentViewModel is not declared as #StateObject. And due to the added id property you get rid of dealing with indices and specifying id: \.self
import SwiftUI
struct ContentView: View {
#StateObject var model = PresidentViewModel()
var body: some View {
List(model.presArray) { president in
Text(president.name)
}
}//Body
}//View
rookie here, this may be a simple question but lets say I have a struct that looks like this:
struct User: Codable, Identifiable {
let id: String
let firstname: String
let lastname: String
let age: Int
}
This is the basic user model, but when i create a new user i basically need the same struct with no id:
struct UserCreate: Codable {
let firstname: String
let lastname: String
let age: Int
}
Seems quite verbose to create two structs. Is there a better pattern for this sort of thing?
Thanks
maybe this is what you want?
struct UserWithID: Codable, Identifiable {
let id: String
struct User: Codable {
let firstname: String
let lastname: String
let age: Int
}
}
You can set id as an optional and use other object in User
struct User: Codable, Identifiable {
let id: String?
let userCreate: UserCreate
}
struct UserCreate: Codable {
let firstname: String
let lastname: String
let age: Int
}
You can just set id as an optional and use this object
struct User: Codable, Identifiable {
let id: String?
let firstname: String
let lastname: String
let age: Int
}
Just give a default value for id.
let id = UUID().uuidString
So I have a user JSON structure that goes like this:
- results: {
meta: {}
users: []
},
- status:
I want to get the user so the User model I implement to get the JSON is like this:
struct Response: Decodable {
let results: Result
let status: Int
}
struct Result: Decodable {
let meta: Meta
let users: [User]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
It is working, but when I have another JSON that the structure is similar, let say like this
- results: {
meta: {}
rooms: []
},
- status:
And when I create Room model, with another struct Response on it, it will cause error because it is duplicated declaration.
Is it possible to reuse struct in Swift? Or is there any convenient way to do this?
Thanks
You could use generics.
struct Response<T: Decodable>: Decodable {
let results: Result<T>
let status: Int
}
struct Result<T: Decodable>: Decodable {
let meta: Meta
let objects: [T]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
let userResponse: Response<User>?
You can try to use generics in the following way:
struct Response<ResultType> : Decodable where ResultType : Decodable {
let results: ResultType
let status: Int
}
and then use that struct via:
struct Result: Decodable {
let something: String
}
let decoder = JSONDecoder()
let data = "{\"status\":123,\"results\":{\"something\":\"hi\"}}".data(using: .utf8)!
let value = try! decoder.decode(Response<Result>.self, from: data) // will be of type Response<Result>
You have one option is to reuse Meta and Status
If you use sm
For user You can replace name
struct UserResult: Decodable {
let meta: Meta
let users: [User]
}
struct UserResponse: Decodable {
let results: UserResult
let status: Int
}
and for Room
struct RoomResult: Decodable {
let meta: Meta
let users: [Room]
}
struct RoomResponse: Decodable {
let results: RoomResult
let status: Int
}
Depending on your needs, you can use generics to compose the whole struct.
Start with your data models
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
struct Room: Decodable {
let id: Int
let name: String
}
The goal is to get nice composed types.
typealias UserResponse = Response<Result<User, Meta>>
typealias RoomResponse = Response<Result<Room, Meta>>
The generics types to build from
struct Response<ResultType: Decodable>: Decodable {
let results: ResultType
let status: Int
}
struct Result<ItemType: Decodable, MetaType: Decodable>: Decodable {
let meta: MetaType
let items: [ItemType]
}
Even Meta is a separate part of the composition.
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
Now lets say we need a custom Meta for the Room response
struct PagedMeta: Decodable {
let current_page: Int
let page_count: Int
}
Here is the new type
typealias RoomPagedResponse = Response<Result<Room, PagedMeta>>
The actual JSON,that I need to parse in swift4 is,
{
"class": {
"semester1": [
{
"name": "Kal"
},
{
"name": "Jack"
},
{
"name": "Igor"
}
],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept",
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
The code that I'd tried but failed to execute is,
struct Class: Decodable {
let semester: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case location = "location"
case statusTracker = "statusTracker"
}
}
struct location: Decodable {
let Dept: [typesSubIn]
let BlockNo: Int
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct internalComponents: Decodable {
let semester1: [semsIn]
let subjects: [subjectsIn]
}
struct semsIn: Decodable {
let nameIn: String
}
struct subjectsIn: Decodable {
let subjects: String
}
struct Dept: Decodable {
let Depts: String
}
I know it's completely wrong can someone give the actual format? I'm actually confused with the format for "subjects".It's not compiling as a whole too.
There are many issues.
You are making a common mistake by ignoring the root object partially.
Please take a look at the JSON: On the top level there are 3 keys class, location and statusTracker. The values for all 3 keys are dictionaries, there are no arrays.
Since class (lowercase) is a reserved word, I'm using components. By the way please conform to the naming convention that struct names start with a capital letter.
struct Root : Decodable {
let components : Class
let location: Location
let statusTracker: StatusTracker
enum CodingKeys: String, CodingKey { case components = "class", location, statusTracker }
}
There are many other problems. Here a consolidated version of the other structs
struct Class: Decodable {
let semester1: [SemsIn]
let subjects : [String]
}
struct Location: Decodable {
let dept : [String]
let blockNo : Int
enum CodingKeys: String, CodingKey { case dept = "Dept", blockNo = "BlockNo" }
}
struct SemsIn: Decodable {
let name: String
}
struct StatusTracker: Decodable {
let googleFormsURL: String // URL is no benefit
let totalCount: Int
}
Now decode Root
do {
let result = try decoder.decode(Root.self, from: data)
} catch { print(error) }
It looks like you sterilize Class object in wrong way. It should looks like:
struct Class: Decodable {
let class: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
}
There are a few things here causing your issue.
You have no top level item, I added Response struct
Location, class and statusTracker are both at the same level, not under class.
In your class struct, your items are set as arrays but they aren't arrays
To debug these types of issues, wrap your decode in a do catch block and print out the error. it will tell you the reason it failed to parse
Try this code from my playground:
let jsonData = """
{
"class": {
"semester1": [{
"name": "Kal"
}, {
"name": "Jack"
}, {
"name": "Igor"
}],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept"
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
""".data(using: .utf8)!
struct Response: Decodable {
let cls: Class
let location: Location
let statusTracker: statusTracker
enum CodingKeys: String, CodingKey {
case cls = "class"
case location
case statusTracker
}
}
struct Class: Decodable {
let semester: [SemesterStudents]
let subjects: [String]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case subjects
}
}
struct Location: Decodable {
let dept: [String]
let blockNo: Int
enum CodingKeys: String, CodingKey {
case dept = "Dept"
case blockNo = "BlockNo"
}
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct SemesterStudents: Decodable {
let name: String
}
struct Dept: Decodable {
let Depts: String
}
do {
let result = try JSONDecoder().decode(Response.self, from: jsonData)
print(result)
} catch let error {
print(error)
}
Another approach is to create an intermediate model that closely matches the JSON, let Swift generate the methods to decode it, and then pick off the pieces that you want in your final data model:
// snake_case to match the JSON
fileprivate struct RawServerResponse: Decodable {
struct User: Decodable {
var user_name: String
var real_info: UserRealInfo
}
struct UserRealInfo: Decodable {
var full_name: String
}
struct Review: Decodable {
var count: Int
}
var id: Int
var user: User
var reviews_count: [Review]
}
struct ServerResponse: Decodable {
var id: String
var username: String
var fullName: String
var reviewCount: Int
init(from decoder: Decoder) throws {
let rawResponse = try RawServerResponse(from: decoder)
// Now you can pick items that are important to your data model,
// conveniently decoded into a Swift structure
id = String(rawResponse.id)
username = rawResponse.user.user_name
fullName = rawResponse.user.real_info.full_name
reviewCount = rawResponse.reviews_count.first!.count
}
}