SwiftUI CoreData crashes preview - swift

I have the following code to draw a list of cars, data stored in coredata.
However the swiftui preview seems to break when I add the line of code that fetches data from the databases.
the error logs tells the following:
PotentialCrashError: test app.app may have crashed
mileage app.app may have crashed. Check
~/Library/Logs/DiagnosticReports for any crash logs from your
application.
==================================
| Error Domain=com.apple.dt.ultraviolet.service Code=12 "Rendering
service was interrupted" UserInfo={NSLocalizedDescription=Rendering
service was interrupted}
this is the code the part where foreach starts and ends causes the error:
import SwiftUI
struct CarListView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: Car.all()) var cars: FetchedResults<Car>
var body: some View {
NavigationView {
ZStack {
List {
Section(header: Text("Cars")) {
ForEach(self.cars, id: \.numberPlate) { car in
HStack {
VStack(alignment: .leading) {
Text(car.name)
Text(car.numberPlate)
}
}
}
}
}
}
}
}
}
struct CarListView_Previews: PreviewProvider {
static var previews: some View {
CarListView()
}
}

The issue seems to be related to the fact it couldn't somehow get a context which allows to fetch the data in preview mode. By manually doing so for preview mode it fixes the issue.
struct CarListView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return CarListView().environment(\.managedObjectContext, context)
}
}

In case anyone else was wondering, this crashed because when simulating a preview for a specific view, it doesn't have contextual information that is provided by the base system environment: in this case, the managedObjectContext. So, it will crash because he's referencing an object provided by the environment. By providing a static version of the object (viewContext: ManagedObjectContext), it allows the preview to load and assert any needed context.
For most newer applications the following will also work:
struct CarListView_Previews: PreviewProvider {
static var previews: some View {
let persistentController = PersistentController.preview
CarListView().environment(\.managedObjectContext, persistentController.container.viewContext)
}
}

Related

SwiftUI App works fine in simulator, but not in preview

I'm trying to get preview working in Xcode. Just a simple test app written in Swift. Grabs rows from a Realm database and lists them.
It works fine when I build/run in the simulator but none of the data shows in the ContentView_Preview.
App in Simulator
App in Preview
Here's the code:
import SwiftUI
import RealmSwift
struct HeaderView: View {
var body: some View {
HStack(alignment: .firstTextBaseline) {
Text("Time Spent").font(.largeTitle)
Text("or waisted...").font(.headline)
}
}
}
struct GroupListView: View {
#ObservedResults(TaskGroup.self) var taskGroups
var body: some View {
NavigationView {
// Nothing from here shows in the preview.. but shows fine in the simulator
List(taskGroups) {
Text("Group: " + $0.name)
}
.navigationTitle("Task Groups (\(taskGroups.count))")
// Grrr
}
}
}
struct FooterView: View {
var body: some View {
HStack {
Text("🤬 Grr.. Preview isn't working.")
Spacer()
Text("Simulator works fine though.")
}
}
}
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
HeaderView()
GroupListView()
FooterView()
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I've searched for hours and tried various different suggestions. No luck. Any help would be wonderful.
In your preview it's likely that taskGroups is empty. You could verify this by sticking something like
if taskGroups.isEmpty {
Text("test")
}
For your preview you should inject some placeholder content. Depending on how Realm works there may be a mechanism to do this already, or you may need to create an abstraction that allows you to do this.

Why do I keep getting a "Cannot find '...' in scope" error?

I am trying to make a second view on an app which is like a leaderboard. When the game has completed 10 rounds on ContentView, I want the calculated score to be stored and printed on the ScoreboardView (accessed by a button). I used #Binding to connect the score variable from one view to the other, but keep getting the "out of scope" error. Does anyone know why this is? Here is my code for the ScoreboardView:
'''
import SwiftUI
struct scoreboardView: View {
#Binding var score: Int
var body: some View {
List {
ForEach(1..<9) {
Text("Game \($0): \(score) ")
}
}
}
}
struct scoreboardView_Previews: PreviewProvider {
static var previews: some View {
scoreboardView(score: $scoreTracker)
}
}
'''
This is not my final code, therefore ignore the middle. However, I get the error in the last line of the initializing of the preview.
You don't have anything defined called scoreTracker in your code, but you're trying to use it in your preview. Instead, you can pass a constant in place of your binding (just for preview purposes):
struct scoreboardView: View {
#Binding var score: Int
var body: some View {
List {
ForEach(1..<9) {
Text("Game \($0): \(score) ")
}
}
}
}
struct scoreboardView_Previews: PreviewProvider {
static var previews: some View {
scoreboardView(score: .constant(50))
}
}

Leaks in NavigationView/List/ForEach with Dynamically Generated Views

