How to trigger SwiftUI DatePicker Programmatically? - ios

As Image below shows, if you type the date "Jan 11 2023", it presents the date picker. What I wanna to achieve is have a button elsewhere, when that button is clicked, present this date picker automatically.
Does anyone know if there is a way to achieve it?
DatePicker("Jump to", selection: $date, in: dateRange, displayedComponents: [.date])
Below is a test on #rob mayoff's answer. I still couldn't
figure out why it didn't work yet.
I tested on Xcode 14.2 with iPhone 14 with iOS 16.2 simulator, as well as on device. What I noticed is that although the triggerDatePickerPopover() is called, it never be able to reach button.accessibilityActivate().
import SwiftUI
struct ContentView: View {
#State var date: Date = .now
let dateRange: ClosedRange<Date> = Date(timeIntervalSinceNow: -864000) ... Date(timeIntervalSinceNow: 864000)
var pickerId: String { "picker" }
var body: some View {
VStack {
DatePicker(
"Jump to",
selection: $date,
in: dateRange,
displayedComponents: [.date]
)
.accessibilityIdentifier(pickerId)
Button("Clicky") {
triggerDatePickerPopover()
print("Clicky Triggered")
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension NSObject {
func accessibilityDescendant(passing test: (Any) -> Bool) -> Any? {
if test(self) { return self }
for child in accessibilityElements ?? [] {
if test(child) { return child }
if let child = child as? NSObject, let answer = child.accessibilityDescendant(passing: test) {
return answer
}
}
for subview in (self as? UIView)?.subviews ?? [] {
if test(subview) { return subview }
if let answer = subview.accessibilityDescendant(passing: test) {
return answer
}
}
return nil
}
}
extension NSObject {
func accessibilityDescendant(identifiedAs id: String) -> Any? {
return accessibilityDescendant {
// For reasons unknown, I cannot cast a UIView to a UIAccessibilityIdentification at runtime.
return ($0 as? UIView)?.accessibilityIdentifier == id
|| ($0 as? UIAccessibilityIdentification)?.accessibilityIdentifier == id
}
}
func buttonAccessibilityDescendant() -> Any? {
return accessibilityDescendant { ($0 as? NSObject)?.accessibilityTraits == .button }
}
}
extension ContentView {
func triggerDatePickerPopover() {
if
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = scene.windows.first,
let picker = window.accessibilityDescendant(identifiedAs: pickerId) as? NSObject,
let button = picker.buttonAccessibilityDescendant() as? NSObject
{
print("triggerDatePickerPopover")
button.accessibilityActivate()
}
}
}
Update 2:
I followed up the debug instruction. It seems that with exact same code. My inspector are missing the accessibility identifier. Not knowing why.... feels mind buggingly now.
Here is a link to download the project https://www.icloud.com/iclouddrive/040jHC0jwwJg3xgAEvGZShqFg#DatePickerTest
Update 3:
#rob mayoff's solution is brilliant! For anyone reading. If it didn't work in your case, just wait. It's probably just due to device or simulator getting ready for accessibility.

UPDATE (2)
In retrospect, my original solution using the accessibility API is a bit risky since Apple could change the accessibility structure of DatePicker in the future. I should have taken a better look at the DatePicker documentation first and noticed the .datePickerStyle(.graphical) modifier, which lets you directly use the calendar view shown in the popover.
Since SwiftUI doesn't support custom popovers on iPhone (the .popover modifier shows a sheet instead), a fairly simple solution is to put a graphical DatePicker directly into your VStack, and show/hide it with any number of buttons. You can style the buttons to look like a default DatePicker button.
Here's a fully-worked example. It looks like this:
And here's the code:
struct ContentView: View {
#State var date = Date.now
#State var isShowingDatePicker = false
let dateRange: ClosedRange<Date> = Date(timeIntervalSinceNow: -864000) ... Date(timeIntervalSinceNow: 864000)
private var datePickerId: String { "picker" }
var body: some View {
ScrollViewReader { reader in
ScrollView(.vertical) {
VStack {
VStack {
HStack {
Text("Jump to")
Spacer()
Button(
action: { toggleDatePicker(reader: reader) },
label: { Text(date, format: Date.FormatStyle.init(date: .abbreviated)) }
)
.buttonStyle(.bordered)
.foregroundColor(isShowingDatePicker ? .accentColor : .primary)
}
VStack {
DatePicker("Jump to", selection: $date, in: dateRange, displayedComponents: .date)
.datePickerStyle(.graphical)
.frame(height: isShowingDatePicker ? nil : 0, alignment: .top)
.clipped()
.background {
RoundedRectangle(cornerRadius: 8, style: .continuous)
.foregroundColor(Color(UIColor.systemBackground))
.shadow(radius: 1)
}
}
.padding(.horizontal, 8)
}.id(datePickerId)
filler
Button("Clicky") { toggleDatePicker(true, reader: reader) }
}
}
}
.padding()
}
private func toggleDatePicker(_ show: Bool? = nil, reader: ScrollViewProxy) {
withAnimation(.easeOut) {
isShowingDatePicker = show ?? !isShowingDatePicker
if isShowingDatePicker {
reader.scrollTo(datePickerId)
}
}
}
private var filler: some View {
HStack {
Text(verbatim: "Some large amount of content to make the ScrollView useful.\n\n" + Array(repeating: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", count: 4).joined(separator: "\n\n"))
.lineLimit(nil)
Spacer()
}
}
}
ORIGINAL
SwiftUI doesn't provide a direct way to programmatically trigger the calendar popover.
However, we can do it using the accessibility API. Here's what my test looks like:
You can see that the calendar popover opens from clicks on either the ‘Clicky’ button or the date picker itself.
First, we need a way to find the picker using the accessibility API. Let's assign an accessibility identifier to the picker:
struct ContentView: View {
#State var date: Date = .now
let dateRange: ClosedRange<Date> = Date(timeIntervalSinceNow: -864000) ... Date(timeIntervalSinceNow: 864000)
var pickerId: String { "picker" }
var body: some View {
VStack {
DatePicker(
"Jump to",
selection: $date,
in: dateRange,
displayedComponents: [.date]
)
.accessibilityIdentifier(pickerId)
Button("Clicky") {
triggerDatePickerPopover()
}
}
.padding()
}
}
Before we can write triggerDatePickerPopover, we need a function that searches the accessibility element tree:
extension NSObject {
func accessibilityDescendant(passing test: (Any) -> Bool) -> Any? {
if test(self) { return self }
for child in accessibilityElements ?? [] {
if test(child) { return child }
if let child = child as? NSObject, let answer = child.accessibilityDescendant(passing: test) {
return answer
}
}
for subview in (self as? UIView)?.subviews ?? [] {
if test(subview) { return subview }
if let answer = subview.accessibilityDescendant(passing: test) {
return answer
}
}
return nil
}
}
Let's use that to write a method that searches for an element with a specific id:
extension NSObject {
func accessibilityDescendant(identifiedAs id: String) -> Any? {
return accessibilityDescendant {
// For reasons unknown, I cannot cast a UIView to a UIAccessibilityIdentification at runtime.
return ($0 as? UIView)?.accessibilityIdentifier == id
|| ($0 as? UIAccessibilityIdentification)?.accessibilityIdentifier == id
}
}
}
I found, in testing, that even though UIView is documented to conform to the UIAccessibilityIdentification protocol (which defines the accessibilityIdentifier property), casting a UIView to a UIAccessibilityIdentification does not work at runtime. So the method above is a little more complex than you might expect.
It turns out that the picker has a child element which acts as a button, and that button is what we'll need to activate. So let's write a method that searches for a button element too:
func buttonAccessibilityDescendant() -> Any? {
return accessibilityDescendant { ($0 as? NSObject)?.accessibilityTraits == .button }
}
And at last we can write the triggerDatePickerPopover method:
extension ContentView {
func triggerDatePickerPopover() {
if
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = scene.windows.first,
let picker = window.accessibilityDescendant(identifiedAs: pickerId) as? NSObject,
let button = picker.buttonAccessibilityDescendant() as? NSObject
{
button.accessibilityActivate()
}
}
}
UPDATE
You say you're having problems with my code. Since it's working for me, it's hard to diagnose the problem. Try launching the Accessibility Inspector (from Xcode's menu bar, choose Xcode > Open Developer Tool > Accessibility Inspector). Tell it to target the Simulator, then use the right-angle-bracket button to step through the accessibility elements until you get to the DatePicker. Then hover the mouse over the row in the inspector's Hierarchy pane and click the right-arrow-in-circle. It ought to look like this:
Notice that the inspector sees the date picker's identifier, “picker”, as set in the code, and that the picker has a button child in the hierarchy. If yours looks different, you'll need to figure out why and change the code to match.
Stepping through accessibilityDescendant method and manually dumping the children (e.g. po accessibilityElements and po (self as? UIView)?.subviews) may also help.

Related

Laggy typing and cursor jumps with TextField?

My app uses TextFields everywhere to modify CoreData entities' String attributes. They work very poorly - typing a space or getting an auto correct event seems to make the cursor jump to the end of the window. Keystrokes are missed and the whole experience is laggy. TextEditors, on the other hand, work fine. The behavior doesn't appear on the simulator, only on (multiple) real devices.
What am I doing wrong here? Am I using TextFields wrong?
Code is below, it's basically the starter Xcode app with a "text: String?" attribute added to the "item" CoreData entity.
struct Detail: View {
#ObservedObject var item: Item
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $item.text ?? "")
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $item.text ?? "")
}, header: {
Text("TextEditor")
})
}
}
}
}
// Optional binding used
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}
Update:
I ended up just putting the TextFields into a subview and then writing their value back to the NSManagedObject via a binding every time the value changes.
I have no idea why, but this fixes the problem for me.
struct CustomTextField: View {
#Binding var string: String?
#State var localString: String
let prompt: String
init(string: Binding<String?>, prompt: String) {
_string = string
_localString = State(initialValue: string.wrappedValue ?? "")
self.prompt = prompt
}
var body: some View {
TextField(prompt, text: $localString, axis: .vertical)
.onChange(of: localString, perform: { _ in
string = localString
})
}
}
Example of using onSubmit, which does not cause CoreData to save the data on every input by the keyboard.
struct Detail: View {
#ObservedObject var item: Item
#State var text: String = "" // for starting with an empty textfield
// Alternative if you want the data from item:
// #State var text: String = item.text.wrappedValue // This only works if text is a binding.
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextEditor")
})
}
}
}
}
If that does not help, it would be nice to know how Item looks like.

