Binding change not detected within List NavigationView - swift

I'm building a picker component and am passing a Binding two layers deep. The issue is that I need to detect it's .onChange on the PickerListView, however, it never gets triggered.
import SwiftUI
public struct SectionItems<T: Hashable>: Hashable {
let header: String
public var items: [T]
public init(header: String, items: [T]) {
self.header = header
self.items = items
}
}
struct PickerListView<T: Hashable>: View {
#Environment(\.dismiss) var dismiss
#Binding var selected: T
#Binding var sections: [SectionItems<T>]
init(selected: Binding<T>, sections: Binding<[SectionItems<T>]>) {
self._selected = selected
self._sections = sections
}
var body: some View {
List {
ForEach(sections, id: \.self) { section in
Section(section.header) {
ForEach(section.items, id: \.self) { item in
Button(action: {
selected = item
dismiss()
}) {
HStack {
Text(String(describing: item))
Spacer()
if selected == item {
Image(systemName: "checkmark")
}
}
}
}
}
}
}
.onChange(of: sections) { _ in
print("PickerListView: \(sections.count)") // Doesn't run
}
}
}
import SwiftUI
public struct PickerView<T: Hashable>: View {
#Binding var selected: T
#Binding var sections: [SectionItems<T>]
public var body: some View {
NavigationView {
NavigationLink(
String(describing: selected),
destination: PickerListView(
selected: $selected,
sections: $sections
)
)
}
.onChange(of: sections) { _ in
print("PickerView: \(sections.count)") // Runs
}
}
}
struct ContentView: View {
#State private var selected = "33"
#State private var sections = [
SectionItems(header: "Itemr", items: [
"33",
"73",
"38"
])
]
var body: some View {
VStack {
PickerView(selected: $selected, sections: $sections)
Button("Add Value", action: {
sections[0].items.append("\(Int.random(in: 1...100))")
})
}
.onChange(of: sections) { _ in
print("ContentView: \(sections.count)") // Runs
}
}
}

You can try a different approach, instead of passing multiple items across many Views you can combine all the variables in a single ObservableObject
Then you can use didSet to make any changes you would make with onChange
///Keeps all the code needed for several views in one spot
class SectionViewModel<T: Hashable>: ObservableObject{
#Published var selected : T
#Published var sections : [SectionItems<T>]{
didSet{
//Use didSet instead of onChange
print("\(type(of: self)) :: \(#function) :: \(sections.count)")
print("\(type(of: self)) :: \(#function) :: section[0]items count: \(sections[0].items.count)")
}
}
init(selected : T, sections : [SectionItems<T>]){
self.selected = selected
self.sections = sections
}
}
Your Views would look something like
struct PickerListView<T: Hashable>: View {
#ObservedObject var vm: SectionViewModel<T>
#Environment(\.dismiss) var dismiss
var body: some View {
List {
ForEach(vm.sections, id: \.self) { section in
Section(section.header) {
ForEach(section.items, id: \.self) { item in
Button(action: {
vm.selected = item
dismiss()
}) {
HStack {
Text(String(describing: item))
Spacer()
if vm.selected == item {
Image(systemName: "checkmark")
}
}
}
}
}
}
}
//Use didset for any changes
// .onChange(of: vm.sections) { _ in
// print("PickerListView: \(vm.sections.count)") // Doesn't run
// }
}
}
public struct PickerView<T: Hashable>: View {
#ObservedObject var vm: SectionViewModel<T>
public var body: some View {
//Navigation View should be at the top
//NavigationView {
NavigationLink(
String(describing: vm.selected),
destination: PickerListView<T>(vm: vm)
)
// }
//Use did set for any actions
// .onChange(of: vm.sections) { _ in
// print("PickerView: \(vm.sections.count)") // Runs
// print("PickerView section[0]items count: \(vm.sections[0].items.count)") // Runs
// }
}
}
struct SampleDeepView: View {
#StateObject var vm: SectionViewModel = .init(selected: "33", sections: [
SectionItems(header: "Itemr", items: [
"33",
"73",
"38"
])
])
var body: some View {
NavigationView{
VStack {
PickerView(vm: vm)
//Adding an item not a section
Button("Add Value", action: {
vm.sections[0].items.append("\(Int.random(in: 1...100))")
})
//Adding a section
Button("Add Section", action: {
vm.sections.append(
SectionItems(header: "Itemr", items: [
"33",
"73",
"38"
])
)
})
}
}
//Use didset for any actions
// .onChange(of: vm.sections) { _ in
// print("ContentView section[0]items count: \(vm.sections[0].items.count)") // Runs
// }
}
}

