How can I fetch value from CoreData to Variable? - swiftui

CoreData:
Item
userName
userSurname
Fetch request
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
How can I apply last added userName in Items value to
var X ?

Thanks to #vadian
var x = items.last?.userName ?? ""

Related

SwiftUI - Use value passed from previous view in predicate

So I'm new to using SwiftUI. Normally I'd have the fetch request in the viewdidload method but not sure how to go about it now as the method doesn't seem to have an equivalent.
So I have a view where I'm passing a variable to another view like so
NavigationLink(destination: ItemAddView(series: series)) {
Label("Add Item", systemImage: "plus")
}
I'm now wanting to use this variable in a predicate on a fetch request.
The fetch request is setup as follows
struct ItemAddView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.name, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var series: Series
#State private var searchText = ""
var body: some View {
List {
ForEach(items) { item in
NavigationLink(destination: ItemDetailView(item: item)) {
Image(item.mainImage ?? "")
.renderingMode(.original)
.resizable()
.scaledToFit()
.frame(width: 100.0)
Text("\(item.name ?? "Error")")
}
}
}
.searchable(text: $searchText)
.onSubmit(of: .search) {
items.nsPredicate = NSPredicate(format: "series = %#", series)
if(!searchText.isEmpty) {
items.nsPredicate = NSPredicate(format: "series = %# AND amount = 0 AND name CONTAINS[cd] %#", series, searchText)
}
}
.onChange(of: searchText) { _ in
items.nsPredicate = NSPredicate(format: "series = %#", series)
if(!searchText.isEmpty) {
items.nsPredicate = NSPredicate(format: "series = %# AND amount = 0 AND name CONTAINS[cd] %#", series, searchText)
}
}
.navigationBarTitle("\(series.name ?? "Error") Add Items", displayMode: .inline)
.onAppear() {
items.nsPredicate = NSPredicate(format: "series = %# AND amount = 0", series)
}
}
}
I get an error if I use the predicate here
Cannot use instance member 'series' within property initializer; property initializers run before 'self' is available
I've taken to using the predicate in the onAppear method as such
.onAppear() {
items.nsPredicate = NSPredicate(format: "series = %# AND amount > 0", series)
}
But the issue is I'm seeing the full list before it then goes to the filtered request. What's the correct way of doing this?
You just need to declare the variable in the header. You can then do the initialization in the init() and use the variable that is passed in. If you aren't subsequently needing series, you do not need to have any variable in the view to assign it to. Also, as you didn't post your full view, I had to guess at the type of series.
struct ItemAddView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest private var items: FetchedResults<Item>
init(series: Series) {
let request = Item.fetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Item.name, ascending: true)], predicate: NSPredicate(format: "series = %# AND amount > 0", series), animation: .default)
_items = FetchRequest(fetchRequest: request)
// Initialize anything else that is necessary
}
...
}

Detail list not appearing when row tapped in previous list

I have a list of languages, when a language is tapped in that list it should move to another list showing categories of that language. I'm using NavigationLink to navigate between menus.
import SwiftUI
import CoreData
struct WordsView: View {
#State private var selectAll = false
#State private var language: Language?
#Binding var selectedTab: Int
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(entity: Language.entity(), sortDescriptors: []) var languages: FetchedResults<Language>
#FetchRequest(entity: SubCategory.entity(), sortDescriptors: []) var subCategories: FetchedResults<SubCategory>
#FetchRequest(entity: Thing.entity(), sortDescriptors: []) var things: FetchedResults<Thing>
var body: some View {
NavigationView {
ZStack {
List {
ForEach(languages, id: \.self) { language in
NavigationLink(destination: SubCategoryView(selectedTab: $selectedTab, language: language)){
LanguageRowView(language: language, selectAll: selectAll)
}
}
}
}
}
}
}
import SwiftUI
import CoreData
struct SubCategoryView: View {
#State private var subCategory: SubCategory?
#Binding var selectedTab: Int
#ObservedObject var homeworkColor = HomeworkTintColor()
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(entity: SubCategory.entity(), sortDescriptors: []) var subCategories: FetchedResults<SubCategory>
#FetchRequest(entity: Language.entity(), sortDescriptors: []) var languages: FetchedResults<Language>
let language: Language
var body: some View {
ZStack {
List {
ForEach(language.subCategory?.allObjects as? [SubCategory] ?? [], id: \.self) { subCategory in
NavigationLink(destination: ThingView(language: language, subCategory: subCategory, selectedTab: $selectedTab)) {
SubCategoryRowView(subCategory: subCategory, selectAll: selectAll).
}
}
}
}
}
}
I know the language parameter passed into SubCategoryView contains the language the user taps on, as I've been able to use it elsewhere in SubCategoryView.
In SubCategoryView, when I use the passed in value 'language' to access a database relationship 'subCategory' (which contains all the subcategories I want to display in a second list), the list does not appear at all.

