Swiftui-Picker doesn't show selected value [duplicate] - core-data

This question already has an answer here:
Choosing CoreData Entities from form picker
(1 answer)
Closed 9 months ago.
Initial position: making two pickers filled from database where the second depends on the first. I followed this Picker Values from a previous picker - CoreData/SwiftUI example and it works pretty good.
Only one problem: the second picker doesn't show the selected value.
#State var courtSelected = 0
#State var judgeSelected = 0
HStack{
Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
ForEach(0..<courts.count){ court in
Text("\(courts[court].name ?? "Unknown")")
}
}
}
HStack {
Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
ForEach(Array(courts[courtSelected].courtsJudges! as! Set<Judges>), id: \.self) { judge in
Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
}
}
}
Only differences:
the modification from NSSet to array
I had to change #Binding var judgeSelected:Int to #State, because otherwise I have to hand over the judge selected as Parameter beginning from App-Struct.
Printing the $judgeSelected inside the label demonstrates, that this var is never changed.

Your selection and presentation are differ by type, so add tag:
Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
ForEach(0..<courts.count){ court in
Text("\(courts[court].name ?? "Unknown")").tag(court) // << here !!
}
}
Second picker presents objects, so selection should also be an object, like
#State var judgeSelected: Judges? = nil
Next shows similar case so should be helpful https://stackoverflow.com/a/68815871/12299030

try something like this:
HStack{
Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
ForEach(0..<courts.count){ court in
Text("\(courts[court].name ?? "Unknown")")
}
}
}
HStack {
Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
// -- here
ForEach(Array(Set(courts[courtSelected].courtsJudges!)), id: \.self) { judge in
Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
}
}.id(UUID()) // <-- here
}
}
Note also the second ForEach with Set. PS, do not use forced unwrap, ie. no ! in your code.
EDIT-1: to avoid the error with arrayLiteral, try this:
HStack {
Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
if let theJudges = courts[courtSelected].courtsJudges {
ForEach(Array(Set(theJudges)), id: \.self) { judge in
Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
}
}
}.id(UUID())
}
EDIT-2:
here is my test code that allows the second picker
to depend on the first picker. I used both the id marker, and tag that
must match the selection type.
Since you don't show your struct code for court and judge,
I created some example structs for those.
You will have to adjust the code to cater for your structs.
Used the id of the Judge struct in the second picker for the tag.
However, there are other ways to have a Int tag, for example using array indices, such as:
struct Judge: Identifiable, Hashable {
var id: Int
var gender: String?
var name: String?
var title: String?
}
struct Court: Identifiable, Hashable {
var id: Int
var name: String?
var courtsJudges: [Judge]?
}
struct ContentView: View {
#State var courtSelected = 0
#State var judgeSelected = 0
#State var courts: [Court] = [
Court(id: 0, name: "one",
courtsJudges: [
Judge(id: 0, gender: "Male", name: "name1", title: "title1"),
Judge(id: 1, gender: "Male", name: "name2", title: "title2"),
Judge(id: 2, gender: "Male", name: "name3", title: "title3")
]),
Court(id: 1, name: "two",
courtsJudges: [
Judge(id: 3, gender: "Female", name: "name7", title: "title7"),
Judge(id: 4, gender: "Female", name: "name8", title: "title8"),
Judge(id: 5, gender: "Female", name: "name9", title: "title9")
])
]
var body: some View {
VStack (spacing: 77) {
HStack{
Picker(selection: $courtSelected, label: Text("Gericht \(courtSelected)")){
ForEach(0..<courts.count) { court in
Text("\(courts[court].name ?? "Unknown")").tag(court)
}
}
}
HStack {
Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
if let theJudges = courts[courtSelected].courtsJudges {
ForEach(Array(Set(theJudges))) { judge in
Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
.tag(judge.id)
}
}
}.id(UUID())
}
}.padding()
}
}
Alternatively:
HStack {
Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
if let theJudges = courts[courtSelected].courtsJudges, let arr = Array(Set(theJudges)) {
ForEach(arr.indices, id: \.self) { index in
Text("\(arr[index].gender ?? "") \(arr[index].title ?? "") \(arr[index].name ?? "")")
.tag(index)
}
}
}.id(UUID())
}