Related

Create list with NavigationLink items in SwiftUI where each NavigationLink again contains a list of items which is individual

[Pic 1 AS IS]
[Pic 2 TO BE]
Hi there,
I am just starting to learn Swift an I would like my app users to build their own list of items (first level) where each item again contains a list of items (second level). Important is that each of the individually created lists in the second level is like no other of the individually created lists. (see picture)
Is anyone aware of which approach I need to take to solve this?
I am myself able to build the list within the list within the NavigationView, but how can I make each list individual?
Here is my code:
struct ItemModel: Hashable {
let name: String
}
struct ProductModel: Hashable {
let productname: String
}
class ListViewModel: ObservableObject {
#Published var items: [ItemModel] = []
}
class ProductlistViewModel: ObservableObject {
#Published var products: [ProductModel] = []
}
struct ContentView: View {
#StateObject private var vm = ListViewModel()
#StateObject private var pvm = ProductlistViewModel()
#State var firstPlusButtonPressed: Bool = false
#State var secondPlusButtonPressed: Bool = false
var body: some View {
NavigationView {
List {
ForEach(vm.items, id: \.self) { item in
NavigationLink {
DetailView() //The DetailView below
.navigationTitle(item.name)
.navigationBarItems(
trailing:
Button(action: {
secondPlusButtonPressed.toggle()
}, label: {
NavigationLink {
AddProduct() //AddProduct below
} label: {
Image(systemName: "plus")
}
})
)
} label: {
Text(item.name)
}
}
}
.navigationBarItems(
trailing:
Button(action: { firstPlusButtonPressed.toggle()
}, label: {
NavigationLink {
AddItem() //AddItem below
} label: { Image(systemName: "plus")
}
})
)
}
.environmentObject(vm)
.environmentObject(pvm)
}
}
struct AddItem: View {
#State var textFieldText: String = ""
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var vm: ListViewModel
var body: some View {
NavigationView {
VStack {
TextField("Add an item...", text: $textFieldText)
Button(action: {
vm.addItem(text: textFieldText)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("SAVE")
})
}
}
}
}
struct DetailView: View {
#StateObject private var pvm = ProductlistViewModel()
#Environment(\.editMode) var editMode
var body: some View {
NavigationView {
List {
ForEach(pvm.products, id: \.self) { product in
Text(product.productname)
}
}
}
.environmentObject(pvm)
}
}
struct AddProduct: View {
#State var textFieldText: String = ""
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var pvm: ProductlistViewModel
var body: some View {
NavigationView {
VStack {
TextField("Add a product", text: $textFieldText)
Button(action: {
pvm.addProduct(text: textFieldText)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("SAVE")
})
}
}
}
}
This is going to be long but here it goes. The issue is the whole ViewModel setup. You detail view now is only using the product view model, you need to rethink your approach.
But what makes the whole thing "complicated" is the 2 different types, Item and Product which you seem to want to combine into one list and use the same subviews for them both.
In swift you have protocol that allows this, protocols require struct and class "conformance".
//Protocols are needed so you can use reuse views
protocol ObjectModelProtocol: Hashable, Identifiable{
var id: UUID {get}
var name: String {get set}
init(name: String)
}
//Protocols are needed so you can use reuse views
protocol ListModelProtocol: Hashable, Identifiable{
associatedtype O : ObjectModelProtocol
var id: UUID {get}
var name: String {get set}
//Keep the individual items with the list
var items: [O] {get set}
init(name: String, items: [O])
}
extension ListModelProtocol{
mutating func addItem(name: String) {
items.append(O(name: name))
}
}
Then your models start looking something like this. Notice the conformance to the protocols.
//Replaces the ListViewModel
struct ListItemModel: ListModelProtocol{
let id: UUID
var name: String
var items: [ItemModel]
init(name: String, items: [ItemModel]){
self.id = .init()
self.name = name
self.items = items
}
}
//Replaces the ProductlistViewModel
struct ListProductModel: ListModelProtocol{
let id: UUID
var name: String
var items: [ProductModel]
init(name: String, items: [ProductModel]){
self.id = .init()
self.name = name
self.items = items
}
}
//Uniform objects, can be specialized but start uniform
struct ItemModel: ObjectModelProtocol {
let id: UUID
var name: String
init(name: String){
self.id = .init()
self.name = name
}
}
//Uniform objects, can be specialized but start uniform
struct ProductModel: ObjectModelProtocol {
let id: UUID
var name: String
init(name: String){
self.id = .init()
self.name = name
}
}
class ModelStore: ObservableObject{
#Published var items: [ListItemModel] = [ListItemModel(name: "fruits", items: [.init(name: "peach"), .init(name: "banana"), .init(name: "apple")])]
#Published var products: [ListProductModel] = [ListProductModel(name: "vegetable", items: [.init(name: "tomatoes"), .init(name: "paprika"), .init(name: "zucchini")])]
}
Now your views can look something like this
struct ComboView: View {
#StateObject private var store = ModelStore()
#State var firstPlusButtonPressed: Bool = false
#State var secondPlusButtonPressed: Bool = false
var body: some View {
NavigationView {
List {
//The next part will address this
ItemLoop(list: $store.items)
ItemLoop(list: $store.products)
}
.toolbar(content: {
ToolbarItem {
AddList(store: store)
}
})
}
}
}
struct ItemLoop<LM: ListModelProtocol>: View {
#Binding var list: [LM]
var body: some View{
ForEach($list, id: \.id) { $item in
NavigationLink {
DetailView<LM>(itemList: $item)
.navigationTitle(item.name)
.toolbar {
NavigationLink {
AddItem<LM>( item: $item)
} label: {
Image(systemName: "plus")
}
}
} label: {
Text(item.name)
}
}
}
}
struct AddList: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var store: ModelStore
var body: some View {
Menu {
Button("add item"){
store.items.append(ListItemModel(name: "new item", items: []))
}
Button("add product"){
store.products.append(ListProductModel(name: "new product", items: []))
}
} label: {
Image(systemName: "plus")
}
}
}
struct AddItem<LM>: View where LM : ListModelProtocol {
#State var textFieldText: String = ""
#Environment(\.presentationMode) var presentationMode
#Binding var item: LM
var body: some View {
VStack {
TextField("Add an item...", text: $textFieldText)
Button(action: {
item.addItem(name: textFieldText)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("SAVE")
})
}
}
}
struct DetailView<LM>: View where LM : ListModelProtocol{
#Environment(\.editMode) var editMode
#Binding var itemList: LM
var body: some View {
VStack{
TextField("name", text: $itemList.name)
.textFieldStyle(.roundedBorder)
List (itemList.items, id:\.id) { item in
Text(item.name)
}
}
.navigationTitle(itemList.name)
.toolbar {
NavigationLink {
AddItem(item: $itemList)
} label: {
Image(systemName: "plus")
}
}
}
}
If you notice the List in the ComboView you will notice that the items and products are separated into 2 loop. That is because SwiftUI requires concrete types for most views, view modifiers and wrappers.
You can have a list of [any ListModelProtocol] but at some point you will have to convert from an existential to a concrete type. In your case the ForEach in de DetailView requires a concrete type.
class ModelStore: ObservableObject{
#Published var both: [any ListModelProtocol] = [
ListProductModel(name: "vegetable", items: [.init(name: "tomatoes"), .init(name: "paprika"), .init(name: "zucchini")]),
ListItemModel(name: "fruits", items: [.init(name: "peach"), .init(name: "banana"), .init(name: "apple")])
]
}
struct ComboView: View {
#StateObject private var store = ModelStore()
#State var firstPlusButtonPressed: Bool = false
#State var secondPlusButtonPressed: Bool = false
var body: some View {
NavigationView {
List {
ConcreteItemLoop(list: $store.both)
}
.toolbar(content: {
ToolbarItem {
AddList(store: store)
}
})
}
}
}
struct ConcreteItemLoop: View {
#Binding var list: [any ListModelProtocol]
var body: some View{
ForEach($list, id: \.id) { $item in
NavigationLink {
if let concrete: Binding<ListItemModel> = getConcrete(existential: $item){
DetailView(itemList: concrete)
} else if let concrete: Binding<ListProductModel> = getConcrete(existential: $item){
DetailView(itemList: concrete)
}else{
Text("unknown type")
}
} label: {
Text(item.name)
}
}
}
func getConcrete<T>(existential: Binding<any ListModelProtocol>) -> Binding<T>? where T : ListModelProtocol{
if existential.wrappedValue is T{
return Binding {
existential.wrappedValue as! T
} set: { newValue in
existential.wrappedValue = newValue
}
}else{
return nil
}
}
}
struct AddList: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var store: ModelStore
var body: some View {
Menu {
Button("add item"){
store.both.append(ListItemModel(name: "new item", items: []))
}
Button("add product"){
store.both.append(ListProductModel(name: "new product", items: []))
}
} label: {
Image(systemName: "plus")
}
}
}
I know its long but this all compiles so you should be able to put it in a project and disect it.
Also, at the end of all of this you can create specific views for the model type.
struct DetailView<LM>: View where LM : ListModelProtocol{
#Environment(\.editMode) var editMode
#Binding var itemList: LM
var body: some View {
VStack{
TextField("name", text: $itemList.name)
.textFieldStyle(.roundedBorder)
List (itemList.items, id:\.id) { item in
VStack{
switch item{
case let i as ItemModel:
ItemModelView(item: i)
case let p as ProductModel:
Text("\(p.name) is product")
default:
Text("\(item.name) is unknown")
}
}
}
}
.navigationTitle(itemList.name)
.toolbar {
NavigationLink {
AddItem(item: $itemList)
} label: {
Image(systemName: "plus")
}
}
}
}
struct ItemModelView: View{
let item: ItemModel
var body: some View{
VStack{
Text("\(item.name) is item")
Image(systemName: "person")
}
}
}

