Problem: When clicking on an item, it will be (un-)completed. When it's completed then ChildView should show GroupsView. However, that's not the case.
The logs are like this:
init, group Fruits - items not completed false - showItems false
body, group Fruits - items not completed false - showItems true
init, group Fruits - items not completed false - showItems false
init, group Fruits - items not completed false - showItems false
import SwiftUI
import SwiftData
@main
struct MainApp: App {
var body: some Scene {
WindowGroup {
SomeView()
}
.modelContainer(appContainer)
}
}
struct SomeView: View {
@Query private var items: [AItem]
var body: some View {
ParentView(items: items)
}
}
struct ParentView: View {
private var groupedItems: [GroupedAItems] = []
init(items: [AItem]) {
Dictionary(grouping: items) { $0.categoryName }
.forEach {
let groupedItems = GroupedAItems(categoryName: $0.key, items: $0.value)
self.groupedItems.append(groupedItems)
}
}
var body: some View {
ScrollView {
VStack(spacing: 15) {
ForEach(groupedItems, id: \.self.categoryName) { groupedItems in
ChildView(groupedItems)
}
}
}
}
}
struct ChildView: View {
public var groupedItems: GroupedAItems
@State private var showItems: Bool
init(_ groupedItems: GroupedAItems) {
self.groupedItems = groupedItems
self._showItems = State(initialValue: !groupedItems.completed)
print("init, group \(groupedItems.categoryName) - items not completed \(!groupedItems.completed) - showItems \(showItems)")
}
var body: some View {
print("body, group \(groupedItems.categoryName) - items not completed \(!groupedItems.completed) - showItems \(showItems)")
if showItems {
return AnyView(ItemsSampleView(items: groupedItems.items, onClick: { showItems = false }))
} else {
return AnyView(GroupsView(groupedItems: groupedItems, onClick: { showItems = true }))
}
}
}
struct ItemsSampleView: View {
public var items: [AItem]
public var onClick: () -> Void
private let gridColumns = [GridItem(.adaptive(minimum: CGFloat(70)))]
var body: some View {
VStack {
Button {
onClick()
} label: {
Image(systemName: "chevron.down")
}
Spacer()
LazyVGrid(columns: gridColumns) {
ForEach(items.sorted(by: {$0.name < $1.name})) { item in
Button {
item.completed.toggle()
} label: {
Text(item.name)
}
}
}
}
}
}
struct GroupsView: View {
public var groupedItems: GroupedAItems
public var onClick: () -> Void
var body: some View {
VStack {
Button {
onClick()
} label: {
Image(systemName: "chevron.down")
}
Spacer()
Text(groupedItems.categoryName)
}
}
}
@Model
final class AItem: Identifiable {
@Attribute(.unique) public var id: String
public var name: String
public var categoryName: String
public var completed = false
internal init(name: String, categoryName: String) {
self.id = UUID().uuidString
self.name = name
self.categoryName = categoryName
}
}
struct GroupedAItems {
var categoryName: String
var items: [AItem]
var completed: Bool {
items.filter { !$0.completed }.isEmpty
}
}
@MainActor
let appContainer: ModelContainer = {
do {
let container = try ModelContainer(for: AItem.self)
// Make sure the persistent store is empty. If it's not, return the non-empty container.
var itemFetchDescriptor = FetchDescriptor<AItem>()
itemFetchDescriptor.fetchLimit = 1
guard try container.mainContext.fetch(itemFetchDescriptor).count == 0 else { return container }
container.mainContext.insert(AItem(name: "Apple", categoryName: "Fruits"))
return container
} catch {
fatalError("Failed to create container")
}
}()
Post
Replies
Boosts
Views
Activity
Task:
A child view should show depending on a boolean flag the corresponding view. The flag is calculated from a model which is given by parent view.
Problem:
The flag is false in init, which is correct. However in body it's true, which is incorrect. Why value in body isn't consistent to init? Is there a race condition? The child view's is rendered thrice, that's another issue, but flag is in init and body as described before.
Parent view looks like this:
struct ParentView: View {
private var groupedItems: [GroupedItems] = []
init(items: [Item]) {
Dictionary(grouping: items) { $0.category.name }
.forEach {
let groupedItems = GroupedItems(categoryName: $0.key, items: $0.value)
self.groupedItems.append(groupedItems)
}
}
var body: some View {
ChildView(groupedItems)
}
}
Child view looks like this:
struct ChildView: View {
@State private var showItems: Bool
init(_ groupedItems: GroupedItems) {
self._showItems = State(initialValue: !groupedItems.completed)
}
var body: some View {
if showItems {
AView()
} else {
BView()
}
}
}
Model looks like this:
@Model
final class Item: Identifiable {
@Attribute(.unique) public var id: String
public var name: String
public var completed = false
}
struct GroupedItems {
var categoryName: String
var items: [Item]
var completed: Bool {
items.filter { !$0.completed }.isEmpty
}
}
struct Preview: View {
private static let NAME = "Test"
@Query(filter: #Predicate<Item> { $0.name == Preview.NAME})
private var items: [Item]
var body: some View {
MyView(name: Preview.NAME, items: items)
}
}
Compile error occurs: key path cannot refer to static member 'NAME'
How can a (static) constant be referred in member closure?