The UI styling of Button is not displayed after being clicked

I am new to SwiftUI and trying doing wrap-up challenge.
I do not know why when I pass a list as Environment Object, change the element's attribute of the list to update the UI of the button, the button cannot display the change despite its attribute has been changed successfully.
Here is my project structure: https://i.stack.imgur.com/7KEaK.png
Main file: BookLibraryAppApp
import SwiftUI
#main
struct BookLibraryAppApp: App {
var body: some Scene {
WindowGroup {
BookListView()
.environmentObject(BookModel())
}
}
}
Model file: Book
import Foundation
class Book : Decodable, Identifiable {
var id = 1
var title = "Title"
var author = "Author"
var content = ["I am a test book."]
var isFavourite = false
var rating = 2
var currentPage = 0
}
On view model file: BookModel
import Foundation
class BookModel : ObservableObject {
#Published var books = [Book]()
init() {
books = getLoadData()
}
func getLoadData() -> [Book] {
let fileName = "Data"
let fileExtension = "json"
var books = [Book]()
// Get link to data file
let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension)
guard url != nil else {
print("Could not retrieve category data: \(fileName).\(fileExtension) not found.")
return books
}
do {
// Decode the data and return it
let data = try Data(contentsOf: url!)
books = try JSONDecoder().decode([Book].self, from: data)
return books
} catch {
print("Error retrieving category data: \(error.localizedDescription)")
}
return books
}
func updateFavourite(forId: Int) {
if let index = books.firstIndex(where: { $0.id == forId }) {
books[index].isFavourite.toggle()
}
}
func getBookFavourite(forId: Int) -> Bool {
if let index = books.firstIndex(where: { $0.id == forId }) {
return books[index].isFavourite
}
return false
}
}
On View folder:
BookListView
import SwiftUI
struct BookListView: View {
#EnvironmentObject var model : BookModel
var body: some View {
NavigationView {
ScrollView {
LazyVStack(spacing: 50) {
ForEach(model.books, id: \.id) {
book in
NavigationLink {
BookPreviewView(book: book)
} label: {
BookCardView(book: book)
}
}
}
.padding(25)
}
.navigationTitle("My Library")
}
}
}
struct BookListView_Previews: PreviewProvider {
static var previews: some View {
BookListView()
.environmentObject(BookModel())
}
}
On smaller view: BookCardView
import SwiftUI
struct BookCardView: View {
var book : Book
var body: some View {
ZStack {
// MARK: container
Rectangle()
.cornerRadius(20)
.foregroundColor(.white)
.shadow(color: Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.5), radius: 10, x: -5, y: 5)
// MARK: card book content
VStack (alignment: .leading, spacing: 9) {
// MARK: title and favourite
HStack {
Text(book.title)
.font(.title2)
.bold()
Spacer()
if (book.isFavourite) {
// Image(systemName: "star.fill")
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 30)
// .foregroundColor(.yellow)
}
}
// MARK: author
Text(book.author)
.font(.subheadline)
.italic()
// MARK: image
Image("cover\(book.id)")
.resizable()
.aspectRatio(contentMode: .fit)
}
.padding(.horizontal, 35)
.padding(.vertical, 30)
}
}
}
struct BookCardView_Previews: PreviewProvider {
static var previews: some View {
BookCardView(book: BookModel().books[0])
}
}
On smaller view: BookPreviewView
import SwiftUI
struct BookPreviewView: View {
#EnvironmentObject var model : BookModel
// #State var select =
var book : Book
var body: some View {
VStack {
// MARK: banner
Text("Read Now!")
Spacer()
// MARK: cover image
Image("cover\(book.id)")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250)
Spacer()
// MARK: bookmark
Text("Mark for later")
Button {
// destination
self.model.updateFavourite(forId: book.id)
// self.select.toggle()
print(model.getBookFavourite(forId: book.id))
} label: {
// label
Image(systemName: self.book.isFavourite ? "star.fill" : "star")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 28)
.foregroundColor(.yellow)
}
Spacer()
// MARK: rating
Text("Rate Amazing Words")
Spacer()
}
}
}
struct BookPreviewView_Previews: PreviewProvider {
static var previews: some View {
BookPreviewView(book: BookModel().books[0])
}
}
Here is Data.json file:
[
{
"title": "Amazing Words",
"author": "Sir Prise Party",
"isFavourite": true,
"currentPage": 0,
"rating": 2,
"id": 1,
"content": [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eu congue lacus",
"ac laoreet felis. Integer eros tortor, blandit id magna non, pharetra sodalesurna."
]},
{
"title": "Text and More",
"author": "Sir Vey Sample",
"isFavourite": false,
"currentPage": 0,
"rating": 2,
"id": 3,
"content": [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eu congue lacus",
"ac laoreet felis. Integer eros tortor, blandit id magna non, pharetra sodalesurna.
]}
]
When I try to click the Yellow start on the BookPreviewView, instead of changing from "star" to "star.fill", it shows nothing. May I ask what's wrong with my code?
Thank you very much!
This is a pretty simple error. The #Published property wrapper will send a notification to the view to update itself as soon as its value changes.
But in your case this never happens. You defined Book as a class (reference type), so changing one of its property doesn´t force the array (valuetype) to change, so #Published doesn´t pick up the change.
Two solutions here:
If you insist on keeping the class use:
func updateFavourite(forId: Int) {
if let index = books.firstIndex(where: { $0.id == forId }) {
objectWillChange.send() // add this
books[index].isFavourite.toggle()
}
}
this will send the notification by hand.
the prefered solution should be to make your model Book a struct and it will pick up the changes.