SwiftUI Executing a method from a view after confirmation from the subview

Is it technically possible to call a method from a view after getting the confirmation from the subview ? I could call it from the SubStruct if I pass the viewModel and item to it, but I am just curious about the code below which results in
Segmentation Fault: 11
import SwiftUI
struct ContentView: View {
var body: some View {
MainStruct(viewModel: MyViewModel())
}
}
struct MainStruct: View {
#StateObject var viewModel: MyViewModel
#State var functionToPass: ()
let items = ["test1", "test2", "test2"]
var body: some View {
ForEach(items, id: \.self) { (item) in
Text(item).onTapGesture {
functionToPass = viewModel.deleteItem(name: item)
}
}
SubStruct(passedFunction: {functionToPass})
}
struct SubStruct: View {
var passedFunction: () -> Void
var body: some View {
Button(action: {passedFunction()}, label: {
Text("confirm deletion")
})
}
}
}
class MyViewModel: ObservableObject {
func deleteItem(name: String) {
print(name)
///deletion logic
}
}
Try this :
struct MainStruct: View {
#StateObject var viewModel: MyViewModel
#State private var functionToPass: () -> Void = {}
let items = ["test1", "test2", "test2"]
var body: some View {
VStack {
ForEach(items, id: \.self) { item in
Text(item)
.onTapGesture {
functionToPass = {
viewModel.deleteItem(name: item)
}
}
}
SubStruct(passedFunction: functionToPass)
}
}
struct SubStruct: View {
var passedFunction: () -> Void
var body: some View {
Button(action: {
passedFunction()
}, label: {
Text("confirm deletion")
})
}
}
}

