I am trying to make a custom segment control using a button array.
When a button is tapped, the correct value is passed to the main view and can be pressed multiple times without issue. The problem is that the previous button which was selected stays selected. The code below should work to recreate the issue. Thanks for the help.
Segment view
struct WaveTypeGridView: View {
var waveTypes = ["Beach Break" ,"Reef Break", "Point Break", "Rivermouth"]
#Binding var waveTypeSelected: String
#State var typeSelected: String
let columnSpacing: CGFloat = 5
let rowSpacing: CGFloat = 10
var gridLayout: [GridItem] {
return Array(repeating: GridItem(.flexible(), spacing: rowSpacing), count: 1)
}
var body: some View {
VStack {
Text("Wave Type \(typeSelected)")
ScrollView(.horizontal, showsIndicators: false, content: {
LazyHGrid(rows: gridLayout, alignment: .center, spacing: columnSpacing, pinnedViews: [], content: {
ForEach(waveTypes, id: \.self) { type in
if type == typeSelected {
TypeItemView(selected: true, typeSelected: self.$typeSelected, name: type)
} else {
TypeItemView(selected: false, typeSelected: self.$typeSelected, name: type)
}
}
})//: GRID
.frame(height: 18)
.padding(.vertical, 8)
.padding(.horizontal, 10)
})//: SCROLL
.background(Color(.systemGray5).cornerRadius(8))
}
}
}
struct WaveTypeGridView_Previews: PreviewProvider {
static var previews: some View {
WaveTypeGridView(waveTypeSelected: .constant(surfDataTests[0].waveType), typeSelected: "Beach Break")
.previewLayout(.sizeThatFits)
}
}
Button View
struct TypeItemView: View {
#State var selected: Bool
#Binding var typeSelected: String
let name: String
func test() {
}
var body: some View {
Button(action: {
selected.toggle()
typeSelected = name
}, label: {
HStack(alignment: .center, spacing: 6, content: {
Text(name)
.font(.footnote)
.fontWeight(.medium)
.foregroundColor(selected ? Color.white : Color.black)
})//: HSTACK
.frame(height: 18)
.padding(6)
.background(selected ? Color.blue
.cornerRadius(8)
: Color.white
.cornerRadius(8))
.background (
RoundedRectangle(cornerRadius: 8)
)
})//: BUTTON
}
}
struct TypeItemView_Previews: PreviewProvider {
static var previews: some View {
TypeItemView(selected: false, typeSelected: .constant("Rivermouth"), name:"Rivermouth")
.previewLayout(.sizeThatFits)
.padding()
.background(Color.gray)
}
}
You seem to have made your state tracking more complex than it needs to be. All you need to know is the currently selected wave type. You don't need to track a separate selected state, since the button can determine this from it's own value and the currently selected value.
For the grid view you can just have a single #State property for the selected wave type (You could inject this as an #Binding if required).
Pass the selected break type and the view's target value to the TypeItemView
struct WaveTypeGridView: View {
var waveTypes = ["Beach Break" ,"Reef Break", "Point Break", "Rivermouth"]
#State var typeSelected: String = "Beach Break"
let columnSpacing: CGFloat = 5
let rowSpacing: CGFloat = 10
var gridLayout: [GridItem] {
return Array(repeating: GridItem(.flexible(), spacing: rowSpacing), count: 1)
}
var body: some View {
VStack {
Text("Wave Type \(typeSelected)")
ScrollView(.horizontal, showsIndicators: false, content: {
LazyHGrid(rows: gridLayout, alignment: .center, spacing: columnSpacing, pinnedViews: [], content: {
ForEach(waveTypes, id: \.self) { type in
TypeItemView(typeSelected: self.$typeSelected, name: type)
}
})//: GRID
.frame(height: 18)
.padding(.vertical, 8)
.padding(.horizontal, 10)
})//: SCROLL
.background(Color(.systemGray5).cornerRadius(8))
}
}
}
Then in your TypeItemView you can create a computed property for selected based on the current value and this view's target value.
struct TypeItemView: View {
#Binding var typeSelected: String
let name: String
private var selected: Bool {
return typeSelected == name
}
func test() {
}
var body: some View {
Button(action: {
typeSelected = name
}, label: {
HStack(alignment: .center, spacing: 6, content: {
Text(name)
.font(.footnote)
.fontWeight(.medium)
.foregroundColor(self.selected ? Color.white : Color.black)
})//: HSTACK
.frame(height: 18)
.padding(6)
.background(self.selected ? Color.blue
.cornerRadius(8)
: Color.white
.cornerRadius(8))
.background (
RoundedRectangle(cornerRadius: 8)
)
})//: BUTTON
}
}
Now the whole control only depends on one #State item.
Related
I am trying to make a simple dropdown list item in SwiftUI. This is what the code looks like:
struct SomeObject: Hashable {
var title: String = "title"
var entries: [String] = ["details", "details2", "details3"]
}
struct ContentView: View {
var data: [SomeObject] = [SomeObject()]
var body: some View {
List(data, id: \.self) { item in
HStack {
Text(item.title)
Spacer()
}
ForEach(item.entries, id: \.self) { entry in
ListItemView(entry)
}
}.listStyle(.plain)
}
}
struct ListItemView: View {
#State var expanded: Bool = false
#State var rotation: Double = 0
private let entry: String
init(_ entry: String) {
self.entry = entry
}
var body: some View {
VStack {
Divider().frame(maxWidth: .infinity)
.overlay(.black)
HStack {
Text(entry)
.fixedSize(horizontal: false, vertical: true)
Spacer()
Image(systemName: "chevron.down")
.foregroundColor(.black)
.padding()
.rotationEffect(.degrees(expanded ? 180 : 360))
.animation(.linear(duration: 0.3), value: expanded)
}.padding(.horizontal)
.padding(.vertical, 6)
if expanded {
Text("Details")
}
Divider().frame(maxWidth: .infinity)
.overlay(.black)
}
.listRowSeparator(.hidden)
.listRowInsets(.init())
.onTapGesture {
expanded.toggle()
}
}
}
For some reason when clicking on the list item the animation looks like this:
How can I make the arrow rotate on its center point without moving up or down at all?
The problem you have there is that the arrow is animated but when the hidden text appears, that vertical expansion is not animated. That contrast between an element animated and another that is not makes the chevron looks like it is not doing it properly. So, try to animate the VStack like this:
struct CombineView: View {
#State var expanded: Bool = false
#State var rotation: Double = 0
let entry: String = "Detalle"
var body: some View {
VStack {
Divider().frame(maxWidth: .infinity)
.overlay(.black)
HStack(alignment: .center) {
Text(entry)
.fixedSize(horizontal: false, vertical: true)
Spacer()
Image(systemName: "chevron.down")
.foregroundColor(.black)
.padding()
.rotationEffect(.degrees(expanded ? 180 : 360))
.animation(.linear(duration: 0.3), value: expanded)
}.padding(.horizontal)
.padding(.vertical, 6)
.background(.green)
if expanded {
Text("Details")
}
Divider().frame(maxWidth: .infinity)
.overlay(.black)
}.animation(.linear(duration: 0.3), value: expanded)//Animation added
.listRowSeparator(.hidden)
.listRowInsets(.init())
.onTapGesture {
expanded.toggle()
}
}
}
I hope this works for you ;)
In SwiftUI, how can I make these text "buttons" change color on tap, but revert when you remove your finger?
https://i.imgur.com/WHPGhAT.jpg
Here's what the button code looks like:
LazyVGrid(columns:
Array(repeating:
GridItem(.flexible(),
spacing: 5),
count: 2),
spacing: 2) {
ForEach(viewModel.productIngredients, id: \.self) { ingredient in
Text(ingredient.name)
.font(.system(size: 14))
.fontWeight(.medium)
.foregroundColor(.black)
.padding(8)
.background(RoundedRectangle(cornerRadius: 10).stroke(Color.black, lineWidth: 2))
.padding(.top,5)
/// .background(self.selectedIngredient == ingredient ? Color.blue : Color.white)
.onTapGesture {
self.didTap.toggle()
self.selectedIngredient = ingredient
}
}
}
You can use a custom ButtonStyle to do this:
struct ContentView : View {
var body: some View {
Button(action: {
//Your action code, taken from the previous `onTapGesture` in the original code
//didTap.toggle()
//selectedIngredient = ingredient
}) {
Text("Ingredient")
.fontWeight(.medium)
}.buttonStyle(CustomButtonStyle(isSelected: false)) //could pass a parameter here like isSelected: selectedIngredient == ingredient from your original code
}
}
struct CustomButtonStyle : ButtonStyle {
var isSelected: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 14))
.foregroundColor(.black)
.padding(8)
.background(RoundedRectangle(cornerRadius: 10)
.stroke(configuration.isPressed ? Color.red : Color.black, lineWidth: 2)
)
.padding(.top,5)
//Could also modify style based on isSelected
}
}
Notice that your Text view is now wrapped in a Button and given a buttonStyle of CustomButtonStyle.
Inside CustomButtonStyle, I use a ternary expression to set the color of the background RoundedRectangle based on configuration.isPressed.
I also showed how you could pass in another parameter (isSelected) because in your original example it looked like you may want to do things conditionally based on that as well.
Update with full working example showing columns:
struct Ingredient : Identifiable, Hashable {
var id = UUID()
var name = "Ingredient"
}
struct ContentView: View {
#State var ingredients = [Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient()]
var body: some View {
LazyVGrid(columns:
Array(repeating:
GridItem(.flexible(),
spacing: 5),
count: 2),
spacing: 2) {
ForEach(ingredients, id: \.self) { ingredient in
Button(action: {
//Your action code, taken from the previous `onTapGesture` in the original code
//didTap.toggle()
//selectedIngredient = ingredient
}) {
Text(ingredient.name)
.fontWeight(.medium)
}.buttonStyle(CustomButtonStyle(isSelected: false))
}
}
}
}
struct CustomButtonStyle : ButtonStyle {
var isSelected: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 14))
.foregroundColor(.black)
.padding(8)
.background(RoundedRectangle(cornerRadius: 10)
.stroke(configuration.isPressed ? Color.red : Color.black, lineWidth: 2)
)
.padding(.top,5)
//Could also modify style based on isSelected
}
}
Below is my code to create a standard segmented control.
struct ContentView: View {
#State private var favoriteColor = 0
var colors = ["Red", "Green", "Blue"]
var body: some View {
VStack {
Picker(selection: $favoriteColor, label: Text("What is your favorite color?")) {
ForEach(0..<colors.count) { index in
Text(self.colors[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Value: \(colors[favoriteColor])")
}
}
}
My question is how could I modify it to have a customized segmented control where I can have the boarder rounded along with my own colors, as it was somewhat easy to do with UIKit? Has any one done this yet.
I prefect example is the Uber eats app, when you select a restaurant you can scroll to the particular portion of the menu by selecting an option in the customized segmented control.
Included are the elements I'm looking to have customized:
* UPDATE *
Image of the final design
Is this what you are looking for?
import SwiftUI
struct CustomSegmentedPickerView: View {
#State private var selectedIndex = 0
private var titles = ["Round Trip", "One Way", "Multi-City"]
private var colors = [Color.red, Color.green, Color.blue]
#State private var frames = Array<CGRect>(repeating: .zero, count: 3)
var body: some View {
VStack {
ZStack {
HStack(spacing: 10) {
ForEach(self.titles.indices, id: \.self) { index in
Button(action: { self.selectedIndex = index }) {
Text(self.titles[index])
}.padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)).background(
GeometryReader { geo in
Color.clear.onAppear { self.setFrame(index: index, frame: geo.frame(in: .global)) }
}
)
}
}
.background(
Capsule().fill(
self.colors[self.selectedIndex].opacity(0.4))
.frame(width: self.frames[self.selectedIndex].width,
height: self.frames[self.selectedIndex].height, alignment: .topLeading)
.offset(x: self.frames[self.selectedIndex].minX - self.frames[0].minX)
, alignment: .leading
)
}
.animation(.default)
.background(Capsule().stroke(Color.gray, lineWidth: 3))
Picker(selection: self.$selectedIndex, label: Text("What is your favorite color?")) {
ForEach(0..<self.titles.count) { index in
Text(self.titles[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Value: \(self.titles[self.selectedIndex])")
Spacer()
}
}
func setFrame(index: Int, frame: CGRect) {
self.frames[index] = frame
}
}
struct CustomSegmentedPickerView_Previews: PreviewProvider {
static var previews: some View {
CustomSegmentedPickerView()
}
}
If I'm following the question aright the starting point might be something like the code below. The styling, clearly, needs a bit of attention. This has a hard-wired width for segments. To be more flexible you'd need to use a Geometry Reader to measure what was available and divide up the space.
struct ContentView: View {
#State var selection = 0
var body: some View {
let item1 = SegmentItem(title: "Some Way", color: Color.blue, selectionIndex: 0)
let item2 = SegmentItem(title: "Round Zip", color: Color.red, selectionIndex: 1)
let item3 = SegmentItem(title: "Multi-City", color: Color.green, selectionIndex: 2)
return VStack() {
Spacer()
Text("Selected Item: \(selection)")
SegmentControl(selection: $selection, items: [item1, item2, item3])
Spacer()
}
}
}
struct SegmentControl : View {
#Binding var selection : Int
var items : [SegmentItem]
var body : some View {
let width : CGFloat = 110.0
return HStack(spacing: 5) {
ForEach (items, id: \.self) { item in
SegmentButton(text: item.title, width: width, color: item.color, selectionIndex: item.selectionIndex, selection: self.$selection)
}
}.font(.body)
.padding(5)
.background(Color.gray)
.cornerRadius(10.0)
}
}
struct SegmentButton : View {
var text : String
var width : CGFloat
var color : Color
var selectionIndex = 0
#Binding var selection : Int
var body : some View {
let label = Text(text)
.padding(5)
.frame(width: width)
.background(color).opacity(selection == selectionIndex ? 1.0 : 0.5)
.cornerRadius(10.0)
.foregroundColor(Color.white)
.font(Font.body.weight(selection == selectionIndex ? .bold : .regular))
return Button(action: { self.selection = self.selectionIndex }) { label }
}
}
struct SegmentItem : Hashable {
var title : String = ""
var color : Color = Color.white
var selectionIndex = 0
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
None of the above solutions worked for me as the GeometryReader returns different values once placed in a Navigation View that throws off the positioning of the active indicator in the background. I found alternate solutions, but they only worked with fixed length menu strings. Perhaps there is a simple modification to make the above code contributions work, and if so, I would be eager to read it. If you're having the same issues I was, then this may work for you instead.
Thanks to inspiration from a Reddit user "End3r117" and this SwiftWithMajid article, https://swiftwithmajid.com/2020/01/15/the-magic-of-view-preferences-in-swiftui/, I was able to craft a solution. This works either inside or outside of a NavigationView and accepts menu items of various lengths.
struct SegmentMenuPicker: View {
var titles: [String]
var color: Color
#State private var selectedIndex = 0
#State private var frames = Array<CGRect>(repeating: .zero, count: 5)
var body: some View {
VStack {
ZStack {
HStack(spacing: 10) {
ForEach(self.titles.indices, id: \.self) { index in
Button(action: {
print("button\(index) pressed")
self.selectedIndex = index
}) {
Text(self.titles[index])
.foregroundColor(color)
.font(.footnote)
.fontWeight(.semibold)
}
.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5))
.modifier(FrameModifier())
.onPreferenceChange(FramePreferenceKey.self) { self.frames[index] = $0 }
}
}
.background(
Rectangle()
.fill(self.color.opacity(0.4))
.frame(
width: self.frames[self.selectedIndex].width,
height: 2,
alignment: .topLeading)
.offset(x: self.frames[self.selectedIndex].minX - self.frames[0].minX, y: self.frames[self.selectedIndex].height)
, alignment: .leading
)
}
.padding(.bottom, 15)
.animation(.easeIn(duration: 0.2))
Text("Value: \(self.titles[self.selectedIndex])")
Spacer()
}
}
}
struct FramePreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
struct FrameModifier: ViewModifier {
private var sizeView: some View {
GeometryReader { geometry in
Color.clear.preference(key: FramePreferenceKey.self, value: geometry.frame(in: .global))
}
}
func body(content: Content) -> some View {
content.background(sizeView)
}
}
struct NewPicker_Previews: PreviewProvider {
static var previews: some View {
VStack {
SegmentMenuPicker(titles: ["SuperLongValue", "1", "2", "Medium", "AnotherSuper"], color: Color.blue)
NavigationView {
SegmentMenuPicker(titles: ["SuperLongValue", "1", "2", "Medium", "AnotherSuper"], color: Color.red)
}
}
}
}
I have control with an edit and a list in a Stack. I want the list to drop down from top to bottom to animate like a fancy menu. However I am having a few issues. If I just try to use the move transition nothing happens. If I use the scale transition it always scales form center never from top down. This was just trying to get the transition overridden to slide. Anything but fade.
My control looks like so
struct Search: Identifiable {
let id: UUID
let text: String
}
struct SearchBox: View {
#State var searchParam: String = ""
#State var stuff = [Search]()
init() {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
var body: some View {
var binding = Binding<String>(
get: {
self.searchParam
},
set: {
self.stuff.append(
Search(id: UUID(), text: $0))
self.searchParam = $0
})
return VStack(spacing: 0.0) {
TextField("Search", text: binding )
.font(.title)
.padding()
.background(Color.white)
Color(.darkGray)
.frame(height: 1.0)
if stuff.count > 0 {
List(stuff, id: \.id) {
Text($0.text)
}
.transition(.slide)
}
}
}
struct SearchBox_Preview: PreviewProvider {
static var previews: some View{
SearchBox()
}
}
}
The content view is simple..
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .topLeading) {
Color.blue
SearchBox()
.frame(width: geometry.size.width * 0.40, alignment: .topLeading)
.frame(minHeight: 0, maxHeight: geometry.size.height * 0.40,
alignment: .topLeading)
.padding()
.clipped()
.shadow(radius: 5.0)
}
.background(Color.clear)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have to be missing, or not getting, something simple
Answered my own question. We need to force an animation by using withAnimation on a state. So I have changed my biding to be like so:
struct Search: Identifiable {
let id: UUID
let text: String
}
struct SearchBox: View {
#State var searchParam: String = ""
#State var stuff = [Search]()
#State var showList = false
init() {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
var body: some View {
var binding = Binding<String>(
get: {
self.searchParam
},
set: {
self.stuff.append(
Search(id: UUID(), text: $0)
)
self.searchParam = $0
// change visibility state with in animation block.
withAnimation { self.showList = stuff.count > 0 }
})
return VStack(spacing: 0.0) {
TextField("Search", text: binding )
.font(.title)
.padding()
.background(Color.white)
Color(.darkGray)
.frame(height: 1.0)
if showList {
List(stuff, id: \.id) {
Text($0.text)
}
.transition(.slide)
}
}
}
struct SearchBox_Preview: PreviewProvider {
static var previews: some View{
SearchBox()
}
}
}
I'm creating a simple form app. In that, I have checkboxes and Radio buttons, but I don't know how to do it.
I have done below code to dynamically change the selected option's colour. But it can be select multiple values. I need to select only one value among 5 values like a radio button.
E.g:
I'm taping on the second radio button. Now if I select the fourth radio button, the second one should deselect and the fourth one should get selected.
struct DCTableCell: View {
#Binding var dcValue: String
#State var isSelected: Bool = false
var body: some View {
Button(action: {
print("Tapped")
self.isSelected.toggle()
}){
ZStack {
RoundedRectangle(cornerRadius: 8)
.stroke(self.isSelected ? Color.init("borderSelected"): Color.init("border"))
.frame(height: 56)
.foregroundColor(.clear)
HStack {
Text(dcValue)
.font(.custom("Montserrat", size: 16))
.fontWeight(.medium)
.foregroundColor(self.isSelected ? Color.init("borderSelected") : .white)
.padding()
Spacer()
ZStack {
Circle()
.stroke(self.isSelected ? Color.init("borderSelected") : Color("circleBorder"))
.frame(width: 18, height: 18)
.padding()
Circle()
.frame(width: 10, height: 10)
.foregroundColor(self.isSelected ? Color.init("borderSelected"): Color.clear)
}
}
}
}
}
}
check this out...an easy to use SwiftUI RadiobuttonGroup for iOS
you can use it like this:
RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in
print("Selected is: \(selected)")
}
and here is the code:
struct ColorInvert: ViewModifier {
#Environment(\.colorScheme) var colorScheme
func body(content: Content) -> some View {
Group {
if colorScheme == .dark {
content.colorInvert()
} else {
content
}
}
}
}
struct RadioButton: View {
#Environment(\.colorScheme) var colorScheme
let id: String
let callback: (String)->()
let selectedID : String
let size: CGFloat
let color: Color
let textSize: CGFloat
init(
_ id: String,
callback: #escaping (String)->(),
selectedID: String,
size: CGFloat = 20,
color: Color = Color.primary,
textSize: CGFloat = 14
) {
self.id = id
self.size = size
self.color = color
self.textSize = textSize
self.selectedID = selectedID
self.callback = callback
}
var body: some View {
Button(action:{
self.callback(self.id)
}) {
HStack(alignment: .center, spacing: 10) {
Image(systemName: self.selectedID == self.id ? "largecircle.fill.circle" : "circle")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: self.size, height: self.size)
.modifier(ColorInvert())
Text(id)
.font(Font.system(size: textSize))
Spacer()
}.foregroundColor(self.color)
}
.foregroundColor(self.color)
}
}
struct RadioButtonGroup: View {
let items : [String]
#State var selectedId: String = ""
let callback: (String) -> ()
var body: some View {
VStack {
ForEach(0..<items.count) { index in
RadioButton(self.items[index], callback: self.radioGroupCallback, selectedID: self.selectedId)
}
}
}
func radioGroupCallback(id: String) {
selectedId = id
callback(id)
}
}
struct ContentView: View {
var body: some View {
HStack {
Text("Example")
.font(Font.headline)
.padding()
RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in
print("Selected is: \(selected)")
}
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentViewDark_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.colorScheme, .dark)
.darkModeFix()
}
}
Ok this is not an ideal solution but it works and hopefully opens your eyes to improve what you have. I give every RadioButton an ID and when the selected ID changes it updates:
struct DCTableCell: View {
var id: Int
#Binding var dcValue: String
#Binding var selectedID: Int
var body: some View {
Button(action: {
print("Tapped")
self.selectedID = self.id
}){
ZStack {
RoundedRectangle(cornerRadius: 8)
.stroke(self.id == self.selectedID ? Color.blue : Color.white)
.frame(height: 56)
.foregroundColor(.clear)
HStack {
Text(dcValue)
.font(.custom("Montserrat", size: 16))
.fontWeight(.medium)
.foregroundColor(self.id == self.selectedID ? .blue : .white)
.padding()
Spacer()
ZStack {
Circle()
.stroke(self.id == self.selectedID ? Color.blue : .black)
.frame(width: 18, height: 18)
.padding()
Circle()
.frame(width: 10, height: 10)
.foregroundColor(self.id == self.selectedID ? Color.blue: Color.clear)
}
}
}
}
}
}
And here how to use it. maybe you should create an array with ids and the strings that you want to pass in.
struct ContentView: View {
#State var str = "lolz"
#State var selectedID = -1
var body: some View {
VStack {
ForEach((1...5), id: \.self) { index in
DCTableCell(id: index, dcValue: self.$str, selectedID: self.$selectedID)
}
}
}
}
I hope this helps!
I have a similar solution, use the labels as the hashable tag which makes it very straight. So you only need to setting the outer layer: CustomDCPicker, like a general picker.
UIHostingController(rootView: CustomDCPicker())
struct CustomDCPicker: View {
#State var dcValue: String = ""
var body: some View {
VStack{
Text(dcValue).bold()
DCTable.init(dcValue: $dcValue, Labels: ["sample1","sample2","sample3","sample4","sample5"])
}
}
}
struct DCTable: View {
#Binding var dcValue: String
var Labels: [String] = []
var body: some View {
ForEach(Labels, id:\.self){
DCTableCell(dcValue: self.$dcValue, myLabel: $0)
}
}
}
struct DCTableCell: View {
#Binding var dcValue: String
var isSelected: Bool {
get{ self.dcValue == self.myLabel}
}
var myLabel : String
var body: some View {
Button(action: {
print("Tapped")
self.dcValue = self.myLabel
}){
ZStack {
RoundedRectangle(cornerRadius: 8.0)
.stroke(self.isSelected ? Color.red: Color.yellow)
.frame(height: 56)
.foregroundColor(.clear)
HStack {
Text(myLabel)
//.font(.custom("Montserrat", size: 16))
.fontWeight(.medium)
.foregroundColor(self.isSelected ? Color.red : .black)
.padding()
Spacer()
ZStack {
Circle()
.stroke(self.isSelected ? Color.red : Color.black)
.frame(width: 18, height: 18)
.padding()
Circle()
.frame(width: 10, height: 10)
.foregroundColor(self.isSelected ? Color.red: Color.clear)
}
}
}
}
}
}