Can't Find WeatherKit Historical Data and Trouble with Attribution - ios

I would like to add some historical weather data to an app. I am able to use the new
WeatherKit to get current weather but cannot find ANY information to tell me how to
access historical data. One of the WWDC videos made reference to adding a start and end
date to the WeatherService call but I cannot find any info on this.
Also, I am struggling with the attribution requirements. I can make it work but only in
light mode. When the device is in dark mode, the Apple Weather Logo is just a white
box in the dark background (I assume the logo is there but in white - but can't prove it).
This is a simplified version - fetching current weather only:
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme
#State private var weather: Weather?
#State private var attLogo: URL?
#State private var attributionURL: URL?
#State private var logoImage: Image?
let weatherService = WeatherService.shared
var body: some View {
VStack {
if let weather {
VStack {
Text("San Francisco")
.font(.largeTitle)
Text("\(weather.currentWeather.temperature.formatted()) | \(weather.currentWeather.condition.description)")
}
}//if let
Spacer()
//white letters on white box if device in dark mode
AsyncImage(url: attLogo)
Group{
if let attributionURL {
Link("Weather Attribution", destination: attributionURL)
}
}//att group
}//outer v
.padding()
.task {
do {
let location = CLLocation(latitude: 37.77, longitude: -122.41)
self.weather = try await weatherService.weather(for: location)
} catch {
print(error)
}//do catch
}//task 1
.task {
do {
let attribution = try await weatherService.attribution
let attributionLink = attribution.legalPageURL
self.attributionURL = attributionLink
let attributionLogo = colorScheme == .light ? attribution.combinedMarkDarkURL : attribution.combinedMarkLightURL
self.attLogo = attributionLogo
} catch {
print("failed to load attribution")
}
}//task for logo and link
}//body
}//struct
Any guidance would be appreciated. Xcode 14.0 Beta, iOS 16.0 (20A5283p) in Simulator

As of 10 July both logo marks are unavailable at the provided links. I have created a placeholder in the AsyncImage for now, I don't know if it would ever pass Apple's check but it seems viable for a Beta/offline solution.
if let arributionLogo = arributionLogo{
AsyncImage(url: arributionLogo) { image in
image.scaledToFit()
} placeholder: {
Label("Apple Weather", systemImage: "cloud.sun.fill")
}
}else{
ProgressView()
}
if let arributionLink = arributionLink{
Link("Other data sources", destination: arributionLink)
}else{
ProgressView()
}
Historical Weather is now available with
let forecast = try await weatherService.weather(for: location, including:.hourly(startDate: startDate, endDate: endDate))
And
let forecast = try await weatherService.weather(for: location, including: .daily(startDate: startDate, endDate: endDate))

Related

How to retrieve and show data on screen in SwiftUI?