passing parameter to a SwiftUI Sheet

I need to pass a parameter calledFrom to a Sheet in SwiftUI.
Strangely, the parameter is not used on the first call, but it works on the following ones.
import SwiftUI
struct ContentView: View {
#State var showSheet = false
#State var calledFrom = -1
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(isPresented: $showSheet) { Dialog(calledFrom: calledFrom) }
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){print("Button \(i) pressed"); calledFrom = i; showSheet = true }
}
}
struct Dialog: View {
var calledFrom : Int
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack{
Text("Called from Button \(calledFrom)")
Button("close"){presentationMode.wrappedValue.dismiss()}
}
.padding()
}
}
You have to use sheet(item:) to get the behavior you're looking for. In iOS 14, the sheet view is calculated before the #State changes:
struct ActiveItem : Identifiable {
var calledFrom: Int
var id: Int { return calledFrom }
}
struct ContentView: View {
#State var activeItem : ActiveItem?
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(item: $activeItem) { item in
Dialog(calledFrom: item.calledFrom)
}
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){
print("Button \(i) pressed");
activeItem = ActiveItem(calledFrom: i)
}
}
}

SwiftUI .onTapGesture issue in TableView with Sections and Rows

I have two models:
struct Category: Identifiable {
var id = UUID()
var title: String
var number: Int
var items: [ChecklistItem]
}
and:
struct ChecklistItem: Identifiable {
let id = UUID()
var name: String
var isChecked = false
}
with:
class Checklist: ObservableObject {
#Published var items = [Category]()
func deleteListItem(whichElement: IndexSet) {
items.remove(atOffsets: whichElement)
}
func moveListItem(whichElement: IndexSet, destination: Int) {
items.move(fromOffsets: whichElement, toOffset: destination)
}
}
I try to implement tap on row to check and uncheck cheklist item in tableView with sections and rows, but I cannot get how this can be released. My code:
struct ChecklistView: View {
#EnvironmentObject var checklist: Checklist
#State var newChecklistItemViewIsVisible = false
var body: some View {
NavigationView {
List {
ForEach(checklist.items) { category in
Section(header: Text(category.title)) {
ForEach(category.items) { item in
HStack {
Text(item.name)
Spacer()
Text(item.isChecked ? "✅" : "🔲")
}
.background(Color.white)
.onTapGesture {
if let matchingIndex =
category.items.firstIndex(where: { $0.id == item.id }) {
category.items[matchingIndex].isChecked.toggle()
}
}
}
}
}
.onDelete(perform: checklist.deleteListItem)
.onMove(perform: checklist.moveListItem)
}
.navigationBarItems(
leading: Button(action: { self.newChecklistItemViewIsVisible = true }) {
HStack {
Image(systemName: "plus.circle.fill")
Text("Add")
}
},
trailing: EditButton()
)
.navigationBarTitle("List")
}
.sheet(isPresented: $newChecklistItemViewIsVisible) {
NewChecklistItemView(checklist: self.checklist)
}
}
}
I get error with this code on line with category.items[matchingIndex].isChecked.toggle():
Cannot use mutating member on immutable value: 'category' is a 'let' constant
How I can get to ChecklistItem and make it check and uncheck on tap.
import SwiftUI
//Change to class and add NSObject structs are immutable
class Category: NSObject, Identifiable {
let id = UUID()
var title: String
var number: Int
var items: [ChecklistItem]
//Now you need an init
init(title: String , number: Int, items: [ChecklistItem]) {
self.title = title
self.number = number
self.items = items
}
}
//Change to class and add NSObject structs are immutable
class ChecklistItem: Identifiable {
let id = UUID()
var name: String
var isChecked: Bool = false
//Now you need an init
init(name: String) {
self.name = name
}
}
class Checklist: ObservableObject {
#Published var items = [Category]()
}
struct ChecklistView: View {
//Can be an #EnvironmentObject if the #ObservedObject comes from a higher View
#ObservedObject var checklist: Checklist = Checklist()
#State var newChecklistItemViewIsVisible = false
var body: some View {
NavigationView {
List {
ForEach(checklist.items) { category in
Section(header: Text(category.title)) {
ForEach(category.items) { item in
Button(action: {
print(item.isChecked.description)
item.isChecked.toggle()
//Something to trigger the view to refresh will not be necessary if using something like #FetchRequest or after you somehow notify `checklist.items` that there is a change
checklist.objectWillChange.send()
}) {
HStack {
Text(item.name)
Spacer()
Text(item.isChecked ? "✅" : "🔲")
}//HStack
//White is incompatible with Text Color in Dark Mode
.background(Color.gray)
}//Button
}//ForEach
}//Section
}//ForEach
//Methods not provided
//.onDelete(perform: checklist.deleteListItem)
//.onMove(perform: checklist.moveListItem)
}
.navigationBarItems(
leading: Button(action: {
self.newChecklistItemViewIsVisible = true
//Code to Add Samples
checklist.items.append(Category(title: "Test", number: Int.random(in: 0...100), items: [ChecklistItem(name: "Test")]))
}) {
HStack {
Image(systemName: "plus.circle.fill")
Text("Add")
}
},
trailing: EditButton()
)
.navigationBarTitle("List")
}
.sheet(isPresented: $newChecklistItemViewIsVisible) {
//Pass as an #EnvironmentObject
NewChecklistItemView().environmentObject(checklist)
}
}
}
struct NewChecklistItemView: View {
#EnvironmentObject var checklist: Checklist
var body: some View {
Text(checklist.items.count.description)
}
}
struct ChecklistView_Previews: PreviewProvider {
static var previews: some View {
//When the #ObservedObject comes from a higher View remove comment below
ChecklistView()//.environmentObject(Checklist())
}
}
The reason you are getting that error is because structs are immutable. You should use method marked with "mutating" inside desired struct. Something like
if let matchingIndex = category.items.firstIndex(where: { $0.id == item.id }) {
category.items[matchingIndex].toggleItem()
}
and inside your struct:
mutating func toggleItem() {
self.isChecked.toggle()
}
But i would recommend you to use #State instead, because what you are trying to do is straight forward related to how you represent your view. And later, when user is willing to do something with that selection you send that data to your model