First: thanks for all the great help so far.
Bringing it all together til now.
The following code creates a picker, but the selection is not shown. Changing to a radioGroup, you can't select anything.
Here's my edited code
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Courts.name, ascending: true)],
animation: .default
)
private var courts: FetchedResults<Courts>
#State var courtSelected = 0
#State var judgeSelected = 0
var body: some View {
HStack{
Picker(selection: $courtSelected, label: Text("Gericht")){
ForEach(0..<courts.count){ court in
Text("\(courts[court].name ?? "Unknown")").tag(court)
}
}
}
HStack {
Picker(selection: $judgeSelected, label: Text("Richter \(judgeSelected)")){
ForEach(Array(courts[courtSelected].courtsJudges as! Set<Judges>), id: \.self) { judge in
Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")").tag(judge)
}
}.id(UUID())
}
}
}
The next code still leads to an error
HStack {
Picker(selection: $judgeSelected, label: Text("Richter: (\(judgeSelected))")){
if let theJudges = courts[courtSelected].courtsJudges {
ForEach(Array(Set(theJudges)), id: \.self) { judge in
Text("\(judge.gender ?? "") \(judge.title ?? "") \(judge.name ?? "")")
}
}
}.id(UUID())
}
I think, the problem has to be anywhere else, because already at the start the picker doesn't show anything and the radios are grey.
Is it, because picker 2 depends on picker 1 and changes when a value in picker 1 is selected?
It's all for macOS on xcode 13.4.1

I created a complete new project with core data, added two entities (Courts and Judges) with two attributes each (id and name).
Then I only made this view:
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Courts.name, ascending: true)],
animation: .default
)
private var courts: FetchedResults<Courts>
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Judges.name, ascending: true)],
animation: .default
)
private var judges: FetchedResults<Judges>
#State var courtSelected = 0
#State var judgeSelected = 0
var body: some View {
if(courts.count > 0) {
HStack{
Picker(selection: $courtSelected, label: Text("Gericht")){
ForEach(0..<courts.count){ court in
Text("\(courts[court].name ?? "Unknown")").tag(court)
}
}
}
HStack {
Picker(selection: $judgeSelected, label: Text("Richter \(judgeSelected)")){
ForEach(Array(courts[courtSelected].courtsJudges as! Set<Judges>), id: \.self) { judge in
Text("\(judge.name ?? "")").tag(judge)
}
}.id(UUID())
}
HStack {
Button("neue Gerichte") {
addItem()
addJudges()
}
}
}
}
private func addItem() {
for id in 0..<3 {
let newItem = Courts(context: viewContext)
newItem.id = UUID()
newItem.name = "Gericht Nr. \(id)"
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func addJudges() {
for cd in 0..<courts.count {
for jd in 0..<3 {
let newJudge = Judges(context: viewContext)
newJudge.id = UUID()
newJudge.name = "Richter \(jd) am Gericht \(courts[cd].name)"
newJudge.judgesCourts = courts[cd]
try? viewContext.save()
}
}
}
}
The result: both pickers are shown, first ist ok, second shows the right judges, but they are not "selectable"

Related

SwiftUI CoreData - How to update the fetch request and the list

I am creating an application in SwiftUI using CoreData and I have a problem. In application you can add song to favorites and it will be added to list (FavoriteSongsView). Until the song is added to favorites everything is fine. In DetailView I click the button and the "heart.fill" icon and the song is added to the list. However, if I click on the icon again to un-favorite the song, it does not disappear from the list. I fought with it a little bit but without any effect. Could you please point out the cause of the problem?
List of favorite songs:
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Song.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Song.number, ascending: true)],
predicate: NSPredicate(format: "favorite <> 'false'")
) var songs: FetchedResults<Song>
var body: some View {
NavigationView{
VStack{
List {
ForEach(songs, id:\.self){ song in
NavigationLink(destination: DetailView(song: song, isSelected: song.favorite)) {
HStack{
Text("\(song.number). ") .font(.headline) + Text(song.title ?? "No title")
}
}
}
}
}
.listStyle(InsetListStyle())
.navigationTitle("Favorite")
}
}
}
Detailed view:
struct DetailView: View {
#State var song : Song
#State var isSelected: Bool
#State var wrongNumber: Bool = false
var body: some View {
VStack{
Text(song.content!)
.padding()
Spacer()
}
.navigationBarTitle("\(song.number). \(song.title ?? "No title")", displayMode: .inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
HStack{
Button(action: {
song.favorite.toggle()
PersistenceController.shared.save()
isSelected=song.favorite
}) {
Image(systemName: "heart.fill")
.foregroundColor(isSelected ? .red : .blue)
}
Button(action: {
alert()
}) {
Image(systemName: "1.magnifyingglass")
}
NavigationLink("DetailView", destination: DetailView(song: song, isSelected: isSelected))
.frame(width: 0, height: 0)
.hidden()
}
}
}
}
}
Use this NSPredicate
NSPredicate(format: "favorite = %d", false)

