class PostManager: ObservableObject {
static let shared = PostManager()
private init() {}
@Published var containers: [PostContainer] = []
// Other code
}
class PostContainer: ObservableObject {
var id: UUID = UUID()
var timestamp: Date
var subreddit: String
var posts: [Post]
var type: ContainerType
var active: Bool
// Init
}
@Model
final class Post: Decodable, ObservableObject {
// Other code
}
In my main view, a network request is made and a PostContainer is created if it doesn't exist. This code works fine, and the view is updated correctly.
let container = PostContainer(timestamp: Date(), subreddit: subreddit, posts: posts, type: .search, active: true)
self.containers.append(container)
If the user wants to see more, they press a button and another request is made. This time, the new data will be added to the PostContainer instead of creating a new one.
if let container = self.containers.first(where: {$0.subreddit == subreddit}) {
// Update previous container
container.timestamp = Date()
print(container.posts.count)
// Map IDs from container and then remove duplicates
let existingIDs = Set(container.posts.map { $0.id })
let filtered = posts.filter { !existingIDs.contains($0.id) }
// Append new post
container.posts.append(contentsOf: filtered)
container.active = true
}
This code is working fine as well, except for the view is not updating. In the view, PostManager is an @EnvironmentObject. I also have a computed variable to get the post and sort them. I added a print statement to that variable and saw that it wasn't being printed even though more data was being added to the PostContainer. At one point, I created an ID for the List that displays the data and had the code inside PostManager update that ID when it was finished. This of course worked, but it's not ideal.
How can I get the view to update when post are appended inside of PostContainer?
Post
Replies
Boosts
Views
Activity
I have my ContentView which has a Sheet that will appear when a button is pressed.
struct ContentView: View {
@EnvironmentObject private var settings: SettingsHandler
@State private var settingsView: Bool = false
var body: some View {
NavigationStack {
Button(action: {
settingsView.toggle()
}, label: {
Image(systemName: "gearshape.fill")
})
}
.preferredColorScheme(settings.darkTheme ? .dark : nil)
.sheet(isPresented: $settingsView, content: {
SettingsView()
})
}
}
Let's say the app is in light mode based on the phones theme settings. You open the SettingsView and select the toggle that will switch to dark mode. Everything changes to dark mode, including the SettingsView sheet. Then you select the same toggle to switch back and ContentView in the background changes to light theme but the sheet doesn't until you close and reopen it. I would think it would change back considering it changed to dark mode without needing to be closed.
I tried attaching an ID to the SettingsView and having it refresh when settings.darkTheme is changed, however, it doesn't seem to be doing anything. I also added the .preferredColorScheme() modifier into the SettingsView, but it did nothing. I also replaced the nil to .light, and the same issue occurred. Settings is an EnvironmentObject that I created to manage all the Settings I have.
At the moment, I'm thinking I can have the sheet just close and reopen, however, I would like for it to update properly. Any ideas?
That may not be the best way to explain it. Essentially, I'm requesting data from Reddit and storing it in an object called Request. This object has a timestamp and an array of objects called Post. Everything was working fine until I started to add some code to filter the post that were being fetched from reddit.
extension [Request] {
func insert(request: Request, in context: ModelContext) {
if self.count == 0 {
context.logMessage("There are no existing request")
context.insert(request)
context.logMessage("Request saved")
}else {
print(request) // No crash
print(request.timestamp) // No crash
print(request.posts) // Causes a crash
}
}
}
When it does crash, it points to this code inside the SwiftData model. This code seems to be something within SwiftData. I didn't write any of this.
{
@storageRestrictions(accesses: _$backingData, initializes: _posts)
init(initialValue) {
_$backingData.setValue(forKey: \.posts, to: initialValue)
_posts = _SwiftDataNoType()
}
get {
_$observationRegistrar.access(self, keyPath: \.posts)
return self.getValue(forKey: \.posts)
}
set {
_$observationRegistrar.withMutation(of: self, keyPath: \.posts) {
self.setValue(forKey: \.posts, to: newValue)
}
}
}
It has the error at the return self.getValue() line: Thread 5: EXC_BREAKPOINT (code=1, subcode=0x2406965c4)
This is the sequence that occurs:
View is loaded
Checks if it should load a new request
If it should, it calls this function
private func requestNewData() {
redditService.fetchRedditAllData(completion: { result in
DispatchQueue.main.async {
switch result {
case .success(let newRequest):
modelContext.logMessage("Successfully retreived and decoded data from Reddit") // Log the success
//modelContext.insert(newRequest)
requests.insert(request: newRequest, in: modelContext)
case .failure:
modelContext.logMessage("Failed to retrieve and decode data from Reddit")
}
}
})
}
The code for the fetch function is here:
func fetchRedditAllData(completion: @escaping (Result<Request, Error>) -> Void) {
// Try to construct the URL and return if it fails
guard let url = URL(string: RedditRequests.all) else {
context.logMessage("Failed to contruct the url for r/all")
return
}
// Try to retrieve data from the URL
session.dataTask(with: url, completionHandler: { data, _, error in
// If it fails, log the failure
if let error = error {
self.context.logMessage("Failed to retrieve data from the r/all URL.\n\(error.localizedDescription)")
} else {
// If it doesn't fail, try to decode the data
do {
let data = data ?? Data() // Get data
let response = try self.decoder.decode(Request.self, from: data) // Decode JSON into Request model
completion(.success(response)) // Return response (request) if successful
self.context.logMessage("Decoded data")
}
catch {
completion(.failure(error))
self.context.logMessage("Failed to decode data into a Request.\n\(error.localizedDescription)")
}
}
}).resume()
}
If I don't try to access request.posts before saving it to the context, it works fine. It will fetch the data and store it to the phone and then display it on the phone. When I try to access request.posts to do some filtering, it crashes.
Does anyone have any ideas?
Hello,
I have an app that uses JSON data to save and load information. This is how the information is loaded when the app is first opened.
DispatchQueue.global(qos: .background).async { [weak self] in
//Get data from "user.data"
guard let data = try? Data(contentsOf: Self.fileURL) else {
//code to use test data
/*#if DEBUG
DispatchQueue.main.async {
self?.scrums = DailyScrum.data
}
#endif*/
return
}
guard let user = try? JSONDecoder().decode(User.self, from: data) else {
fatalError("Can't decode user data")
}
//Set published value to user that was loaded from the file
DispatchQueue.main.async {
self?.user = user
}
}
}
There is a struct called Month that is used inside of another struct called User. When the app is open, in the onAppear in ContentView, it runs a function to check the current date. It compares it against what's already saved, and if it's a new month, it will create a new Month struct to replace the current month.
private mutating func newMonth() {
//Create new month
month = Month(name: Date().month)
}
This seems to be working, but not exactly as intended. The new month will be created, but when I open the app it will still show the previous month. I have a TextView that displays the month. I printed the month variable to the console to see what it was reading.
Printed in an HStack this way
let _ = print(user.month)
Output
Month(name: "July", transactions: [], income: 0.0, expenses: 0.0, net: 0.0, saved: 0.0, invested: 0.0, netMargin: 0.0)
Month(name: "June", transactions: [], income: 0.0, expenses: 0.0, net: 0.0, saved: 0.0, invested: 0.0, netMargin: 0.0)
Somehow that month variable is showing two different months in it, June and July. It doesn't make sense to me, because it's not an array and that month variable should be getting replaced due to the newMonth() function. What's going on here? How is it creating a new month, but not overwriting the previous month? Let me know if you need anymore information.
Hello. I have a picker that uses an enum to populate itself. There is a class called "user" that has a variable called "defaultTab". This is what's being used as the selection for the picker.
enum Tab: String, CaseIterable {
case data = "Data"
case transactions = "Transactions"
case user = "User"
var id: String {self.rawValue}
}
That is the enum.
import SwiftUI
class User: ObservableObject {
var name: String
var sources: [Source]
var accounts: [Account]
var categories: [Category]
var transactions: [Transaction]
var defaultTab: Tab
init(name: String, sources: [Source], accounts: [Account], categories: [Category], transactions: [Transaction], defaultTab: Tab) {
self.name = name
self.sources = sources
self.accounts = accounts
self.categories = categories
self.transactions = transactions
self.defaultTab = defaultTab
}
}
That is the class, and below is the picker.
Picker("Default tab", selection: $user.defaultTab) {
ForEach(Tab.allCases, id: \.self) { kind in
Text(kind.rawValue)
.tag(kind as Tab)
}
}
This picker is inside of a Section that is inside of a List. When I make a selection, it does not change the picker. How can I resolve this issue?