SwiftUI: How to not show "case none" in a ForEach loop - foreach

Currently, I have a foreach loop that displays all the Icons cases. I want to hide case none because I need a selected case for my EffectIcon view.
ParentView :
enum Icons: String,CaseIterable, Hashable {
case overlayText = "Text"
case image = "Image"
case rotate = "Rotate"
...
case none
}
struct EffectPanel: View {
#State var currentIconSelected: Icons = .none
#State var listIcons = [Bool](repeating: false, count: Icons.allCases.count)
var body: some View {
VStack {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
ForEach(0..<listIcons.count, id: \.self) { i in
EffectIcon(icon: Icons(rawValue: Icons.allCases[i].rawValue)!, currentIconSelected: $currentIconSelected)
}
}
.background(Color.black)
}
}
}
}

you could try something like this to hide the .none cases:
ForEach(0..<listIcons.count, id: \.self) { i in
if Icons(rawValue: Icons.allCases[i].rawValue)! != .none {
EffectIcon(icon: Icons(rawValue: Icons.allCases[i].rawValue)!,
currentIconSelected: $currentIconSelected)
}
}

Here is possible alternate (just simpler)
ForEach(Icons.allCases, id: \.self) { i in
if i != .none {
EffectIcon(icon: Icons(rawValue: i.rawValue), currentIconSelected: $currentIconSelected)
}
}

You could also add this static var to your enum:
enum Icons {
static var selectableCases: [Icons] {
var selectableCases = allCases
selectableCases.removeLast()
return selectableCases
}
}

Related

How to change picker based on text field input

I'm currently trying to change the data the picker will display based on the value in the series text field. I'm not getting the picker to show up, I'm not getting any errors but I'm getting this warning "Non-constant range: not an integer range" for both the ForEach lines below.
struct ConveyorTracks: View {
#State private var series = ""
#State private var selectedMaterial = 0
#State private var selectedWidth = 0
#State private var positRack = false
let materials8500 = ["HP", "LF", "Steel"]
let widths8500 = ["3.25", "4", "6"]
let materials882 = ["HP", "LF", "PS", "PSX"]
let widths882 = ["3.25", "4.5", "6","7.5", "10", "12"]
var materials: [String] {
if series == "8500" {
return materials8500
} else if series == "882" {
return materials882
} else {
return []
}
}
var widths: [String] {
if series == "8500" {
return widths8500
} else if series == "882" {
return widths882
} else {
return []
}
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Text("Series:")
TextField("Enter series", text: $series)
}.padding()
HStack {
Text("Material:")
Picker("Materials", selection: $selectedMaterial) {
ForEach(materials.indices) { index in
Text(self.materials[index])
}
}.pickerStyle(SegmentedPickerStyle())
}.padding()
HStack {
Text("Width:")
Picker("Widths", selection: $selectedWidth) {
ForEach(widths.indices) { index in
Text(self.widths[index])
}
}.pickerStyle(SegmentedPickerStyle())
}.padding()
HStack {
Text("Positive Rack:")
Toggle("", isOn: $positRack)
}.padding()
}
}
}
struct ConveyorTrack_Previews: PreviewProvider {
static var previews: some View {
ConveyorTracks()
}
}
I would like the pickers to change based on which value is input in the series text field, for both materials and width.
Perhaps pickers isn't the best choice, I am open to any suggestions.
Thanks!
ForEach(materials.indices)
Needs to be
ForEach(materials.indices, id: \.self)
Because you are not using a compile-time constant in ForEach.
In general for fixed selections like this your code can be much simpler if you make everything enums, and make the enums Identifiable. This simplified example only shows one set of materials but you could return an array of applicable materials depending on the selected series (which could also be an enum?)
enum Material: Identifiable, CaseIterable {
case hp, lf, steel
var id: Material { self }
var title: String {
... return an appropriate title
}
}
#State var material: Material
...
Picker("Material", selection: $material) {
ForEach(Material.allCases) {
Text($0.title)
}
}

How to change the selection of the second picker after changing the first picker?

