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.
Related
I'm currently building a ToDo List App in SwiftUI. One feature that I'd really like to implement is the ability to sort your List manually, so I've integrated the functionality using a .onMove modifier on my ForEach loop populating my List, but I still had to toggle EditMode manually, so I set the EditMode of the list to be .active as follows:
import SwiftUI
struct ContentView: View {
#State private var items = ["1", "2", "3"]
#State var editMode: EditMode = .active
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text("Item \(item)")
}
.onMove(perform: { _, _ in })
}
.environment(\.editMode, $editMode)
}
}
But I'm not happy with this Implementation, as I still have to use the grip from EditMode, and it also breaks SwipeActions as well as Button functionality.
So how can I move List Items without using EditMode?
Based on Asperi's answer on this question I implemented drag and drop Gestures to fix that problem as follows:
struct ContentView: View {
#State var items = [Item(id: 1), Item(id: 2), Item(id: 3), Item(id: 4)]
#State private var dragging: Item?
var body: some View{
List{
ForEach(items){ item in
Text("Item \(item.id)")
.onDrag {
self.dragging = item
return NSItemProvider(object: NSString())
}
.onDrop(of: [UTType.text], delegate: DragDelegate(current: $dragging))
}
.onMove(perform: {_, _ in })
}
}
}
Using a DropDelegate implementation:
struct DragDelegate<Item: Equatable>: DropDelegate {
#Binding var current: Item?
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
current = nil
return true
}
}
Note: the Items now have to conform to Identifiable & Equatable so the minimal Implementation is:
struct Item: Identifiable, Equatable{
let id: Int
}
and you also need to import:
import UniformTypeIdentifiers
in order to use drag and drop functionality
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)
}
I have a Class "ActualCourse"
class ActualCourse : ObservableObject {
#Published var id : UUID?
#Published var CourseName : String = ""
}
And two Structs "Settings" and "Course"
struct Settings: View {
#State private var action : Int? = 0
#ObservedObject var objCourse = ActualCourse()
#State var courseId : UUID = UUID()
#State var list : [Course] = [] // list is not empty, I didn't show it to you to make the code lighter
init() {
objCourse.id = UUID()
}
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: Course(), tag: 1, selection: $action){}
List {
let count = list.count
ForEach(0 ..< count, id: \.self){ index in
HStack {
Image(systemName: "chevron.right.circle.fill")
.font(.title)
.onTapGesture {
objCourse.id = list[index].id
objCourse.nomCourse = list[index].nomCourse
print(objCourse.id!) // Id Appear when I click !
self.action = 1
}
But when I navigate to Course view, objCourse.id return nil
struct Settings: View {
#ObservedObject var objCourse = CourseActuelle()
....
.onAppear(){
print(self.objCourse.id!) // RETURN nil
}
What am I doing wrong ? I have to give a random UUID at the beginning because I can't find how to do in another way...
The CourseActuelle you're creating in the second view is independent of the one you're creating in the first view of which you're initializing the id value. If you want them to be the same you could use #EnvironmentObject in the second view and inject it in the first view or pass it in some other way.
I have following code in my SwiftUI app
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: \Country.cntryName) { country in
Text(country.cntryName ?? "Error").tag(country as Country?)
}
}
if selectedCountry != nil {
DetailView(cntryName: (selectedCountry?.cntryName!)!)
}
}
}
.navigationBarTitle("UNECE Data")
.navigationBarItems(trailing: Button("Settings", action: {
self.showSetting.toggle()
}))
}
.sheet(isPresented: $showSetting) {
SettingsView(showSetting: self.$showSetting)
}
}
}
I do CoreData Country entity update in SettingView and once app is back in ContentView I`d like to delete all items from the Picker and load fresh data. Code above duplicate items in the Picker - add new ones to old set.
I wanted to use a SwiftUI Picker for a complex type. I see the picker and I can select a value, but I never get the didSet output and category always stays nil. Any suggestions?
struct EntryView: View {
#State private var category: UUID? = UUID() {
didSet {
print("category changed to \(category!)")
}
}
#FetchRequest(
entity: Category.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Category.title, ascending: true)
]
) var categories: FetchedResults<Category>
var body: some View {
NavigationView {
Form {
Section {
Picker("Meter", selection: $category) {
ForEach(categories) { cat in
Text(cat.title ?? "")
}
}
}
}
}
}
}