Cannot use instance member 'videoName' within property initializer; property initializers run before 'self' is available

I'm quite new to SwiftUI and I'm running into this problem as I'm trying to display a video using videoName from Model.
In the player = AVPlayer(...)(line 4), instead of finding the resource by the string "squats", I want to use videoName from Model. If I replace them I get the error
Cannot use instance member 'videoName' within property initializer; property initializers run before 'self' is available
Can anyone please help me?
Here is my code:
struct ExercisingSessionView: View {
let exerciseName: String
let videoName: String
#State var player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "squats", ofType: "mov")!))
#State var isplaying = false
#State var showcontrols = false
var body: some View {
CustomVideoPlayer(player: $player)
.frame(width: 390, height: 219)
.onTapGesture {
self.showcontrols = true
}
}
struct CustomVideoPlayer : UIViewControllerRepresentable {
#Binding var player: AVPlayer
func makeUIViewController(context: UIViewControllerRepresentableContext<CustomVideoPlayer>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<CustomVideoPlayer>) {
}
}
}
Option 1:
Create an initializer for your View that creates your #State initial value:
struct ExercisingSessionView: View {
let exerciseName: String
let videoName: String
#State var player : AVPlayer
#State var isplaying = false
#State var showcontrols = false
init(exerciseName: String, videoName: String) {
self.exerciseName = exerciseName
self.videoName = videoName
self._player = State(initialValue: AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoName, ofType: "mov")!)))
}
var body: some View {
CustomVideoPlayer(player: $player)
.frame(width: 390, height: 219)
.onTapGesture {
self.showcontrols = true
}
}
}
The downside to this is if ExercisingSessionView gets initialized often (even if it doesn't get actually re-rendered to the view hierarchy), you're doing heavy lifting inside init, which is generally a pretty bad idea for performance.
Option 2:
Declare player as optional and load the initial value in onAppear:
struct ExercisingSessionView: View {
let exerciseName: String
let videoName: String
#State var player : AVPlayer?
#State var isplaying = false
#State var showcontrols = false
var body: some View {
Group {
if let player = player {
CustomVideoPlayer(player: player)
.frame(width: 390, height: 219)
.onTapGesture {
self.showcontrols = true
}
}
}.onAppear {
player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoName, ofType: "mov")!))
}
}
}
struct CustomVideoPlayer : UIViewControllerRepresentable {
var player: AVPlayer
func makeUIViewController(context: UIViewControllerRepresentableContext<CustomVideoPlayer>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<CustomVideoPlayer>) {
}
}
This avoids the issue in option 1 because onAppear will only be called once.
Note that here, I've made player inside CustomVideoPlayer a regular, non-binding property -- because AVPlayer is an class, passed by reference, there's no reason to have a #Binding with it.
You could just pass your name down to your custom view.
struct ExercisingSessionView: View {
let exerciseName: String
let videoName: String
#State var isplaying = false
#State var showcontrols = false
var body: some View {
CustomVideoPlayer(player: videoName)
.frame(width: 390, height: 219)
.onTapGesture {
self.showcontrols = true
}
}
struct CustomVideoPlayer : UIViewControllerRepresentable {
let videoName: String
func makeUIViewController(context: UIViewControllerRepresentableContext<CustomVideoPlayer>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoName, ofType: "mov")!))
controller.showsPlaybackControls = false
return controller
}
}
I'm assuming the error is happening on this line, when you replace squats with videoName:
#State var player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "squats", ofType: "mov")!))
The reason you're getting the error is because videoName is not defined when you call this line of code.
To resolve this, you can set your properties in a custom init method that you define. Like this:
let videoName: String
#State var player: AVPlayer
init(exerciseName: String, videoName: String) {
self.exerciseName = exerciseName
self.videoName = videoName
self.player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoName, ofType: "mov")!))
}
Keep in mind, you'll have to add

Delete Func Deletes All Items in List - SwiftUI

