Xcode preview crashes on DB loop read SwiftUI - swift

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())
}
}

Related

No ObservableObject of type ModelData found: Missing environmentObject, but the environmentObject is actually there (SwiftUI, Xcode)

Xcode version: 14.2
I am trying to display a list of sports clubs. Since my modelData.clubs is still an empty array when just viewing the preview (modelData.clubs gets its actual value in my contentView, when the app is loaded), I would like it to display an array of default clubs if modelData.clubs.isEmpty. However, when I try to run my code, it gives me the following error in the marked line: Thread 1: Fatal error: No ObservableObject of type ModelData found. A View.environmentObject(_:) for ModelData may be missing as an ancestor of this view.
struct ClubList: View {
#EnvironmentObject var modelData: ModelData
#State private var sortByValue = false
var clubs: [Club] = [Club.default, Club.default, Club.default, Club.default, Club.default]
init() {
if (!modelData.clubs.isEmpty) { // ERROR IN THIS LINE
clubs = modelData.clubs
}
if sortByValue {
clubs.sort {
$0.value < $1.value
}
} else {
clubs.sort {
$0.name < $1.name
}
}
}
var body: some View {
NavigationView {
List {
ForEach(clubs) { club in
NavigationLink {
ClubDetail(club: club)
} label: {
ClubRow(club: club)
}
}
}
}
}
}
struct ClubList_Previews: PreviewProvider {
static var previews: some View {
ClubList()
.environmentObject(ModelData())
}
}
ClubList() gets called in my ContentView:
struct ContentView: View {
#EnvironmentObject var modelData: ModelData
// ... //
var body: some View {
if clubsLoaded {
ClubList()
.environmentObject(ModelData())
} // ... //
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(ModelData())
}
}
And ContentView gets called in my App:
#main
struct clubsApp: App {
#StateObject private var modelData = ModelData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(modelData)
}
}
}
I am pretty new to Xcode, but I think I put the environmentObject(ModelData()) everywhere, where it would be required, and I am pretty lost now on what to do.
Any help would be greatly appreciated!
I think I figured it out!
This post here made me realise that my problem was that I was trying to access my modelData in init(), where it is not possible yet.
Someone from the link said:
The environment is passed down when the body is called, so it doesn't yet exist during the initialization phase of the View struct.
Thus, I solved my problem by moving
if (!modelData.clubs.isEmpty) { clubs = modelData.clubs }
from init() into its own function which is then called in onAppear of my NavigationView.

SwiftUI | Preview not updating on #Binding var value change

I am learning SwiftUI and tried to make a simple todo list but I'm having issues understanding why #Binding property doesn't update my preview.
The code is the following.
import SwiftUI
struct TodoRow: View {
#Binding var todo: Todo
var body: some View {
HStack {
Button(action: {
todo.completed.toggle()
}, label: {
Image(systemName: todo.completed ? "checkmark.square" : "square")
})
.buttonStyle(.plain)
Text(todo.title)
.strikethrough(todo.completed)
}
}
}
struct TodoRow_Previews: PreviewProvider {
static var previews: some View {
TodoRow(todo: .constant(Todo.sampleData[0]))
}
}
The preview doesn't update when I click the square button but the app works fine. Am I using it incorrectly?
EDIT:
Even without .constant(#), the preview doesn't work.
struct TodoRow_Previews: PreviewProvider {
#State private static var todo = Todo.sampleData[0]
static var previews: some View {
TodoRow(todo: $todo)
}
}
Found a solution in the article Testing SwiftUI Bindings in Xcode Previews.
In order for previews to change you must create a container view that holds state and wraps the view you're working on.
In my case what I've ended up doing was changing my preview to the following.
struct TodoRow_Previews: PreviewProvider {
// A View that simply wraps the real view we're working on
// Its only purpose is to hold state
struct TodoRowContainer: View {
#State private var todo = Todo.sampleData[0]
var body: some View {
TodoRow(todo: $todo)
}
}
static var previews: some View {
Group {
TodoRow(todo: .constant(Todo.sampleData[0]))
.previewDisplayName("Immutable Row")
TodoRowContainer()
.previewDisplayName("Mutable Row")
}
}
}

How to fix TextField popping back to root on cancel?

I have a simple test watchOS application with a TextField in a secondary view (navigated to from a NavigationLink).
However, when the TextField is canceled or submitted, it will pop back out to the root view instead of staying in the current view. I can't find any information on this anywhere else. Any fixes?
ContentView:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
NavigationLink("what", destination: DestinationView())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DestinationView:
import SwiftUI
struct DestinationView: View {
#State private var message: String = ""
var body: some View {
TextField(
"Send Something...",
text: $message
)
}
}
struct DestinationView_Previews: PreviewProvider {
static var previews: some View {
DestinationView()
}
}
I found the issue..
I was using a NavigationView, which is deprecated. I removed it and now it's working as intended. (XCode 13.2.1, watchOS 8.3)
*facepalm*

SwiftUI Preview not working with Binding<Bool> [duplicate]

I present this view as a sheet from its parent view
struct NamesView: View {
#Binding var match: Match
var body: some View {
...
}
}
Since the match source of truth is in the parent view presenting this NamesView sheet, when the view is constructed I pass in a $match binding and data flows as intended.
However, when constructing this view in a preview provider
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView()
}
}
the compiler says that NamesView() expects a match argument of type Binding<Match> (Match being the parent view presenting this view as a sheet). I'm not sure what would be a good way to proceed from here or if this is a limitation of SwiftUI.
If you want only constant preview, then it can be
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView(match: .constant(Match()))
}
}
if you want it in live, the it can be
struct NamesView_Previews: PreviewProvider {
struct BindingTestHolder: View {
#State private var testedMatch = Match()
var body: some View {
NamesView(match: $testedMatch)
}
}
static var previews: some View {
BindingTestHolder()
}
}
Try this:
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView(match:.constant(Match()))
}
}
I wrote about this in depth here, but the short version is simply to add the following extension:
extension Binding {
public static func variable(_ value: Value) -> Binding<Value> {
var state = value
return Binding<Value> {
state
} set: {
state = $0
}
}
}
And then do...
struct NamesView_Previews : PreviewProvider {
static var previews: some View {
NamesView(match: .variable(Match()))
}
}
This lets you actually mutate the value, useful when doing live previews in Xcode 14.

SwiftUI CoreData crashes preview

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)
}
}