How do I select single items out of an array in SwiftUI? - xcode

I am new to coding and I want to select a single Item out of an array. So far I have built an array of items (pretty similar to what you can see on "Tinder" for example) but If I tab on one of the items all of the others will be filled blue as well. Does any one know how do modify the code so it will work? Thank you in advance.
import SwiftUI
struct WhatInterestsYouView: View {
let sportsSelectionItems = ["NFL", "CollegeFootball", "soccer", "baseball", "tennis", "Icehockey", "Gym", "running", "climbing"]
let layout = [GridItem(.adaptive(minimum: 80))]
#State private var sportsSelectionItemIsSelected:Bool = false
var body: some View {
LazyVGrid (columns: layout, spacing: 10) {
ForEach(sportsSelectionItems, id: \.self) { selectionItem in
Text(selectionItem)
.onTapGesture {
sportsSelectionItemIsSelected.toggle()
}
.font(.footnote)
.padding(.all, 5)
.background(RoundedRectangle(cornerRadius: 10).fill(sportsSelectionItemIsSelected ? Color.blue : Color.gray.opacity(0.1)))
}
}
}
}
struct WhatInterestsYouView_Previews: PreviewProvider {
static var previews: some View {
WhatInterestsYouView()
}
}

The problem is "sportsSelectionItemIsSelected:Bool" is just a flag common in the View and it is not bound to any item in the array. so once it becomes true, it remains true for all the items in array.
Instead, have the selected item of array stored and compare it with items in loop and fill it out with your color. like this,
#State private var selectedItem: String?
var body: some View {
LazyVGrid (columns: layout, spacing: 10) {
ForEach(sportsSelectionItems, id: \.self) { selectionItem in
Text(selectionItem)
.onTapGesture {
selectedItem = selectionItem
}
.font(.footnote)
.padding(.all, 5)
.background(RoundedRectangle(cornerRadius: 10).fill(selectedItem == selectionItem ? Color.blue : Color.gray.opacity(0.1)))
}
}
}

Related

Why does ForEach not work with onTapGesture modifier correctly in SwiftUI

I am trying to figure out why all the elements in a ForEach array change when only a single element is tapped.
import SwiftUI
struct SwiftUIView: View {
#State var boolTest = false
var nums = ["1","2","3","4","5","6","7"]
var body: some View {
VStack {
ForEach(nums, id: \.self) { num in
Text("\(num)")
.font(.system(size: 70))
.foregroundColor(boolTest ? .red : .green)
.onTapGesture {
boolTest.toggle()
}
}
}
}
}
For example, when I tap on the view containing 1, all the other numbers also change colors. I want to be able to click on a number and have only that number change color.
You are using one single variable for all elements of the array nums. So, the foreground color will always be the same - they all read the same value.
What you need is to create a subview that manages its own color independently. Separate the ForEach from its content, like this:
struct SwiftUIView: View {
// This variable should not be here: all elements read the same value
// #State var boolTest = false
var nums = ["1","2","3","4","5","6","7"]
var body: some View {
VStack {
ForEach(nums, id: \.self) { num in
// Call another view
Subview(text: num)
}
}
}
}
// Create a subview with its own variable for each element
struct Subview: View {
// Here, there is one boolTest for each element
#State private var boolTest = false
let text: String
var body: some View {
Text(text)
.font(.system(size: 70))
.foregroundColor(boolTest ? .red : .green)
.onTapGesture {
boolTest.toggle()
}
}
}

#State var doesn't update when changing the value outside the view

I need to declare the checkBox array using "#State" if I want to use it inside the view using $checkBox and it works fine but when I want to update the toggles (1 or more elements of the array) in a function outside the view, the array is not updated. I tried to declare it using #Binding and #Published but without success. I saw many similar Q&A but I didn't find a solution for my case. This is my code:
struct CheckboxStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return HStack {
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(configuration.isOn ? .green : .gray)
.onTapGesture { configuration.isOn.toggle() }
configuration.label
}
}
}
struct ContentView: View {
#State var checkBox = Array(repeating: false, count: 14)
let checkBoxName: [LocalizedStringKey] = ["checkPressure", "checkVisibility", "checkCloudCover", "checkAirTemp", "checkWaterTemp", "checkWindDir", "checkWindSpeed", "checkWindGust", "checkCurrentDir", "checkCurrentSpeed", "checkSwellDir", "checkWaveHeight", "checkWavePeriod", "checkTideHeight"]
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Group {
ForEach(0..<7) { t in
Toggle(isOn: $checkBox[t], label: {
Text(checkBoxName[t]).font(.footnote).fontWeight(.light)
}).toggleStyle(CheckboxStyle()).padding(5)
}
}
Group {
ForEach(7..<14) { t in
Toggle(isOn: $checkBox[t], label: {
Text(checkBoxName[t]).font(.footnote).fontWeight(.light)
}).toggleStyle(CheckboxStyle()).padding(5)
}
}
}
}
}
}
Thx for help

SwiftUI List how to identify what item is selected on macOS