Why does this SwiftUI List require an extra objectWillChange.send?

Here is a simple list view of "Topic" struct items. The goal is to present an editor view when a row of the list is tapped. In this code, tapping a row is expected to cause the selected topic to be stored as "tappedTopic" in an #State var and sets a Boolean #State var that causes the EditorV to be presented.
When the code as shown is run and a line is tapped, its topic name prints properly in the Print statement in the Button action, but then the app crashes because self.tappedTopic! finds tappedTopic to be nil in the EditTopicV(...) line.
If the line "tlVM.objectWillChange.send()" is uncommented, the code runs fine. Why is this needed?
And a second puzzle: in the case where the code runs fine, with the objectWillChange.send() uncommented, a print statement in the EditTopicV init() shows that it runs twice. Why?
Any help would be greatly appreciated. I am using Xcode 13.2.1 and my deployment target is set to iOS 15.1.
Topic.swift:
struct Topic: Identifiable {
var name: String = "Default"
var iconName: String = "circle"
var id: String { name }
}
TopicListV.swift:
struct TopicListV: View {
#ObservedObject var tlVM: TopicListVM
#State var tappedTopic: Topic? = nil
#State var doEditTappedTopic = false
var body: some View {
VStack(alignment: .leading) {
List {
ForEach(tlVM.topics) { topic in
Button(action: {
tappedTopic = topic
// why is the following line needed?
tlVM.objectWillChange.send()
doEditTappedTopic = true
print("Tapped topic = \(tappedTopic!.name)")
}) {
Label(topic.name, systemImage: topic.iconName)
.padding(10)
}
}
}
Spacer()
}
.sheet(isPresented: $doEditTappedTopic) {
EditTopicV(tlVM: tlVM, originalTopic: self.tappedTopic!)
}
}
}
EditTopicV.swift (Editor View):
struct EditTopicV: View {
#ObservedObject var tlVM: TopicListVM
#Environment(\.presentationMode) var presentationMode
let originalTopic: Topic
#State private var editTopic: Topic
#State private var ic = "circle"
let iconList = ["circle", "leaf", "photo"]
init(tlVM: TopicListVM, originalTopic: Topic) {
print("DBG: EditTopicV: originalTopic = \(originalTopic)")
self.tlVM = tlVM
self.originalTopic = originalTopic
self._editTopic = .init(initialValue: originalTopic)
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Button("Cancel") {
presentationMode.wrappedValue.dismiss()
}
Spacer()
Button("Save") {
editTopic.iconName = editTopic.iconName.lowercased()
tlVM.change(topic: originalTopic, to: editTopic)
presentationMode.wrappedValue.dismiss()
}
}
HStack {
Text("Name:")
TextField("name", text: $editTopic.name)
Spacer()
}
Picker("Color Theme", selection: $editTopic.iconName) {
ForEach(iconList, id: \.self) { icon in
Text(icon).tag(icon)
}
}
.pickerStyle(.segmented)
Spacer()
}
.padding()
}
}
TopicListVM.swift (Observable Object View Model):
class TopicListVM: ObservableObject {
#Published var topics = [Topic]()
func append(topic: Topic) {
topics.append(topic)
}
func change(topic: Topic, to newTopic: Topic) {
if let index = topics.firstIndex(where: { $0.name == topic.name }) {
topics[index] = newTopic
}
}
static func ex1() -> TopicListVM {
let tvm = TopicListVM()
tvm.append(topic: Topic(name: "leaves", iconName: "leaf"))
tvm.append(topic: Topic(name: "photos", iconName: "photo"))
tvm.append(topic: Topic(name: "shapes", iconName: "circle"))
return tvm
}
}
Here's what the list looks like:
Using sheet(isPresented:) has the tendency to cause issues like this because SwiftUI calculates the destination view in a sequence that doesn't always seem to make sense. In your case, using objectWillSend on the view model, even though it shouldn't have any effect, seems to delay the calculation of your force-unwrapped variable and avoids the crash.
To solve this, use the sheet(item:) form:
.sheet(item: $tappedTopic) { item in
EditTopicV(tlVM: tlVM, originalTopic: item)
}
Then, your item gets passed in the closure safely and there's no reason for a force unwrap.
You can also capture tappedTopic for a similar result, but you still have to force unwrap it, which is generally something we want to avoid:
.sheet(isPresented: $doEditTappedTopic) { [tappedTopic] in
EditTopicV(tlVM: tlVM, originalTopic: tappedTopic!)
}