If you create a very simple example that shows a lot of leaking object within the SwiftUI code if you nest NavigationView/List/ForEach and return different types of views in the ForEach closure.
import SwiftUI
class MyStateObject : ObservableObject {
#Published var items:[Int]
init() {
self.items = Array(0..<1000)
}
}
struct ContentView: View {
#StateObject var stateObject = MyStateObject()
var body: some View {
NavigationView {
List {
ForEach(stateObject.items, id: \.self) { item in
if(item % 2 == 0) {
Text("Even \(item)")
}
else {
Image(systemName: "xmark.octagon")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I strongly suspect this is a bug in SwiftUI but I wanted to ask if I am doing anything wrong here.
You can see the leaks by attaching Instruments. It will show immediately and increase if you scroll through the list.
Interestingly, it seems the leaks don't happen if
you remove the NavigationView from the hierarchy.
you only supply one type of View in ForEach (and don't branch via if/else).
the list of items you want to show is small (100 does not seem to result in leaks).
(Tested on XCode 12.5 and iOS 14.5 Simulator and Device,)
Since in my app I am pretty much reliant on this kind of hierarchy, I am very open for some suggestions on how to avoid the leaking.

How do you edit the members of a Struct of type View from another Struct of type View? (SwiftUI)

I have the following 2 structs within separate files and displayed in the contentView. What I'm trying to understand is how to maintain the contentView as only displaying and organizing the UI. Placing all of my other views in separate files. My first thought was the correct approach would be to use static variables updated by functions that are called from the button press action. But the buttons text did not update accordingly. As they are dynamically updated according to #State.
update:
I attempted to solve this by using protocols and delegates to no avail. By my understanding this delegate call should be receiving on the other end and updating structcop.ID and the change should be reflected in the content view.
FILE 1
import SwiftUI
struct structdispatch: View {
var radio:RadioDelegate?
func send() {
radio?.update()
self.debug()
}
var body: some View {
Button(action: self.send)
{Text("DISPATCHER")}
}
func debug() {
print("Button is sending?")
}
}
struct structdispatch_Previews: PreviewProvider {
static var previews: some View {
structdispatch()
}
}
**FILE 2:**
import SwiftUI
protocol RadioDelegate {
func update()
}
struct structcop: View, RadioDelegate {
#State public var ID:Int = 3
func update(){
print("message recieved")
self.ID += 1
print(self.ID)
}
var body: some View {
Text(String(self.ID))
}
}
struct structcop_Previews: PreviewProvider {
static var previews: some View {
structcop()
}
}
DEBUG CONSOLE RETURNS:
The Button is working
View is updated on some internal DynamicProperty change, like #State, so here is possible solution
Tested with Xcode 12 / iOS 14
struct structcop: View {
static public var ID = 3
#State private var localID = Self.ID {
didSet {
Self.ID = localID
}
}
var body: some View {
Button(action: printme)
{Text(String(localID))}
}
func printme(){
self.localID = 5
print(structcop.ID)
}
}
Solution:
After some digging I have a working solution but I'm still curious if there is a way to modify properties of other structs while maintaining dynamic view updates.
Solution: store data for display in an observable object which will either read or act as the model which the user is interacting with.
An observable object is a custom object for your data that can be bound to a view from storage in SwiftUI’s environment. SwiftUI watches for any changes to observable objects that could affect a view, and displays the correct version of the view after a change. -apple
A new model type is declared that conforms to the ObservableObject protocol from the Combine framework. SwiftUI subscribes to the observable object and updates relevant views that need refreshing when the data changes. SceneDelegate.swift needs to have the .environmentObject(_:) modifier added to your root view.
Properties declared within the ObservableObject should be set to #Published so that any changes are picked up by subscribers.
For test code I created an ObservableObject called headquarters
import SwiftUI
import Combine
final class hq: ObservableObject {
#Published var info = headQuarters
}
let headQuarters = hqData(id: 3)
struct hqData {
var id: Int
mutating func bump() {
self.id += 1
}
}
In my struct dispatch I subscribed to the object and called a function that iterated the id in the model whenever the button was pressed. My struct cop also subscribed to the object and thus the model and button text updated accordingly to changes.
struct dispatch: View {
#EnvironmentObject private var hq: headqarters
var body: some View {
Button(action: {self.hq.info.bump()}) {
Text("Button")
}
}
}
struct cop: View {
#EnvironmentObject private var hq: headquarters
var body: some View {
Text(String(self.hq.info.id))
}
}

Xcode preview crashes on DB loop read SwiftUI

When I call Core Data using:
#FetchRequest(
entity: Entity.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Entity.id, ascending: true),
]
) var myData: FetchedResults<Entity>
then inside the View:
VStack {
ForEach(myData, id: \.self) { data in
Text("Test")
}
}
Xcode live preview crashes, if I remove the ForEach with a simple text it would work, how can I fix this? This is my current preview:
struct HomeItemListView_Previews: PreviewProvider {
static var previews: some View {
HomeItemListView()
}
}
This is the error it shows:
PotentialCrashError: MyApp.app may have crashed
MyApp.app may have crashed. Check ~/Library/Logs/DiagnosticReports for any crash logs from your application.
==================================
| Error Domain=com.apple.dt.ultraviolet.service Code=12 "Rendering service was interrupted" UserInfo={NSLocalizedDescription=Rendering service was interrupted}
As side note, the code compiles fine and displays fine on the normal simulator, it's the live one that has issues.
UPDATE:
So I have a viewRouter that controls the first screen, example Onboarding, if true, it goes to onboarding, if false it goes to HomeView.
This is how I have setup SceneDelegate:
let viewRouter = ViewRouter()
let contentView = MotherView()
.environment(\.managedObjectContext, context)
.environmentObject(viewRouter)
// Set the MotherView as the root view
window.rootViewController = UIHostingController(rootView: contentView)
On my MotherView, I have the Preview like this:
struct MotherView_Previews: PreviewProvider {
static var previews: some View {
MotherView().environmentObject(ViewRouter())
}
}
And on the Home view I have it like this:
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
So, most probably, your HomeItemListView preview should be set as follows
struct HomeItemListView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return HomeItemListView()
.environment(\.managedObjectContext, context)
.environmentObject(ViewRouter())
}
}