Here is what I have based on this answer. The code currently allows the user to select a cell but I cannot distinguish which cell is selected or execute any code in response to the selection. In summary, how can I execute code based on the selected cell's name and execute on click. The cell currently highlights in blue where clicked, but I want to identify it and act accordingly based on that selection. Note: I am not looking to select the cell in editing mode. Also, how can I programmatically select a cell without click?
struct OtherView: View {
#State var list: [String]
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(list, id: \.self, selection: $selectKeeper) { item in
Text(item)
}
}
}
}
Here is a gif demoing the selection
I found a workaround, but the text itself has to be clicked- clicking the cell does nothing:
struct OtherView: View {
#State var list: [String]
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(list, id: \.self, selection: $selectKeeper) { item in
Text(item)
.onTapGesture {
print(item)
}
}
}
}
}
List selection works in Edit mode, so here is some demo of selection usage
struct OtherView: View {
#State var list: [String] = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(list, id: \.self, selection: $selectKeeper) { item in
if self.selectKeeper.contains(item) {
Text(item).bold()
} else {
Text(item)
}
}.navigationBarItems(trailing: HStack {
if self.selectKeeper.count != 0 {
Button("Send") {
print("Sending selected... \(self.selectKeeper)")
}
}
EditButton()
})
}
}
}
To spare you the labour pains:
import SwiftUI
struct ContentView: View {
#State private var selection: String?
let list: [String] = ["First", "Second", "Third"]
var body: some View {
NavigationView {
HStack {
List(selection: $selection) {
ForEach(list, id: \.self) { item in
VStack {
Text(item)
}
}
}
TextField("Option", text: Binding(self.$selection) ?? .constant(""))
}
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
}
}
}
This solution deals with the problem #Isaac addressed.
screenshot
my 2 cents for custom selection and actions
works in swift 5.1 / iOS14:
see:
https://gist.github.com/ingconti/49497419e5edd84a5f3be52397c76eb4
I left a lot of debug "print".. to better understand.
You can react to the selected item by using onChange on your selection variable.
You can set the selection manually by simply setting the variable. This will also trigger onChange.
Here's some example code that will run directly in Playgrounds:
import SwiftUI
import PlaygroundSupport
struct OtherView: View {
#State var list: [String] = ["a", "b", "c"]
#State var selection: String?
var body: some View {
NavigationView {
VStack {
List(list, id: \.self, selection: $selection) { item in
Text(item)
}
.onChange(of: selection) {s in
// React to changes here
print(s)
}
Button("Select b") {
// Example of setting selection programmatically
selection = "b"
}
}
}
}
}
let view = OtherView()
PlaygroundPage.current.setLiveView(view)

Change color of image (icon) in tabItems in SwiftUI