Here I'm trying to create the first little app that helps to convert a value from one unit of measurement to another.
The first conversion picker helps me to select data for the second picker(from which measure I'm going to converse) and the third picker(to which measure I'm going converse).
But when I'm changing the first picker - it doesn't change the #State value of the second and third pickers. I tried different approaches but the result is the same. Could you please help me?
struct ContentView: View {
#State private var userInput = 0.0
#State var selectionOfTypeOfConversion: Conversions = .temperature
#State var selectionFromConversion: String = TemperatureConversions.celsius.rawValue
#State var selectionToConversion: String = TemperatureConversions.celsius.rawValue
#FocusState private var inputIsFocused: Bool
var output: Double {
//doing all counting
}
var body: some View {
VStack {
NavigationView {
Form {
Section {
Picker("Type of conversion", selection: $selectionOfTypeOfConversion) {
ForEach(Conversions.allCases, id: \.self) {
Text($0.rawValue)
}
}.onSubmit {
checkConvention()
}
TextField("", value: $userInput, format: .number)
.keyboardType(.decimalPad)
.focused($inputIsFocused)
Picker("From conversion", selection: $selectionFromConversion) {
switch selectionOfTypeOfConversion {
case Conversions.temperature:
ForEach(TemperatureConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.length:
ForEach(LengthConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.volume:
ForEach(VolumeConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
}
}
} header: {
Text("From")
}
Section {
Picker("To conversion", selection: $selectionToConversion) {
switch selectionOfTypeOfConversion {
case Conversions.temperature:
ForEach(TemperatureConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.length:
ForEach(LengthConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
case Conversions.volume:
ForEach(VolumeConversions.allCases, id: \.self) {
Text($0.rawValue).tag($0.rawValue)
}
}
}
Text(output, format: .number)
} header: {
Text("To")
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
inputIsFocused = false
}
}
}
}
}
}
private func checkConvention() {
if selectionOfTypeOfConversion == Conversions.temperature {
selectionFromConversion = TemperatureConversions.celsius.rawValue
selectionToConversion = TemperatureConversions.celsius.rawValue
} else if selectionOfTypeOfConversion == Conversions.length{
selectionFromConversion = LengthConversions.meters.rawValue
selectionToConversion = LengthConversions.meters.rawValue
} else if selectionOfTypeOfConversion == Conversions.volume{
selectionFromConversion = VolumeConversions.milliliters.rawValue
selectionToConversion = VolumeConversions.milliliters.rawValue
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
enum Conversions: String, CaseIterable {
case temperature = "temperature"
case length = "length"
case volume = "volume"
}
enum TemperatureConversions: String, CaseIterable {
var id: Self { self }
case celsius = "celsius"
case fahrenheit = "fahrenheit"
case kelvin = "kelvin"
}
enum LengthConversions: String, CaseIterable {
var id: Self { self }
case meters = "meters"
case feet = "feet"
case miles = "miles"
}
enum VolumeConversions: String, CaseIterable {
var id: Self { self }
case milliliters = "ml"
case pints = "pints"
case gallons = "gallons"
}
Expecting: change the formula which counts input into output.

if Array is empty, append all values in enum to array

First Dabble in SwiftUI, I've managed to get the below code working such that when I press a button, it will show a "selected" state and add the selected sports into an array. (and remove from the array if "deselected")
However, I can't figure out how to initialise the sportsArray with ALL values within the enum HelperIntervalsIcu.icuActivityType.allCases if it is initially empty.
I tried to put in
if sportsArray.isEmpty {
HelperIntervalsIcu.icuActivityType.allCases.forEach {
sportsArray.append($0.rawvalue)
}
but Xcode keeps telling me type() cannot conform to View or things along those lines
struct selectSports: View {
#State private var sportsArray = [String]()
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
// https://stackoverflow.com/a/69455949/14414215
ForEach(Array(HelperIntervalsIcu.icuActivityType.allCases), id:\.rawValue) { sport in
Button(action: {
addSports(sport: sport.rawValue)
}) {
HStack {
Image(getSportsIcon(sport: sport.rawValue))
.selectedSportsImageStyle(sportsArray: sportsArray, sport: sport.rawValue)
Text(sport.rawValue)
}
}
.buttonStyle(SelectedSportButtonStyle(sportsArray: sportsArray, sport: sport.rawValue))
}
}
}
}
struct SelectedSportButtonStyle: ButtonStyle {
var sportsArray: [String]
var sport: String
var selectedSport : Bool {
if sportsArray.contains(sport) {
return true
} else {
return false
}
}
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.font(selectedSport ? Font.subheadline.bold() : Font.subheadline)
.aspectRatio(contentMode: .fit)
.foregroundColor(selectedSport ? Color.orange : Color(UIColor.label))
.padding([.leading, .trailing], 15)
.padding([.top, .bottom],10)
.overlay(
RoundedRectangle(cornerRadius: 5.0)
.stroke(lineWidth: 2.0)
.foregroundColor(selectedSport ? Color.orange : Color.gray)
)
.offset(x: 10, y: 0)
}
}
func addSports(sport: String) {
if sportsArray.contains(sport) {
let sportsIndex = sportsArray.firstIndex(where: { $0 == sport })
sportsArray.remove(at: sportsIndex!)
} else {
sportsArray.append(sport)
}
print("Selected Sports:\(sportsArray)")
}
}
No Sports Selected (in this case sportsArray is empty and thus the default state which I would like have would be to have ALL sports Pre-selected)
2 Sports Selected
I assume you wanted to do that in onAppear, like
struct selectSports: View {
#State private var sportsArray = [String]()
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
// .. other code
}
.onAppear {
if sportsArray.isEmpty { // << here !!
HelperIntervalsIcu.icuActivityType.allCases.forEach {
sportsArray.append($0.rawvalue)
}
}
}
}
}

SwiftUI DisclosureGroup Expand each section individually

I'm using a Foreach and a DisclosureGroup to show data.
Each section can Expand/Collapse.
However they all are Expanding/Collapsing at the same time.
How do I Expand/Collapse each section individually?
struct TasksTabView: View {
#State private var expanded: Bool = false
var body: some View {
ForEach(Array(self.dict!.keys.sorted()), id: \.self) { key in
if let tasks = self.dict![key] {
DisclosureGroup(isExpanded: $expanded) {
ForEach(Array(tasks.enumerated()), id:\.1.title) { (index, task) in
VStack(alignment: .leading, spacing: 40) {
PillForRow(index: index, task: task)
.padding(.bottom, 40)
}.onTapGesture {
self.selectedTask = task
}
}
} label: {
Header(title: key, SubtitleText: Text(""), showTag: true, tagValue: tasks.count)
}.accentColor(.rhinoRed)
}
}
}
}
You could have a Set containing the keys of all the expanded sections. If a section is expanded, add it to the set. It is then removed when it is collapsed.
Code:
#State private var expanded: Set<String> = []
DisclosureGroup(
isExpanded: Binding<Bool>(
get: { expanded.contains(key) },
set: { isExpanding in
if isExpanding {
expanded.insert(key)
} else {
expanded.remove(key)
}
}
)
) {
/* ... */
}
It’s not working because the expanded flag links the DiscolureGroup all together. DisclosureGroup is smart enough to expand/collapse each item individually (see below demo).
struct ContentView: View {
struct Task: Identifiable, Hashable {
let id: UUID = UUID()
let name: String = "Task"
}
let allTasks: [[Task]] = [
[Task(), Task()],
[Task()],
[Task(), Task(), Task()]
]
var body: some View {
VStack {
ForEach(allTasks.indices, id: \.self) { indice in
DisclosureGroup() {
ForEach(allTasks[indice]) { task in
Text(task.name)
}
} label: {
Text("Tasks \(indice)")
}
}
}
}
}
However it seems that OutlineGroup is a perfect fit to your use case:
struct Task<Value: Hashable>: Hashable {
let value: Value
var subTasks: [Task]? = nil
}
List(allTasks, id: \.value, children: \.subTasks) { tree in
Text(tree.value).font(.subheadline)
}.listStyle(SidebarListStyle())

List Item with Picker (segmented control) subview and selection gesture

I have a List and items containing a Picker view (segmented control stye). I would like to handle selection and picker state separately. the picker should be disabled when list item is not selected.
The problem is:
first tap - selects the list item. (selected = true)
tap on picker - set selection = false
On UIKit, Button/SegmentControl/etc... catch the 'tap' and do not pass through to TableView selection state.
struct ListView: View {
#State var selected = Set<Int>()
let items = (1...10).map { ItemDataModel(id: $0) }
var body: some View {
List(items) { item in
ListItemView(dataModel: item)
.onTapGesture { if !(selected.remove(item.id) != .none) { selected.insert(item.id) }}
}
}
}
struct ListItemView: View {
#ObservedObject var dataModel: ItemDataModel
var body: some View {
let pickerBinding = Binding<Int>(
get: { dataModel.state?.rawValue ?? -1 },
set: { dataModel.state = DataState(rawValue: $0) }
)
HStack {
Text(dataModel.title)
Spacer()
Picker("sdf", selection: pickerBinding) {
Text("State 1").tag(0)
Text("State 2").tag(1)
Text("State 3").tag(2)
}.pickerStyle(SegmentedPickerStyle())
}
}
}
enum DataState: Int {
case state1, state2, state3
}
class ItemDataModel: Hashable, Identifiable, ObservableObject {
let id: Int
let title: String
#Published var state: DataState? = .state1
init(id: Int) {
self.id = id
title = "item \(id)"
}
func hash(into hasher: inout Hasher) {
hasher.combine(title)
}
static func == (lhs: ItemDataModel, rhs: ItemDataModel) -> Bool {
return lhs.id == rhs.id
}
}
[Please try to ignore syntax errors (if exsist) and focus on the gesture issue]
A possible solution is to apply modifiers only when the Picker is not selected:
struct ListView: View {
#State var selected = Set<Int>()
let items = (1 ... 10).map { ItemDataModel(id: $0) }
var body: some View {
List(items) { item in
if selected.contains(item.id) {
ListItemView(dataModel: item)
} else {
ListItemView(dataModel: item)
.disabled(true)
.onTapGesture {
if !(selected.remove(item.id) != .none) {
selected.insert(item.id)
}
}
}
}
}
}

Resources