I am storing simple data in core data which i want to retrieve and show on screen, but I am not sure how and where should I write that code to show it on screen as I am always get out of bound error..
Also not all data is saving at time its only saving when i scroll til bottom
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.title, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
#StateObject private var viewModel = HomeViewModel()
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView {
LazyVGrid(columns: Array(repeating: .init(.flexible()),
count: UIDevice.current.userInterfaceIdiom == .pad ? 4 : 2)) {
ForEach(viewModel.results, id: \.self) {
let viewModel = ResultVM(model: $0)
NavigationLink(destination: {
DetailView(data: viewModel.trackName)
}, label: {
SearchResultRow(resultVM: viewModel, coreDM: PersistenceController())
})
}
}
}
}
.onAppear(perform: {
viewModel.performSearch()
})
}
}
}
struct SearchResultRow: View {
let resultVM: ResultVM
let coreDM: PersistenceController
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 16).fill(.yellow)
.frame(maxWidth: .infinity).aspectRatio(1, contentMode: .fit)
.overlay(Text(resultVM.trackName)) // want to show data from Core data here
}.padding()
.background(Color.red)
.onAppear(perform: {
coreDM.saveResult(title: resultVM.trackName)
})
}
}
Data showing from API which same data storing in CoreData
Method to save and retrieve (which is working fine)
func saveResult(title: String) {
let result = Item(context: container.viewContext)
result.title = title
do {
try container.viewContext.save()
}
catch {
print("error")
}
}
func getResult() -> [Item] {
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
do {
return try container.viewContext.fetch(fetchRequest)
}
catch {
return []
}
}
API call
import Foundation
import CoreData
class HomeViewModel: ObservableObject {
#Published var results = [ResultItem]()
func performSearch() {
guard let gUrl = URL(
string: "https://api.artic.edu/api/v1/artworks"
) else { return }
Task {
do {
let (data, _) = try await URLSession.shared.data(from: gUrl)
let response = try JSONDecoder()
.decode(ResponseData.self, from: data)
DispatchQueue.main.async { [weak self] in
self?.results = response.data ?? []
}
} catch {
print("*** ERROR ***")
}
}
}
}
Remove the view model we don't use view model objects in SwiftUI. The View struct stores the view data and does dependency tracking, and the property wrappers give it external change tracking features like an object. If you add an actual object on top you'll get consistency bugs that SwiftUI was designed to eliminate. So first remove this:
#StateObject private var viewModel = HomeViewModel()
You already have the fetch request property wrapper and as I said that gives the View change tracking, so when the items change (e.g. an item is added, removed or moved), the body will be called, so simply use it in your ForEach like this:
ForEach(items) { item in
NavigationLink(destination: {
DetailView(item: item)
}, label: {
SearchResultRow(item: item)
})
}
Then use #ObservedObject in your subview, this gives the View the ability to track changes to the item so body will be called when the item's properties change, like item.title. Do the same in the DetailView.
struct SearchResultRow: View {
#ObservedObject var item: Item
It seems to me you are trying to download and cache data in Core Data. That is unnecessary since you can simply set a NSURLCache on the NSURLSession and it will automatically cache it for you, it even works if offline. However if you really want to cache it yourself in Core Data then the architecture should be your UI fetches it from Core Data and then you have another object somewhere responsible for syncing down the remote data into Core Data. Normally this would be at the App struct level not in the View appearing. When the data is saved to core data the managed object context context will be changed which the #FetchRequest is listening for and body will be called.

SwiftUI Preview Canvas doesn't load Decoded Struct data from local file

I'm trying to get SwiftUI Previews working (with already downloaded API data) so that I don't need to run the app in simulator. Not sure what is wrong/how to go about it.
I've seen a few YouTube and (a lot) of SO on how to get this done but still hitting a wall. Appreciate pointers. (Note: 1st time trying to learn SwiftUI by porting a view of my app)
The data is going to be read-only, so there's no use for #State or #Bindings I believe and shouldn't be part of the #EnvironmentObject as well?
The YouTube's I've seen and google links are mainly using (locally stored) JSON files and they work.
eg: https://www.youtube.com/watch?v=hfjZNwayXfg and https://www.youtube.com/watch?v=EycwLxTU-EA
#available(iOS 13, *)
struct avatarView: View {
let weeklySummary: [HelperIntervalsIcu.icuWeeklyData]
func getAvatarPic() -> UIImage? {
if let avatarUrl = weeklySummary.last?.icuAvatar {
let avatarPicName = URL(fileURLWithPath: avatarUrl).lastPathComponent
let avatarPicImage = HelperIntervalsIcu.loadAvatarPic(fileName: avatarPicName)
return avatarPicImage
}
return nil
}
func getUserName() -> String {
if let userName = weeklySummary.last?.icuName {
return userName.prefix(1).capitalized + userName.dropFirst()
}
return "User Name Placeholder"
}
var body: some View {
HStack {
if let image = getAvatarPic() {
Image(uiImage: image)
} else {
Image("profile-200x200")
}
Text(getUserName())
Spacer()
}
}
}
#available(iOS 13.0, *)
struct IntervalsWeeklyView_Previews: PreviewProvider {
static var previews: some View {
let weeklySummary = HelperIntervalsIcu.loadWeeklySummaryFromFile()
avatarView(weeklySummary: weeklySummary)
}
}
The Preview just shows this
But when running in the simulator, it will work.
The data within HelperIntervalsIcu.icuWeeklyData is from a struct
struct icuWeeklyData : Codable {
var icuName : String
var icuAvatar : String
}

SwiftUI TabView PageTabViewStyle crashes without showing any error

I am trying to create an Image carousel using tabview and loading pictures from firebase. Without showing any error message or code tabview crashing. Please shed some light on what's going wrong here.
struct HomeView : View{
var body : View{
NavigationView{
VStack{
ScrollView{
CategoryView(homeViewModel: homeViewModel)
PosterView(homeViewModel: homeViewModel)
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarTitle("")
}
}
}
struct PosterView : View {
#StateObject var homeViewModel : HomeViewModel = HomeViewModel()
#State var currentIndex: Int = 0
var timer = Timer.publish(every: 3, on: .main, in: .common)
func next(){
withAnimation{
currentIndex = currentIndex < homeViewModel.posterList.count ? currentIndex +
1 : 0
}
}
var body: some View{
Divider()
GeometryReader{ proxy in
VStack{
TabView(selection: $currentIndex){
ForEach(homeViewModel.posterList){ item in
let imgURL = homeViewModel.trendingImgDictionary[item.id ?? ""]
AnimatedImage(url: URL(string: imgURL ?? ""))
}
}.tabViewStyle(PageTabViewStyle())
.padding()
.frame(width: proxy.size.width, height: proxy.size.height)
.onReceive(timer) { _ in
next()
}
.onTapGesture {
print("Tapped")
}
}
}
}
}
ViewModel: It contains two methods to fetch data and pictures from Firebase. That's working fine and am getting proper data. The only issue is while displaying it tabview crashes without showing any error messages.
class HomeViewModel : ObservableObject {
#Published var posterList : [TrendingBanner] = []
#Published var trendingImgDictionary : [String : String] = [:]
init() {
self.fetchTrendingList()
}
func fetchTrendingList() {
self.posterList.removeAll()
firestore.collection(Constants.COL_TRENDING).addSnapshotListener { snapshot, error in
guard let documents = snapshot?.documents else{
print("No Documents found")
return
}
self.posterList = documents.compactMap({ (queryDocumentSnapshot) -> TrendingBanner? in
return try? queryDocumentSnapshot.data(as:TrendingBanner.self )
})
print("Trending list \(self.posterList.count)")
print(self.posterList.first?.id)
let _ = self.posterList.map{ item in
self.LoadTrendingImageFromFirebase(id: item.id ?? "")
}
}
}
func LoadTrendingImageFromFirebase(id : String) {
let storageRef = storageRef.reference().child("trending/\(id)/\(id).png")
storageRef.downloadURL { (url, error) in
if error != nil {
print((error?.localizedDescription)!)
return
}
self.trendingImgDictionary[id] = url!.absoluteString
print("Trending img \(self.trendingImgDictionary)")
}
}
If you open SwiftUI module sources, you'll see the comment on top of TabView:
Tab views only support tab items of type Text, Image, or an image
followed by text. Passing any other type of view results in a visible but
empty tab item.
You're using AnimatedImage which is probably not intended to be supported by TabView.
Update
I made a library that liberates the SwiftUI _PageView which can be used to build a nice tab bar. Check my story on that.
Had the same issue recently on devices running iOS 14.5..<15. Adding .id() modifier to the TabView solved it.
Example:
TabView(selection: $currentIndex) {
ForEach(homeViewModel.posterList) { item in
content(for: item)
}
}
.tabViewStyle(PageTabViewStyle())
.id(homeViewModel.posterList.count)

SwiftUI Firestore data doesn't showing on the first load

i connected my app wirh Firestore. Everything works fine exept for my data does not show first time when i launch the app. When i switch between tabs, the data shows. It's accting like [Place] array is appending slowly after my app shows. How to force to show data on the first load? here is code:
class FirebaseManager: ObservableObject {
#Published var places = [Place]()
init() {
fetchData()
}
func fetchData(){
Firestore.firestore().clearPersistence()
let db = Firestore.firestore()
db.collection("places").addSnapshotListener { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
self.places.removeAll()
for i in (snap?.documentChanges)!{
let name = i.document.data()["name"] as! String
let type = i.document.data()["type"] as! String
let desc = i.document.data()["desc"] as! String
let image = i.document.data()["image"] as! String
let loc = i.document.data()["loc"] as! String
let contact = i.document.data()["contact"] as! String
let fol = i.document.data()["followers"] as! String
DispatchQueue.main.async {
self.places.append(Place(id: fol, name: name, type: type, description: desc, image: image, location: loc, contact: contact))
print(name)
}
}
}
}
}
struct topView : View {
#ObservedObject var firebaseMan = FirebaseManager()
var body : some View{
VStack(alignment: .leading, spacing: 15){
Text("Clubs & Places")
.font(.system(size: 25, weight: .regular))
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 15){
ForEach(0 ..< firebaseMan.places.count) {i in
Image(self.firebaseMan.places[i].image)
I tried with .onAppear{... on view, but that doesn't help too.
Thanks.
Resolved! This code is ok, but in SwiftUI ScrollView has a problem that it's not refreshing observasble object! After ScrollView always check if (your #Published var) =! nil! I hope Apple will resolve this problem soon, in next update.

SwiftUI List with TextField adjust when keyboard appears/disappears

I have written a List with SwiftUI. I also have a TextField object which is used as a search bar. My code looks like this:
import SwiftUI
struct MyListView: View {
#ObservedObject var viewModel: MyViewModel
#State private var query = ""
var body: some View {
NavigationView {
List {
// how to listen for changes here?
// if I add onEditingChange here, Get the value only after the user finish search (by pressing enter on the keyboard)
TextField(String.localizedString(forKey: "search_bar_hint"), text: self.$query) {
self.fetchListing()
}
ForEach(viewModel.myArray, id: \.id) { arrayObject in
NavigationLink(destination: MyDetailView(MyDetailViewModel(arrayObj: arrayObject))) {
MyRow(arrayObj: arrayObject)
}
}
}
.navigationBarTitle(navigationBarTitle())
}
.onAppear(perform: fetchListing)
}
private func fetchListing() {
query.isEmpty ? viewModel.fetchRequest(for: nil) : viewModel.fetchRequest(for: query)
}
private func navigationBarTitle() -> String {
return query.isEmpty ? String.localizedString(forKey: "my_title") : query
}
}
The problem I have now is that the List remains behind the keyboard :(. How can I set the list padding bottom or edge insets (or whatever else works, I am totally open) so that the scrolling of the list ends above the keyboard? The list „size“ should also adjust automatically depending on if keyboard will be opened or closed.
Problem looks like this:
Please help me with any advice on this, I really have no idea how to do this :(. I am a SwiftUI beginner who is trying to learn it :).
You may try the following and add detailed animations by yourself.
#ObservedObject var keyboard = KeyboardResponder()
var body: some View {
NavigationView {
List {
// how to listen for changes here?
// if I add onEditingChange here, Get the value only after the user finish search (by pressing enter on the keyboard)
TextField("search_bar_hint", text: self.$query) {
self.fetchListing()
}
ForEach(self.viewModel, id: \.self) { arrayObject in
Text(arrayObject)
}
}.padding(.bottom, self.keyboard.currentHeight).animation(.easeIn(duration: self.keyboard.keyboardDuration))
.navigationBarTitle(self.navigationBarTitle())
}
.onAppear(perform: fetchListing)
}
class KeyboardResponder: ObservableObject {
#Published var currentHeight: CGFloat = 0
#Published var keyboardDuration: TimeInterval = 0
private var anyCancellable: Set<AnyCancellable> = Set<AnyCancellable>()
init() {
let publisher1 = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillShowNotification).map{ notification -> Just<(CGFloat, TimeInterval)> in
guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {return Just((CGFloat(0.0), 0.0)) }
guard let duration:TimeInterval = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return Just((CGFloat(0.0), 0.0)) }
return Just((keyboardSize.height, duration))}
let publisher2 = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillHideNotification) .map{ notification -> Just<(CGFloat, TimeInterval)> in
guard let duration:TimeInterval = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return Just((CGFloat(0.0), 0.0)) }
return Just((0.0, duration))}
Publishers.Merge(publisher1, publisher2).switchToLatest().subscribe(on: RunLoop.main).sink(receiveValue: {
if $0.1 > 1e-6 { self.currentHeight = $0.0 }
self.keyboardDuration = $0.1
}).store(in: &anyCancellable)
}
}
The resolution for the problem with the keyboard padding is like E.coms suggested. Also the class written here by kontiki can be used:
How to make the bottom button follow the keyboard display in SwiftUI
The problems I had was because of state changes in my view hierarchy due to multiple instances of reference types publishing similar state changes.
My view models are reference types, which publish changes to its models, which are value types. However, these view models also contain reference types which handle network requests. For each view I render (each row), I assign a new view model instance, which also creates a new network service instance. Continuing this pattern, each of these network services also create and assign new network managers.

Resources