Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Opening a modal sheet with dynamic content fails only on first attempt

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?

like image 806
Megagator Avatar asked Aug 24 '20 23:08

Megagator


1 Answers

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.

like image 139
Megagator Avatar answered Oct 20 '22 00:10

Megagator