Core Data, Problem with updating (duplicating instead)

I am new to Swift UI. Could you please help me with core data updating?
Here is the point of a problem:
I am building a WatchOS app. There are 3 Views there:
FirstView - a view with a button to Add a new Goal and a List of added Goals.
AddGoalView - appears after pressing Add new Goal.
RingView - a view with a Goal Ring (similar to activity ring mechanics) and all the data presented.
The point of the problem is the next:
After adding a new Goal everything is alright. The Data passes correctly from AddGoalView to the FirstView. I need only 2 items to be passed out of AddGoalView (One String and one Double).
Then, after pressing on the recently created Goal I appear on the Ring View. I successfully pass there 2 items (that String and Double I mentioned).
On the RingView I want to update the 3-rd Item (double) and send it back. So it can be updated on the FirstView.
the Result:
Instead of updating this 3-d Item it just seems to create a completely new Goal on the First View below the previous Goal. Photo
My Code (FirstView):
struct FirstView: View {
#FetchRequest (
entity:NewGoal.entity(),
sortDescriptors:[NSSortDescriptor(keyPath: \NewGoal.dateAdded, ascending: false)],
animation: .easeInOut )
var results:FetchedResults<NewGoal>
#State var showMe = false
var body: some View {
ScrollView{
VStack{
VStack(alignment: .leading){
Text("My Goals:")
NavigationLink(
destination: AddGoalView(),
isActive: $showMe,
label: {
Image(systemName: "plus")
Text("Set Money Goal")
})
Text("Recents:")
ForEach(results){ item in
VStack(alignment: .leading){
NavigationLink(
destination: RingView(GTitle: item.goalTitle ?? "", Sum: item.neededSum),
label: {
HStack{
Image(systemName: "gear")
VStack(alignment: .leading){
Text(item.goalTitle ?? "")
HStack{
Text("$\(item.yourSum, specifier: "%.f")") ///This item doesn't update
Text("/ $\(item.neededSum, specifier: "%.f")")
}
}
}
})
}
}
}
}
}
}
}
My Code (AddGoalView):
struct AddGoalView: View {
#State private var goalTitle = ""
#State private var showMe:Bool = true
#State private var neededSum:Double = 0.0
#State private var isFocusedNum = false
#Environment(\.managedObjectContext) var context
#Environment(\.presentationMode) var presentationMode
var body: some View {
ScrollView{
VStack (alignment: .leading, spacing: 6){
TextField("Goal Name...", text: $goalTitle)
HStack{
Text("$\(neededSum, specifier: "%.f")")
.overlay(
RoundedRectangle(cornerRadius: 9)
.stroke(isFocusedNum ? Color.red : Color.white, lineWidth: 1)
.opacity(1.0))
.focusable(true) { newState in isFocusedNum = newState}
.animation(.easeInOut(duration: 0.1), value: isFocusedNum)
.digitalCrownRotation(
$neededSum,
from: 0,
through: 100000,
by: 25,
sensitivity: .high)
}
Button(action: addGoal) {
Text("Add Goal")
}
.disabled(neededSum == 0.0)
.disabled(goalTitle == "")
.navigationTitle("Edit")
}
}
}
private func addGoal(){
let goal = NewGoal(context: context)
goal.goalTitle = goalTitle
goal.dateAdded = Date()
goal.neededSum = neededSum
do{
try context.save()
presentationMode.wrappedValue.dismiss()
}catch let err{
print(err.localizedDescription)
}
}
My Code (RingView Code):
struct RingView: View {
#State private var isFocusedSum = false
#State private var yournewSum:Double = 0.0
var goalItem: NewGoal?
var Sum:Double
var GTitle:String
#Environment(\.managedObjectContext) var context
#Environment(\.presentationMode) var presentationMode
#FetchRequest var results: FetchedResults<NewGoal>
init(GTitle: String, Sum: Double){
self.GTitle = GTitle
self.Sum = Sum
let predicate = NSPredicate(format:"goalTitle == %#", GTitle)
self._results=FetchRequest(
entity: NewGoal.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \NewGoal.dateAdded, ascending: false)],
predicate: predicate,
animation: .easeInOut
)
}
var body: some View {
ZStack{
ForEach(results) { item in
RingShape(percent:(yournewSum/item.neededSum*100), startAngle: -90, drawnClockwise: false) /// Ring
.stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round))
.fill(AngularGradient(gradient: Gradient(colors: [.red, .pink, .red]), center: .center))
.frame(width: 155, height: 155)
HStack(alignment: .top){
Spacer()
Button(action: addSum) { ///BUTTON TO Update
Image(systemName: "gear")
}
.clipShape(Circle())
}
VStack(alignment: .trailing, spacing: 0.0){
Spacer()
Text("$\(yournewSum, specifier: "%.f")") /// Here is the data I want to change via Digital Crown and update
.font(.title3)
.overlay(
RoundedRectangle(cornerRadius: 7)
.stroke(Color.white, lineWidth: 2)
.opacity(isFocusedSum ? 1.0:0.0)
)
.focusable(true) { newState in isFocusedSum = newState}
.animation(.easeInOut(duration: 0.3), value: isFocusedSum)
.digitalCrownRotation(
$yournewSum,
from: 0,
through: Double((item.neededSum)),
by: 10,
sensitivity: .high)
Text("/ $\(item.neededSum, specifier: "%.f")") ///Here is the Double data I entered in AddGoalView
.font(.caption)
}
.frame(width: 200, height: 230)
.padding(.top, 7)
VStack(alignment: .center, spacing: 1.0){
Text(item.goalTitle ?? "Your Goal Name") ///Here is the String data I entered in AddGoalView
.foregroundColor(.gray)
}
.padding(.top, 200.0)
}
}
.padding([.top, .leading, .trailing], 5.0)
}
private func addSum(){
let goal = goalItem == nil ? NewGoal(context: context): goalItem
goal?.yourSum = yournewSum //// I am trying to update the Data here, but after running the func it creates a duplicate.
do{
try context.save()
presentationMode.wrappedValue.dismiss()
} catch let err{
print(err.localizedDescription)
}
}
You never give var goalItem: NewGoal? the initial value of the item you want to update.
try replacing this
RingView(GTitle: item.goalTitle ?? "", Sum: item.neededSum)
with
RingView(goalItem: item, GTitle: item.goalTitle ?? "", Sum: item.neededSum)
and of course your have to change your initializer for RingView to
init(goalItem: NewGoal? = nil, GTitle: String, Sum: Double){
and add to the initializer this line
self.goalItem = goalItem

Struggling with layout in SwiftUI on TVOS

I have a workable UI in TVOS using SwiftUI, but I can't figure out how to make it lay out properly.
Goals:
Screen is full screen, not inset in safe area
Album image is square, aspect ratio preserved, fills top-to-bottom
Album and artist text lays out comfortably, near the album art
import SwiftUI
struct ContentView: View {
#ObservedObject var ds = DataStore()
var body: some View {
HStack(alignment: .top) {
if (ds.currentRoom.albumImage != nil) {
Image(uiImage: ds.currentRoom.albumImage!)
.resizable()
.aspectRatio(contentMode: ContentMode.fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.black)
}
VStack(alignment: .leading, spacing: 8) {
Text(ds.currentRoom.artist ?? "?")
.font(.system(.title, design: .default))
.bold()
.foregroundColor(Color.gray)
.padding(.top, 100)
Text(ds.currentRoom.title ?? "?")
.font(.system(.headline, design: .default))
.foregroundColor(Color.gray)
.padding(.bottom, 100)
Button(action: { print ("pressed!" )} ) {
Image(systemName: "playpause")
.font(.system(.title, design: .default))
.foregroundColor(Color.gray)
.padding(30)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.green, lineWidth: 4)
)
}
}
}
.padding(20)
.background(Color.black)
.edgesIgnoringSafeArea(.all)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In case it helps, here are the relevant helper and DataSource methods:
The DataStore() class has this method:
import SwiftUI
import Combine
class CurrentRoom {
var artist: String?
var title: String?
var album: String?
var albumArtURI: String?
var absoluteAlbumArtURI: String?
var albumImage: UIImage?
}
class DataStore: ObservableObject {
#Published var currentRoom: CurrentRoom = CurrentRoom()
init() {
getCurrentRoom()
}
func getCurrentRoom() {
currentRoom.artist = "Eilen Jewell"
currentRoom.album = "Sundown over Ghost Town"
currentRoom.title = "Half-Broke Horse"
currentRoom.absoluteAlbumArtURI = "https://images-na.ssl-images-amazon.com/images/I/71mkKfTQD0L._SX425_.jpg"
currentRoom.albumImage = Api().getImageDataFromURI(UrlAsString: currentRoom.absoluteAlbumArtURI)
}
}
struct DataStore_Previews: PreviewProvider {
static var previews: some View {
/*#START_MENU_TOKEN#*/Text("Hello, World!")/*#END_MENU_TOKEN#*/
}
}
Finally:
class Api {
func getImageDataFromURI(UrlAsString: String?) -> UIImage? {
if let URI = UrlAsString {
if URI.starts(with: "http") {
if let url = URL(string: URI) {
let data = try? Data(contentsOf: url)
if let imageData = data {
return UIImage(data: imageData)
}
}
}
}
return nil
}
}
Goal:
You can either go with Asperis answer, or, if ContentMode.Fit is important to you (as you don't want to clip the image), just remove the width part of the frame modifier at your Image, and then add a Spacer behind the VStack, which will consume the rest:
struct ContentView: View {
#ObservedObject var ds = DataStore()
var body: some View {
HStack(alignment: .top) {
if (ds.currentRoom.albumImage != nil) {
Image(uiImage: ds.currentRoom.albumImage!)
.resizable()
.aspectRatio(contentMode: ContentMode.fit)
.frame(minHeight: 0, maxHeight: .infinity)
.background(Color.black)
}
VStack(alignment: .leading, spacing: 8) {
Text(ds.currentRoom.artist ?? "?")
.font(.system(.title, design: .default))
.bold()
.foregroundColor(Color.gray)
.padding(.top, 100)
Text(ds.currentRoom.title ?? "?")
.font(.system(.headline, design: .default))
.foregroundColor(Color.gray)
.padding(.bottom, 100)
Button(action: { print ("pressed!" )} ) {
Image(systemName: "playpause")
.font(.system(.title, design: .default))
.foregroundColor(Color.gray)
.padding(30)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.green, lineWidth: 4)
)
}
}
.padding(.leading, 10)
Spacer()
}
.padding(20)
.background(Color.black)
.edgesIgnoringSafeArea(.all)
}
}
This will result in this image:
Correct layout