When I select one of the items inside my list from my lists it only deletes the selected item.
But when I list all the lists and their reminders inside the AllView it deletes all of the reminders inside the list.
How can I overcome that problem?
To tell my problem clearly I have two videos that show both cases.
First Case
Second case
it is my delete button inside ReminderCell view
struct ReminderCell: View {
#Environment(\.managedObjectContext) private var viewContext
var reminder: CDReminder
#State var isSelected: Bool
Button(action: {
self.isSelected = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1){
deleteReminder(at: Int(reminder.index))
}
and again inside the ReminderCell I have deleteReminder func
func deleteReminder(at offsets: Int) {
viewContext.delete(reminder)
PersistenceController.shared.saveContext()
}
Inside the AllView I am calling listDetailCell as
struct AllView: View {
#State var title = ""
#State var note = ""
#State var releaseDate = Date()
#ObservedObject var list : CDListModel
#State var selectedList = CDListModel()
#Environment(\.presentationMode) var mode
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest var lists: FetchedResults<CDListModel>
init(){
list = CDListModel()
let request: NSFetchRequest<CDListModel> = CDListModel.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \CDListModel.name, ascending: true)]
request.entity = CDListModel.entity()
_lists = FetchRequest(fetchRequest: request)
}
var body: some View {
List{
ForEach(lists, id: \.self) { list in
ListDetailCell(list: list)
}
}
}
My ListDetailCell
struct ListDetailCell: View {
#State var title = ""
#ObservedObject var list : CDListModel
#State var selectedList: CDListModel!
#State var isAddReminderTapped = false
#Environment(\.managedObjectContext) private var viewContext
var body: some View {
VStack(alignment: .leading){
Text(list.text ?? "")
ForEach((list.reminders?.allObjects as! [CDReminder]).indices , id: \.self) { reminderIndex in
ReminderCell(reminder: (list.reminders?.allObjects[reminderIndex]) as! CDReminder, isSelected: false, selectedList: $selectedList, onComplete: {})
}
}
}
}
Your delete function is wrong.
Here you are passing an offsets: Int. But you are never using that offsets inside the function. You are just deleting the whole reminder.
func deleteReminder(at offsets: Int) {
viewContext.delete(reminder)
PersistenceController.shared.saveContext()
}
Somehow using ForEach inside the List was causing this problem in AllView.
When I change the body of the AllView like below my problem disappeared.
NavigationView {
ScrollView {
VStack{
HStack{
Text("Tumu")
.font(.system(size: 40, weight: .bold, design: .rounded))
.foregroundColor(.gray)
Spacer()
}
.padding(.leading)
ForEach(lists, id: \.self) { list in
ListDetailCell(list: list)
}

How to pass State variables as parameters to a model class which is of ObservableObject type?

I want to save some data from a SwiftUI view to a Model so that I can use these data into another SwiftUI view. However, I came up with some error when I try to call the Model class and pass all the data as parameters. The error says:
Cannot use instance member 'expectedEndDate' within property initializer; property initializers run before 'self' is available"
Here is my SearchBikeDataModel() code:
import Foundation
class SearchBikeDataModel: ObservableObject {
#Published var startDate: Date = Date()
#Published var startTime: Date = Date()
#Published var endDate: Date = Date()
#Published var endTime: Date = Date()
#Published var bikeType: String = ""
#Published var collectionPoint: String = ""
#Published var returnPoint: String = ""
init(selectedStartDate: Date, selectedStartTime: Date, selectedEndDate: Date, selectedEndTime: Date, bikeType: String, collectionPoint: String, returnPoint: String) {
self.startDate = selectedStartDate
self.startTime = selectedStartTime
self.endDate = selectedEndDate
self.endTime = selectedEndTime
self.bikeType = bikeType
self.collectionPoint = collectionPoint
self.returnPoint = returnPoint
}
}
And here is the code where I try to pass data as parameters:
import SwiftUI
struct BikeSearchFormView: View {
#Binding var isDateTimeShown: Bool
#Binding var isEndDateTimePickerShown: Bool
#State var expectedStartDate = Date()
#State var expectedStartTime = Date()
#State var expectedEndDate = Date()
#State var expectedEndTime = Date()
#State var isBikeTypePickerExpand: Bool = false
#State var isDropOffPointPickerExpand: Bool = false
#State var isPickUpPointPickerExpand: Bool = false
#State var selectedBikeType: String = "BIKE TYPE"
#State var selectedDropOffPoint: String = "DROP OFF POINT"
#State var selectedPickUpPoint: String = "PICKUP POINT"
#State var findBikeError: String = ""
#State var isActive: Bool = false
#ObservedObject var bikeTypeViewModel = VehicleTypeViewModel()
#ObservedObject var findBikeViewModel = FindBikeViewModel()
#ObservedObject var dataModel = SearchBikeDataModel(selectedStartDate: expectedStartDate, selectedStartTime: expectedStartTime, selectedEndDate: expectedEndDate, selectedEndTime: expectedEndTime, bikeType: selectedBikeType, collectionPoint: selectedPickUpPoint, returnPoint: selectedDropOffPoint)
var body: some View {
Text("Hello, World")
}
}
I have omitted codes of my UI as the question is just about am I following the right way to pass the data into parameters.