Correct animation for changing cells in List/ForEach loop on search in SwiftUI?

The problem I am having with the below code is that when the user searches for a specific contact and I need to change the list from contacts to searchResults (which contain the same type of CNContact element with unique .identifier, the animation slides the entire list to the right and bring another one from the left. Surprisingly, this doesn't reproduce in iOS 14, so maybe this is a bug with iOS 13.4?
var body: some View {
VStack {
SearchBar(text: $searchViewModel.searchText)
let displayContacts = searchViewModel.searchText.count == 0 ? contacts : searchResults
List {
ForEach(displayContacts, id: \.identifier) { contact in
cellWithContact(contact)
}
}
}
}
#ViewBuilder
private func cellWithContact(_ contact: CNContact) -> some View {
let displayName: String? = CNContactFormatter.string(from: contact, style: .fullName)
HStack() {
Text(displayName ?? "No Name")
Spacer()
Button {
// invite
} label: {
Text("Invite")
}
}
}

Passing data from enum to sheet triggered inside ForEach loop

I've got an ForEach loop inside my VStack, so that for every element in my enum a new "cell" is created. This works just fine. I can pass the title and the number for each cell, but in each cell there is a button which is toggling a sheet view. Each sheet should contain the according text in a scroll view. The text therefore is also given in the enum.
Problem: But when I'm trying to pass that infoText via the element.infoText for every sheet the infoText of the first element in the enum gets presented.
The ForEach loop:
struct ListView: View{
#State var infoSheetIsPresented: Bool = false
var body: some View{
VStack {
ForEach(WelcomeCardViewContent.allCases, id: \.self) {
element in
HStack {
Text(element.text)
Button(action: {
self.infoSheetIsPresented.toggle()
}) {
Image(systemName: "info.circle")
}
.sheet(isPresented: self.$infoSheetIsPresented) {
Text(element.infoText)
}
}
}
}
}
}
And here is my enum. Of course there's the InfoSheetView as well, but like i said its basically just a scroll view with text. The text gets passed with a simple "text" constant. For simplicity I've replaced the separate sheet view with a simple text view -> same problem.
enum WelcomeCardViewContent: String, CaseIterable{
case personalData
case bodyParameters
var text: String {
switch self{
case .personalData:
return "Personal Data"
case .bodyParameters:
return "Body Parameters"
}
}
var infoText: String {
switch self{
case .personalData:
return "1 Lorem ipsum dolor.."
case .bodyParameters:
return "2 Lorem ipsum dolor sit amet."
}
}
}
Thanks for your advice ^^.
Since you were losing track of the current card, I fixed this by saving the card which is going to be shown. Now the text is displayed correctly.
Here is the fixed version:
struct ListView: View {
#State private var infoSheetIsPresented: Bool = false
#State private var showingCard: WelcomeCardViewContent = .personalData
var body: some View {
ForEach(WelcomeCardViewContent.allCases, id: \.self) { element in
HStack {
Text(element.text)
Button(action: {
self.showingCard = element
self.infoSheetIsPresented.toggle()
}) {
Image(systemName: "info.circle")
}
.sheet(isPresented: self.$infoSheetIsPresented) {
Text(self.showingCard.infoText)
}
}
}
}
}

Resources