Warning: Attempt to present * on * which is already presenting (null) SwiftUI

I am relatively new to iOS app development as well as SwiftUI and I am currently working on an app that allows the user to create a schedule by the week.
I recently modified the entity to also accept time inputs so that the lists organize themselves based on the time they were scheduled for. Here's a quick look:
Main List View
However, when I click on each item it sends me to the wrong event.
In UIKit I noticed this problem was mainly prevalent due to a call to multiple views being presented, however I could not see where I would be presenting multiple views.
Main List View
List {
ForEach(self.Events) { event in
if("\(event.dotw ?? "Unknown")" == self.name) {
Button(action: {
self.isEvent.toggle()
}) {
HStack {
Text("\(event.name ?? "Unknown")")
.foregroundColor(Color.white)
Spacer()
Text("\(event.time ?? Date(), formatter: Self.taskDateFormat)")
}
}
.sheet(isPresented: self.$isEvent){
EventView(eventName: "\(event.name ?? "Unknown")", description: "\(event.descript ?? "Unknown")", eventTime: (event.time ?? Date())).environment(\.managedObjectContext, self.managedObjectContext)
}
.buttonStyle(ItemButton())
}
}.onDelete { indexSet in
let deleteItem = self.Events[indexSet.first!]
self.managedObjectContext.delete(deleteItem)
do{
try self.managedObjectContext.save()
} catch {
print(error)
}
}
Button(action: {
self.isPresented.toggle()
}) {
HStack {
Text("new")
Spacer()
}
}
.buttonStyle(NewItemButton())
.sheet(isPresented:$isPresented) {
NewEventView(dotw: self.name).environment(\.managedObjectContext, self.managedObjectContext)
}
}
.onAppear {
UITableView.appearance().separatorStyle = .none
}.onDisappear {
UITableView.appearance().separatorStyle = .singleLine
}
.navigationBarHidden(true)
.navigationBarTitle("")
.edgesIgnoringSafeArea(.top)
}
.edgesIgnoringSafeArea(.top)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
This code:
.sheet(isPresented: self.$isEvent){
EventView(eventName: "\(event.name ?? "Unknown")", description: "\(event.descript ?? "Unknown")", eventTime: (event.time ?? Date())).environment(\.managedObjectContext, self.managedObjectContext)
}
Is where I am assuming the problem lies however I could find nothing wrong with it
Event View
struct EventView: View {
var eventName: String
var description: String
var eventTime: Date
var body: some View {
VStack {
VStack {
Image(systemName: "chevron.compact.down")
.foregroundColor(.white)
.imageScale(.large)
.padding()
HStack {
Text(eventName)
.foregroundColor(.white)
.font(.largeTitle)
.padding(.top, 15)
.padding(.leading, 20)
Spacer()
}
.padding(.bottom)
.frame(maxWidth:.infinity)
}
.background(Color.gray)
.shadow(color: Color.black.opacity(0.2), radius: 7, x: 3, y: 3)
.shadow(color: Color.white.opacity(0.7), radius: 4, x: -5, y: -5)
VStack(alignment:.leading) {
HStack {
Text(description)
.padding(.leading, 20)
.padding(.top, 30)
.foregroundColor(.gray)
Spacer()
}
Spacer()
}
.frame(maxWidth:.infinity,maxHeight:.infinity)
}
.edgesIgnoringSafeArea(.top)
}
}
Event Entity Initializer
import Foundation
import CoreData
import SwiftUI
public class Event:NSManagedObject,Identifiable {
#NSManaged public var name: String?
#NSManaged public var createdAt: Date?
#NSManaged public var descript: String?
#NSManaged public var dotw: String?
#NSManaged public var time: Date?
}
extension Event {
static func getAllEvents() -> NSFetchRequest<Event> {
let request: NSFetchRequest<Event> = NSFetchRequest<Event>(entityName: "Event")
let sortDescriptor = NSSortDescriptor(key: "time", ascending:true)
let sortDescriptor2 = NSSortDescriptor(key: "createdAt", ascending: true)
request.sortDescriptors = [sortDescriptor,sortDescriptor2]
return request
}
}
Are your Events identifiable? This helps SwiftUI knowing which is which:
struct Events: Identifiable {
var id = UUID()
var name: String
}
Might also help to put .id(event.id) in your ForEach block to identify it there too.