SwiftUI NavigationLink with custom binding based on dictionary fails to reset after transitioning back

I have a dictionary that stores string -> bool, in my view, I wrap this dictionary around a Binding<Bool> to be able to store the states of each NavigationLink in order to later be able to programmatically navigate. Now, this works the first time, Page 1 -> Page 2 -> Page 3, but when you hit back to Page 2, the navigation stops working.
I have checked that the values of isLinkActive dictionary do correctly get updated when NavigationLink is tapped, however, NavigationLink does not get activated.
import SwiftUI
class ContentViewModel: ObservableObject {
#Published var isLinkActive:[String: Bool] = [:]
}
struct ContentView: View {
#ObservedObject var contentViewModel = ContentViewModel()
var page3: some View {
Text("Page 3")
}
#State var data = ["1", "2", "3"]
func binding(chatId: String) -> Binding<Bool> {
return .init(get: { () -> Bool in
return self.contentViewModel.isLinkActive[chatId, default: false]
}) { (value) in
self.contentViewModel.isLinkActive[chatId] = value
}
}
var page2: some View {
return
List(data, id: \.self) { data in
NavigationLink(destination: self.page3, isActive: self.binding(chatId: data)) {
Text("Page \(data) Link")
}
}
}
var body: some View {
return NavigationView() {
VStack {
Text("Page 1")
NavigationLink(destination: page2) {
Text("Page 2 Link")
}
}
}
}
}
There's nothing wrong with the logic of your code. The only issue is that SwiftUI doesn't redraw page2 as it only redraws the var body during an onAppear, and this view is not in context. The way to force refresh is thus to make page2 a body variable.
struct ContentView: View {
var body: some View {
return NavigationView() {
VStack {
Text("Page 1")
NavigationLink(destination: ContentViewTwo()) {
Text("Page 2 Link")
}
}
}
}
}
struct ContentViewTwo: View {
#ObservedObject var contentViewModel = ContentViewModel()
var page3: some View {
Text("Page 3")
}
#State var data = ["1", "2", "3"]
func binding(chatId: String) -> Binding<Bool> {
return .init(get: { () -> Bool in
return self.contentViewModel.isLinkActive[chatId, default: false]
}) { (value) in
self.contentViewModel.isLinkActive[chatId] = value
}
}
var body: some View {
return
List(data, id: \.self) { data in
NavigationLink(destination: self.page3, isActive: self.binding(chatId: data)) {
Text("Page \(data) Link")
}
}
}
}