|
Yeah, I accidentally deleted my original post, but I'll try to figure it out on my own, thanks!
|
|
In its simplest form (as I may assume using observable etc. might look way too much to refactor the whole code), below version might be more sutable. There we just can check if season id == season which was passed to the button. And if it was just mark its property as pressed true.
struct Season: Identifiable {
let id = UUID()
let number: Int
}
struct ContentView: View {
@State var seasons: [Season] = [
Season(number: 1),
Season(number: 2),
Season(number: 3)
]
@State var seasonSelected: Season?
var body: some View {
HStack {
ForEach(seasons) { season in
SeasonButton(season: season, seasonSelected: $seasonSelected)
}
}
}
}
struct SeasonButton: View {
let season: Season
@Binding var seasonSelected: Season?
let brandColor: Color = Color(red: 28/255, green: 35/255, blue: 40/255)
var isPressed: Bool {
if let seasonSelected {
if seasonSelected.id == season.id { return true }
}
return false
}
var body: some View {
Button {
seasonSelected = season
} label: {
Text("\(season.number)")
}
.font(.title2)
.frame(width: 50, height: 50)
.background(RoundedRectangle(cornerRadius: 8)
.fill(brandColor)
)
.foregroundStyle(isPressed ? .orange : .white)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(isPressed ? Color.orange : Color.white, lineWidth: 3)
}
}
}
|
|
import SwiftUI
struct RadioButtonsView: View {
@State private var selectedOption: Int = 0
let options = ["Option 1", "Option 2", "Option 3"]
var body: some View {
Picker("Select an option", selection: $selectedOption) {
ForEach(0..<options.count) { index in
Text(options[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding()
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Radio Buttons in SwiftUI")
.font(.title)
.padding()
RadioButtonsView()
// You can use the selectedOption value here for further processing.
Text("Selected Option: \(RadioButtonsView().selectedOption)")
.padding()
}
}
}
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Also check: gasolineracercaubicaion. com
|
|
@ygeras
That worked, thanks so much!! However, now I have this:
ForEach(seasons, id: \.self) { season in
SeasonButtonView(season: season, seasonSelected: $seasonSelected)
.onTapGesture {
print("Yo")
}
}
This doesn't print anything. Why?
As well, how do I make it so that the first button is selected by default?
|
|
Hi! Why do you attach onTapGesture to SeasonButtonView?
SeasonButtonView is the BUTTON itself. You have to put your print statement inside that view like so:
struct SeasonButton: View {
let season: Season
@Binding var seasonSelected: Season?
let brandColor: Color = Color(red: 28/255, green: 35/255, blue: 40/255)
var isPressed: Bool {
if let seasonSelected {
if seasonSelected.id == season.id { return true }
}
return false
}
var body: some View {
Button {
seasonSelected = season
print("Yo") // <--- YOUR ACTION TRIGGERED HERE
} label: {
Text("\(season.number)")
}
.font(.title2)
.frame(width: 50, height: 50)
.background(RoundedRectangle(cornerRadius: 8)
.fill(brandColor)
)
.foregroundStyle(isPressed ? .orange : .white)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(isPressed ? Color.orange : Color.white, lineWidth: 3)
}
}
}
|
|
The "beauty" of this approach is that you have @State var seasonSelected: Season? which you can use to display the selection etc...
struct ContentView: View {
@State var seasons: [Season] = [
Season(number: 1),
Season(number: 2),
Season(number: 3)
]
// Once you have season selected
@State var seasonSelected: Season?
var body: some View {
VStack {
// You can display that selection
Text(seasonSelected != nil ? "Your selected SEASON is \(seasonSelected!.number)" : "SEASON is not yet selected!")
HStack {
ForEach(seasons) { season in
SeasonButton(season: season, seasonSelected: $seasonSelected)
}
}
}
}
}
|
|
The reason I put onTapGesture there is because the button filters a viewModel's list of episodes, and I don't want to inject the viewModel into the buttonsView.
|
|
Then pass in closure to the view and use it like so. Will that help?
struct ContentView: View {
@State var seasons: [Season] = [
Season(number: 1),
Season(number: 2),
Season(number: 3)
]
// Once you have season selected
@State var seasonSelected: Season?
var body: some View {
VStack {
// You can display that selection
Text(seasonSelected != nil ? "Your selected SEASON is \(seasonSelected!.number)" : "SEASON is not yet selected!")
HStack {
ForEach(seasons) { season in
SeasonButton(season: season, seasonSelected: $seasonSelected) {
print("USE YOUR MODEL HERE to find \(seasonSelected!.number)") // <-- YOU can use closure instead of tap gesture, it will be triggered upon button press
}
}
}
}
}
}
struct SeasonButton: View {
let season: Season
@Binding var seasonSelected: Season?
let brandColor: Color = Color(red: 28/255, green: 35/255, blue: 40/255)
var action: () -> Void // <-- add closure
var isPressed: Bool {
if let seasonSelected {
if seasonSelected.id == season.id { return true }
}
return false
}
var body: some View {
Button {
seasonSelected = season
action() // <-- will be triggered upon button press
} label: {
Text("\(season.number)")
}
.font(.title2)
.frame(width: 50, height: 50)
.background(RoundedRectangle(cornerRadius: 8)
.fill(brandColor)
)
.foregroundStyle(isPressed ? .orange : .white)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(isPressed ? Color.orange : Color.white, lineWidth: 3)
}
}
}
|
|
Didn't notice that question before.
As well, how do I make it so that the first button is selected by default?
So that can be handled this way
// Once you have season selected
@State var seasonSelected: Season?
// You will need to add this custom init for view where you have @State var seasonSelected: Season?
// What it does? So it accesses wrappedValue of @State var seasonSelected
// And provides inital value for @State (it has to be in this format: State(initialValue: YOUR VALUE))
init() {
_seasonSelected = State(initialValue: seasons[0]) // <-- Here you pass what you want to be selecteb by default
}
|
|
Hmm, when I try this:
init(shortName: String) {
self.shortName = shortName
_viewModel = StateObject(wrappedValue: EpisodeViewModel(shortName: shortName))
_seasonSelected = State(initialValue: viewModel.seasons[0])
}
I get an index out of range error. I guess it's a race condition? The _viewModel was there before to pass the shortName from the view to the view model.
|
|
I cannot say what is the right way to handle that particular case, as I do not see the full picture of the project and your view model set up and how you pass that object to other views.
This part of the code:
_seasonSelected = State(initialValue: viewModel.seasons[0])
is necessary only for one reason. When your view loads, @State var seasonSelected: Season? this property is nil. But you want it to be something initially, so your either display that nothing is there (as for example: "Season not selected"), or you provide initial value, what was done in the line above.
BUT if you pass view model object to your view, (I have no idea again about your data setup and flow, so only guessing), why do you need to assign initial value? You just pass reference to that object and use it in that view... using @Bindable for example or via @Environment...
Sorry, maybe it sounds a bit complicated, but you probably can imagine how it is "difficult" to offer solutions whey you see onlyt tip of the iceberg 😅
|
|
I will post the whole code... well I hope it is more clear what I meant in the above lines. All comments are there so should be clear what is what. You can just copy in test project and have a look if it is similar to what you're trying to do.
import SwiftUI
@main
struct ButtonProjectApp: App {
var body: some Scene {
WindowGroup {
// This view is now entry point
MainView()
}
}
}
import SwiftUI
import Observation
struct Movie: Identifiable {
let id = UUID()
let number: Int
}
struct Season: Identifiable {
let id = UUID()
let number: Int
var movies: [Movie]
}
@Observable
class Seasons {
// Pay attention that the order of movies is random with number
// This is just to use as a sample data
var allSeason: [Season] = [
Season(number: 1, movies: [Movie(number: 3), Movie(number: 1), Movie(number: 2)]),
Season(number: 2, movies: [Movie(number: 5), Movie(number: 6), Movie(number: 4)]),
Season(number: 3, movies: [Movie(number: 8), Movie(number: 7), Movie(number: 9)])
]
// fetch
// delete
// etc...
}
struct MainView: View {
// We initalized view model in this view
// just to show how you can pass further
@State var vm = Seasons()
var body: some View {
ContentView(viewModel: vm)
}
}
struct ContentView: View {
// As we just need one way connection in this exapmle
// we don't need to use @Bindable
// but should you need two way communitcation you will need to add @Bindalbe
let viewModel: Seasons
// Once you have season selected
@State var seasonSelected: Season?
// You will need to add this custom init for view where you have @State var seasonSelected: Season?
// What it does? So it accesses wrappedValue of @State var seasonSelected
// And provides inital value for @State (it have to be in this format: State(initialValue: YOUR VALUE))
init(viewModel: Seasons) {
self.viewModel = viewModel
_seasonSelected = State(initialValue: viewModel.allSeason[0])
}
var body: some View {
NavigationStack {
ZStack(alignment: .bottom) {
// As we use seasonSelectd as Optinal, let's make sure it exists so that we dont' have to unwrap it later
if let seasonSelected {
List(seasonSelected.movies) { movie in
Text("This is movie \(movie.number) for Season \(seasonSelected.number)")
}
}
VStack {
// You can display that selection
Text(seasonSelected != nil ? "Your selected SEASON is \(seasonSelected!.number)" : "SEASON is not yet selected!")
HStack {
ForEach(viewModel.allSeason) { season in
SeasonButton(season: season, seasonSelected: $seasonSelected) {
// Now you can sort it here
sortMovies()
}
}
}
}
}
}
// should you need to sort on launch
.onAppear {
sortMovies()
}
.navigationTitle("Movies")
}
private func sortMovies() {
seasonSelected?.movies.sort { $0.number > $1.number }
}
}
struct SeasonButton: View {
let season: Season
@Binding var seasonSelected: Season?
let brandColor: Color = Color(red: 28/255, green: 35/255, blue: 40/255)
var action: () -> Void // <-- add closure
var isPressed: Bool {
if let seasonSelected {
if seasonSelected.id == season.id { return true }
}
return false
}
var body: some View {
Button {
seasonSelected = season
action() // <-- will be triggered upon button press
} label: {
Text("\(season.number)")
}
.font(.title2)
.frame(width: 50, height: 50)
.background(RoundedRectangle(cornerRadius: 8)
.fill(brandColor)
)
.foregroundStyle(isPressed ? .orange : .white)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(isPressed ? Color.orange : Color.white, lineWidth: 3)
}
}
}
#Preview {
NavigationStack {
ContentView(viewModel: Seasons())
}
}
|
|
Ah, okay, I think I get what you're trying to say. Unfortunately I cannot post the whole code so I will have to try to figure it out with what you gave me. Thanks for all the help!
|