How can i change the color of the image (icon) in the tabItems in SwiftUI?
I tried so many things and nothing works...
Thanks
Here is my test code :
import SwiftUI
struct TestTabviewIconColor: View {
var body: some View {
TabView {
Text("The First Tab")
.tabItem {
Image(systemName: "1.square.fill")
.foregroundColor(.red)
.accentColor(.red)
.colorMultiply(.red)
Text("First")
}
Text("Another Tab")
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}
Text("The Last Tab")
.tabItem {
Image(systemName: "3.square.fill")
Text("Third")
}
}
.font(.headline)
}
}
struct TestTabviewIconColor_Previews: PreviewProvider {
static var previews: some View {
TestTabviewIconColor()
}
}
If you want to have different TabBar button colors when the tab is selected than I'm reasonably confident that the Apple provided control won't do that for you.
You'll need to roll your own control. This is fairly straightforward. For an example with a THREE tabs see the code below. If you're working with a fixed number of tabs this approach might work for you. And the principles could be applied to build a control for more and variable number of tabs using #ViewBuilder etc.
I've approximated the styling of the stock TAB bar. You'd need to do a bit of refinement to look exactly the same if that's important for your use case.
struct TabLabel : View {
var text : String
var imageName : String
var color : Color
var body : some View {
VStack() {
Image(systemName: imageName)
Text(text).font(.caption)
}.foregroundColor(color)
}
}
struct TabButton : View {
#Binding var currentSelection : Int
var selectionIndex : Int
var label : TabLabel
var body : some View {
Button(action: { self.currentSelection = self.selectionIndex }) { label }.opacity(selectionIndex == currentSelection ? 0.5 : 1.0)
}
}
struct CustomTabBarView<SomeView1 : View, SomeView2 : View, SomeView3 : View> : View {
var view1 : SomeView1
var view2 : SomeView2
var view3 : SomeView3
#State var currentSelection : Int = 1
var body : some View {
let label1 = TabLabel(text: "First", imageName: "1.square.fill", color: Color.red)
let label2 = TabLabel(text: "Second", imageName: "2.square.fill", color: Color.purple)
let label3 = TabLabel(text: "Third", imageName: "3.square.fill", color: Color.blue)
let button1 = TabButton(currentSelection: $currentSelection, selectionIndex: 1, label: label1)
let button2 = TabButton(currentSelection: $currentSelection, selectionIndex: 2, label: label2)
let button3 = TabButton(currentSelection: $currentSelection, selectionIndex: 3, label: label3)
return VStack() {
Spacer()
if currentSelection == 1 {
view1
}
else if currentSelection == 2 {
view2
}
else if currentSelection == 3 {
view3
}
Spacer()
HStack() {
button1
Spacer()
button2
Spacer()
button3
}.padding(.horizontal, 48)
.frame(height: 48.0)
.background(Color(UIColor.systemGroupedBackground))
}
}
}
struct ContentView: View {
var body: some View {
let view1 = VStack() {
Text("The First Tab").font(.headline)
Image(systemName: "triangle").resizable().aspectRatio(contentMode: .fit).frame(width: 100)
}
let view2 = Text("Another Tab").font(.headline)
let view3 = Text("The Final Tab").font(.headline)
return CustomTabBarView(view1: view1, view2: view2, view3: view3)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
For change tint color of tabbar, you just need set .accentColor() modifier for the TabView to apply on items.
Try changing rendering mode of the image:
In Assets.xcassets select the image, open inspectors view and switch to attribute inspector
Then select Render As: Template Image
This worked for me.

How to access value from an item in ForEach list

How to access values from particular item on the list made with ForEach?
As you can see I was trying something like this (and many other options):
Text(item[2].onOff ? "On" : "Off")
I wanted to check the value of toggle of 2nd list item (for example) and update text on the screen saying if it's on or off.
And I know that it's something to do with #Binding and I was searching examples of this and trying few things, but I cannot make it to work. Maybe it is a beginner question. I would appreciate if someone could help me.
My ContentView:
struct ContentView: View {
// #Binding var onOff : Bool
#State private var onOff = false
#State private var test = false
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOff ? "On" : "Off")
// Text(item[2].onOff ? "On" : "Off")
}
ForEach((1...15), id: \.self) {item in
ListItemView()
}
}
.navigationBarTitle(Text("List"))
}
}
}
And ListItemView:
import SwiftUI
struct ListItemView: View {
#State private var onOff : Bool = false
// #Binding var onOff : Bool
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: self.$onOff) {
Text("Label")
}
.labelsHidden()
}
}
}
I don't know what exactly you would like to achieve, but I made you a working example:
struct ListItemView: View {
#ObservedObject var model: ListItemModel
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: self.$model.switchedOnOff) {
Text("Label")
}
.labelsHidden()
}
}
}
class ListItemModel: ObservableObject {
#Published var switchedOnOff: Bool = false
}
struct ContentView: View {
#State private var onOff = false
#State private var test = false
#State private var list = [
(id: 0, model: ListItemModel()),
(id: 1, model: ListItemModel()),
(id: 2, model: ListItemModel()),
(id: 3, model: ListItemModel()),
(id: 4, model: ListItemModel())
]
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOff ? "On" : "Off")
// Text(item[2].onOff ? "On" : "Off")
}
ForEach(self.list, id: \.id) {item in
ListItemView(model: item.model)
}
}
.navigationBarTitle(Text("List"))
}.onReceive(self.list[1].model.$switchedOnOff, perform: { switchedOnOff_second_item in
self.onOff = switchedOnOff_second_item
})
}
}
The #Published basically creates a Publisher, which the UI can listen to per onReceive().
Play around with this and you will figure out what these things do!
Good luck :)
import SwiftUI
struct ContentView: View {
#State private var onOffList = Array(repeating: true, count: 15)
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOffList[1] ? "On" : "Off")
}
ForEach((onOffList.indices), id: \.self) {idx in
ListItemView(onOff: self.$onOffList[idx])
}
}
.navigationBarTitle(Text("List"))
}
}
}
struct ListItemView: View {
#Binding var onOff : Bool
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: $onOff) {
Text("Label")
}
.labelsHidden()
}
}
}
I understand that you are directing me to use ObservableObject. And probably it's the best way to go with final product. But I am still thinking about #Binding as I just need to pass values better between 2 views only. Maybe I still don't understand binding, but I came to this solution.
struct ContentView: View {
// #Binding var onOff : Bool
#State private var onOff = false
// #State private var test = false
var body: some View {
NavigationView {
List {
HStack {
Text("Is 2nd item on or off? ")
Text(onOff ? "On" : "Off")
// Text(self.item[2].$onOff ? "On" : "Off")
// Text(item[2].onOff ? "On" : "Off")
}
ForEach((1...15), id: \.self) {item in
ListItemView(onOff: self.$onOff)
}
}
.navigationBarTitle(Text("List"))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and ListItemView:
import SwiftUI
struct ListItemView: View {
// #State private var onOff : Bool = false
#Binding var onOff : Bool
var body: some View {
HStack {
Text("99")
.font(.title)
Text("List item")
Spacer()
Toggle(isOn: self.$onOff) {
Text("Label")
}
.labelsHidden()
}
}
}
What is happening now is text is being updated after I tap toggle. But I have 2 problems:
tapping 1 toggle changes all of them. I think it's because of this line:
ListItemView(onOff: self.$onOff)
I still cannot access value of just one row. In my understanding ForEach((1...15), id: .self) make each row have their own id, but I don't know how to access it later on.

Resources