Reputation: 424
The goal is to pass dynamic content into a single modal, as if it was a detailed view:
import SwiftUI
struct Item {
let number: String
let id = UUID()
}
class ItemSet: ObservableObject {
@Published var collection: [Item]
init() {
self.collection = []
for index in 1...100 {
self.collection.append(Item(number: "\(index)"))
}
}
}
struct ContentView: View {
@ObservedObject var items: ItemSet
@State private var selectedItem: Item?
@State private var showingFull = false
init() {
self.items = ItemSet()
self.selectedItem = nil
}
var columns = [
GridItem(.adaptive(minimum: 150), spacing: 5.0)
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(items.collection, id: \.id) {item in
Text(item.number)
.frame(height: 100)
.onTapGesture {
self.selectedItem = item
self.showingFull = true
}
.sheet(isPresented: $showingFull) {
if let item = selectedItem {
Text(item.number)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
For some reason, the first time you tap a cell, the modal is empty, as if the state was not updated before rendering the modal content, but any time after that works as intended. Am I missing something obvious or should I file a radar?
Upvotes: 11
Views: 3361
Reputation: 762
encountered this same problem and solved it by switching to sheet(item)
and removing the boolean check to show sheet
struct TextItem: View {
@State let item: Item
var body: some View {
Text(item.number)
.frame(height: 100)
.onTapGesture {
self.showingFull = true
}
.sheet($item) {
Text($0.number)
}
}
}
Upvotes: 1
Reputation: 424
One possible solution I found is moving the sheet and state to be their own structs:
struct ContentView: View {
@ObservedObject var items: ItemSet
init() {
self.items = ItemSet()
}
var columns = [
GridItem(.adaptive(minimum: 150), spacing: 5.0)
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(items.collection, id: \.id) {item in
TextItem(item: item)
}
}
}
}
}
struct TextItem: View {
let item: Item
@State private var showingFull = false
var body: some View {
Text(item.number)
.frame(height: 100)
.onTapGesture {
self.showingFull = true
}
.sheet(isPresented: $showingFull) {
Text(item.number)
}
}
}
I don't know how "correct" this solution is in terms of being proper SwiftUI, but I haven't noticed any performance issues when used inside a more complex and heavy layout.
Upvotes: 5
Reputation: 257653
This is multi-sheets conflict as you attached same sheet to every cell in ForEach
with binding to single state, so on click they are all activated at once. The solution is to move sheet out of ForEach
Here is a corrected part. Tested with Xcode 12 / iOS 14
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(items.collection, id: \.id) {item in
Text(item.number)
.frame(height: 100)
.onTapGesture {
self.selectedItem = item
self.showingFull = true
}
}
}
}
.sheet(isPresented: $showingFull) { // << here !!
if let item = selectedItem {
Text(item.number)
}
}
}
Upvotes: 3