How to deal with master/detail CoreData between SwiftUI views

I'm dealing with issue how to pass parameter selected in master view to CoreData predicate in detail view. I have this master view
struct ContentView: View {
#State private var selectedCountry: Country?
#State private var showSetting = false
#FetchRequest(entity: Country.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Country.cntryName, ascending: true)]
) var countries: FetchedResults<Country>
var body: some View {
NavigationView {
VStack {
Form {
Picker("Pick a country", selection: $selectedCountry) {
ForEach(countries, id: \.self) { country in
Text(country.cntryName ?? "Error").tag(country as Country?)
}
}
if selectedCountry != nil {
Years(cntryName: selectedCountry?.cntryName! ?? "")
}
}
}
.navigationBarTitle("UNECE Data")
.navigationBarItems(trailing: Button("Settings", action: {
self.showSetting.toggle()
}))
}
.sheet(isPresented: $showSetting) {
SettingsView(showSetting: self.$showSetting)
}
}
}
where I use Picker to select country name (from CoreData entity Country and its attribute cntryName) and pass it as String value to Years view which is coded like this
struct Years: View {
var cntryName: String
#State private var selectedDataRow: Data?
#State private var result: NSFetchRequestResult
#FetchRequest(entity: Data.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Data.dataYear, ascending: true)],
predicate: NSPredicate(format: "dataCountry == %#", "UK"), animation: .default
) var data: FetchedResults<Data>
var body: some View {
Picker("Year", selection: $selectedDataRow) {
ForEach(data, id: \.self) { dataRow in
Text(dataRow.dataYear ?? "N/A")
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: CGFloat(UIScreen.main.bounds.width), height: CGFloat(100))
.clipped()
.onAppear() {
let request = NSFetchRequest<Data>(entityName: "Data")
request.sortDescriptors = [NSSortDescriptor(key: "dataYear", ascending: true)]
request.predicate = NSPredicate(format: "dataCountry == %#", self.cntryName)
do {
self.result = try context.fetch(request) as! NSFetchRequestResult
print(self.result)
} catch let error {
print(error)
}
}
}
}
It works fine with #FetchRequest and FetchedResults stored in var data but I'm wondering how to build predicate here based on passed country name. To overcome this I considered to use onAppear section and classic NSFetchRequest and NSFetchRequestResult which causes compiler error "'Years.Type' is not convertible to '(String, NSFetchRequestResult, FetchRequest) -> Years'" in the line
Years(cntryName: selectedCountry?.cntryName! ?? "")
of ContentView struct. Error disappear if I comment the line
#State private var result: NSFetchRequestResult
in Years struct but it obviously causes another error. So I'm lost in circle. What`s recommended practice here, please?
Thanks.
Finally I found the way thanks to this post SwiftUI use relationship predicate with struct parameter in FetchRequest
struct Years: View {
var request: FetchRequest<Data>
var result: FetchedResults<Data> {
request.wrappedValue
}
#State private var selectedDataRow: Data?
init(cntryName: String) {
self.request = FetchRequest(entity: Data.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Data.dataYear, ascending: true)],
predicate: NSPredicate(format: "dataCountry == %#", cntryName), animation: .default)
}
var body: some View {
VStack {
Picker("Year", selection: $selectedDataRow) {
ForEach(result, id: \.self) { dataRow in
Text(dataRow.dataYear ?? "N/A").tag(dataRow as Data?)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: CGFloat(UIScreen.main.bounds.width), height: CGFloat(100))
.clipped()
VStack(alignment: .leading, spacing: 10) {
HStack {
Text("Total polutation: ")
.alignmentGuide(.leading) { dimension in
10
}
if selectedDataRow != nil {
Text(String(describing: selectedDataRow!.dataTotalPopulation))
} else {
Text("N/A")
}
}
}}
}
}

Resources