Post

Replies

Boosts

Views

Activity

SwiftUI: View is not rendered properly
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") } }()
1
0
286
Jul ’24
SwiftUI view is not updated properly
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 } }
3
0
445
Jul ’24