I have a list generated from a ForEach loop:
struct TrainingList: View {
@EnvironmentObject var trainingVM: TrainingViewModel
var body: some View {
VStack(alignment: .leading) {
Text("Your training sessions")
.font(.system(size: 35, weight: .semibold, design: .default))
.padding(.all, 10)
.foregroundColor(.white)
Divider()
ScrollView{
if(trainingVM.loading){
ProgressView("Loading training session").progressViewStyle(CircularProgressViewStyle(tint: .blue))
}
LazyVStack {
ForEach(trainingVM.trainingList) { training in
TrainingCell(training: training)
}
}
}
Spacer()
}
.background {
Rectangle()
.fill(Color(.sRGB, red: 41/255, green: 41/255, blue: 41/255))
.cornerRadius(10, corners: [.topRight, .bottomRight])
.shadow(color: .black.opacity(1), radius: 8, x: 6, y: 0)
}
.frame(width: 650)
.zIndex(.infinity)
}
}
Each TrainingCell has a button that opens an extra panel on the side of it. To indicate which row has the panel opened the button changes its styling:
struct TrainingCell: View {
@EnvironmentObject var trainingVM: TrainingViewModel
@State var showEvents = false
let training: Training
var body: some View {
HStack(spacing: 0) {
ZStack(alignment: .top) {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color(.sRGB, red: 41/255, green: 41/255, blue: 41/255))
VStack {
HStack(spacing: 10) {
VStack(alignment: .leading, spacing: 5) {
HStack{
Text(training.readableDate, style: .date)
.font(.system(size: 25, weight: .semibold, design: .default))
.foregroundColor(.white)
Text(" | ")
Text(training.readableDate, style: .time)
.font(.system(size: 25, weight: .semibold, design: .default))
.foregroundColor(.white)
}
VStack(alignment: .leading,spacing: 5){
HStack {
HStack(spacing: 5) {
Image(systemName: "person.text.rectangle.fill")
.foregroundColor(Color(.sRGB, red: 10/255, green: 90/255, blue: 254/255))
Text(training.instructor.fullName)
.foregroundColor(.white)
}
}
HStack{
ForEach(training.students){ student in
HStack(spacing: 5) {
Image(systemName: "person")
.imageScale(.medium)
.foregroundColor(Color(.sRGB, red: 10/255, green: 90/255, blue: 254/255))
Text(student.fullName_shortenedFirstName)
.foregroundColor(.white)
}
}
}
}
.font(.system(size: 20, weight: .regular, design: .default))
.foregroundColor(.primary)
}
.frame(maxHeight: .infinity, alignment: .center)
.clipped()
Spacer()
View_Close_Button(showEvents: $showEvents)
}
.frame(maxWidth: .infinity, maxHeight: 80, alignment: .top)
.padding(.all)
.background {
RoundedRectangle(cornerRadius: 0, style: .continuous)
.fill(Color(.sRGB, red: 41/255, green: 44/255, blue: 49/255))
.shadow(color: .black.opacity(1), radius: 5, x: 0, y: 5)
}
}
}
}
}
}
The button code:
struct View_Close_Button: View {
@EnvironmentObject var trainingVM: TrainingViewModel
@Binding var showEvents: Bool
var body: some View {
HStack
{
Image(systemName: showEvents ? "xmark" : "eye")
.imageScale(.large)
.padding(.horizontal, 5)
.font(.system(size: 17, weight: .regular, design: .default))
Text(showEvents ? "Close" : "View")
.padding(.all, 10)
.font(.system(size: 25, weight: .regular, design: .default))
.multilineTextAlignment(.leading)
}
.onTapGesture {
withAnimation(.easeIn(duration: 0.3)) {
showEvents.toggle()
if(showEvents) {
trainingVM.showingEvents = true
}else{
trainingVM.showingEvents = false
}
}
}
.foregroundColor(.white)
.background {
Capsule(style: .continuous)
.foregroundColor(showEvents ? Color(.sRGB, red: 253/255, green: 77/255, blue: 77/255) : Color(.sRGB, red: 10/255, green: 90/255, blue: 254/255))
.clipped()
.frame(maxWidth: 180)
}
}
}
The only problem I have is that all button can be activated at the same time. How would I go about disabling the rest of the button when one is tapped?
I need the user to only be able to have on of the button displayed as "X Close"
I tought about looping trough other buttons to deactivate them programatically before activating the one that was tapped but I have no clue how
CodePudding user response:
You could keep track of the activated button in the parent view.
If you have some kind of unique identifier per button you could make a variable in the parent view that contains the active identifier.
You can pass that variable as a binding into the button views and depending on that you can change the views appearance.
This way there is always just one active button. When a button is clicked you can set the value of the binding variable in the button view with the unique identifier of this button and the other views change automatically.
On the TrainingList you can define a variable with the active tab:
@State var activeTab: Int = 0
On TrainingCell you can add this variable as a binding.
@Binding var activeTab: Int
And you pass it like:
TrainingCell(training: training, activeTab: $activeTab)
Then on View_Close_Button you can add two variables:
@Binding var activeTab: Int
@State var training: Training
And pass it like this on the TrainingCell:
View_Close_Button(showEvents: $showEvents, activeTab: $activeTab, training: training)
In the View_Close_Button you can use this to get the value and set the styles accordingly:
Image(systemName: activeTab == training.id ? "xmark" : "eye")
Text(activeTab == training.id ? "Close" : "View")
And you can set it when the button it tapped:
.onTapGesture {
withAnimation(.easeIn(duration: 0.3) {
activeTab = training.id
}
}
CodePudding user response:
Instead of showPanel
you can have a selectedPanel
struct SelectedPanelView: View {
@State var selectedPanel: Int?
var body: some View {
HStack{
VStack{
ForEach(0...5) { n in
Button("open panel \(n)") {
selectedPanel = n
}
//Change the buton based on the selection
.foregroundColor(selectedPanel == n ? Color.red : Color.blue)
//Enable the buttons only if there isnt a button selected
.disabled(selectedPanel != nil)
}
}
if let selectedPanel = selectedPanel{
Text("panel \(selectedPanel) is open")
//Deselect the panel
Button("close panel") {
self.selectedPanel = nil
}
}else{
Text("select panel")
}
}
}
}