How to update and compare new data user entered to previous SwiftUI - swift

I have a list of devices with radio buttons which is filled when user chooses special cell. When I click again to change mind - I have 2 active radio buttons and cannot delete previous one. Do you have any suggestions using Swift tools?
My list cell where radio button located
struct DeviceChoiceRow : View {
#StateObject var deviceModel = DeviceViewModel()
#State var checker = false
var devices : DeviceModel
var body: some View {
HStack {
HStack(spacing: 10) {
Image(devices.device_image)
.resizable()
.scaledToFit()
.frame(width: 60, height: 60)
Text(devices.device_name)
.font(.RegularFont(size: 15))
.foregroundColor(.black)
}
Spacer()
Button(action: {
deviceModel.current_device = devices.device_name
print(deviceModel.current_device)
checker = deviceModel.check_device(current_device_entered: devices.device_name)
}) {
ZStack {
Circle()
.fill(Color.main_blue)
.frame(width: 12, height: 12)
.opacity(checker ? 1 : 0)
Circle()
.strokeBorder(Color.medium_grey, lineWidth: 1)
.frame(width: 24, height: 24)
}
}
}
.padding(.leading, 21)
.padding(.trailing, 38)
}
}
Class with Published values and checker
class DeviceViewModel : ObservableObject {
#Published var devices : [DeviceModel]
#Published var checker : String = ""
#Published var current_device : String = ""
init() {
let devices = DeviceDataSet.devices
self.devices = devices
}
func check_device(current_device_entered: String) -> Bool {
return current_device == current_device_entered
}
}

Related

How to set columns of "ForEach" next to eachOthers

I'm trying to put some data from "ForEach" side by side but I don't know how to do it in a right way...
This is what've got
import SwiftUI
struct ContentView: View {
#StateObject private var vm = notaViewModel()
#State var puntajeMaximo: String
#State var notaMaxima: String
#State var notaMinima: String
#State var notaAprobacion: String
#State var notaExigencia: String
var body: some View {
ScrollView {
// puntajeMaximo = Maximun score you can get
ForEach(0...vm.puntajeMaximo, id: \.self) { score in
HStack {
if vm.puntajeMaximo / 2 >= score {
Text("\(score) -> \(vm.getAverage(puntos: Float(score)))")
.frame(width: 100, height: 50)
.background(Color("textFieldBackground"))
.cornerRadius(5)
.foregroundColor(.red)
}
if vm.puntajeMaximo / 2 < score {
Text("\(score) -> \(vm.getAverage(puntos: Float(score)))")
.frame(width: 100, height: 50)
.background(Color("textFieldBackground"))
.cornerRadius(5)
}
}
}
}
.onAppear {
vm.setParameters(pMax: puntajeMaximo, nMaxima: notaMaxima, nMinima: notaMinima, nAprobacion: notaAprobacion, nExigencia: notaExigencia)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(puntajeMaximo: "30", notaMaxima: "70", notaMinima: "10", notaAprobacion: "40", notaExigencia: "60")
}
}
As you can see, I have a column with numbers (score) from 0 to 30 and a Float next to it, the first 15 are red and I need them to be at the left and the others, from 15 to 30 to be at the right. I've been trying with HStack, Vstack, Grid and cannot get the answer
Please help, this is driving me crazy
1. Why you have taken String instead of Number when you need to compare the value.
2. We have two different way which I have shared below from which 2 one is appropriate answer according to me still you can choose any one as per your requirement.
ANSWER 1
var body: some View {
HStack{
VStack{
ScrollView(showsIndicators: false) {
ForEach(0...puntajeMaximo, id: \.self) { score in
HStack {
if puntajeMaximo / 2 >= score {
Text("\(score) -> \(score).\(score)")
.frame(width: 100, height: 50)
.background(.white)
.cornerRadius(10)
.foregroundColor(.red)
}
}
}
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height - 100)
.background(Color.orange)
VStack{
ScrollView {
ForEach(0...puntajeMaximo, id: \.self) { score in
HStack {
if puntajeMaximo / 2 < score {
Text("\(score) -> \(score).\(score)")
.frame(width: 100, height: 50)
.background(.white)
.cornerRadius(12)
}
}
}
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height)
.background(Color.yellow)
}
}
Answer 1 Output Image
Here you can check scroll hidden and some other difference in two scrollview
ANSWER 2 (Preferred)
// Need to create Item in List which requires to conform Identifiable protocol
struct ListItem: Identifiable {
let id = UUID()
let score: Int
}
struct ContentViews: View {
// #StateObject private var vm = notaViewModel()
#State var puntajeMaximo: Int
init(puntajeMaximo: Int) {
self.puntajeMaximo = puntajeMaximo
getData()
}
var leftData: [ListItem] = []
var rightData: [ListItem] = []
var body: some View {
HStack{
VStack{
List(leftData) { data in
Text("\(data.score) -> \(data.score).\(data.score)")
.frame(width: 100, height: 50)
.background(.gray.opacity(0.3))
.cornerRadius(10)
.foregroundColor(.red)
.listRowBackground(Color.clear)
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height - 100)
.background(Color.orange)
VStack{
List(rightData) { data in
Text("\(data.score) -> \(data.score).\(data.score)")
.frame(width: 100, height: 50)
.background(.gray.opacity(0.3))
.cornerRadius(10)
.foregroundColor(.black)
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height)
.background(Color.yellow)
}
}
// Must be called in init or before rendering the view
mutating func getData() {
for score in 0...puntajeMaximo {
if (puntajeMaximo / 2 >= score) {
leftData.append(ListItem(score: score))
} else if puntajeMaximo / 2 < score {
rightData.append(ListItem(score: score))
}
}
}
}
struct ContentViews_Previews: PreviewProvider {
static var previews: some View {
ContentViews(puntajeMaximo: 30)
}
}
Answer 2 Output Image
Required
A mutating method that separates the list for left and right view
A struct that conforms Identifiable protocol to pass in ListView as a row.
You can make changes to design as per you requirement.
As this method runs loop only 1 time and separates the list instead of Answer 1 which runs loop twice for both view

Trying to create a grid of numbers that must be clicked in order and when clicked the background changes color SWIFTUI

As the title says I am trying to create a grid of random numbers that must be clicked in ascending order and when pressed the background changes color so they're no longer visible.
I figured out how to create the grid from 0 to what ever size grid I want (5x5, 10x10, etc...)
I want the background to be white to start and then when the button is pressed it changes to black
The two biggest issues I'm having are all of the buttons turning black after I press on one button and the numbers randomize every time I press a button.
Any one have any ideas?
import SwiftUI
struct ContentView: View {
#State var buttonbackg = Color.white
#State var gridsize = 100
var body: some View {
//make grid
let cellCount = (gridsize/10)
let r = numbrand(min: 00, max: ((gridsize/10) * (gridsize/10) - 1))
ZStack{
Rectangle()
.ignoresSafeArea()
.background(Color.black)
VStack{
Text("Concentration Grid")
.font(.title)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.padding(.bottom)
Spacer()
}
VStack(spacing:3) {
Spacer()
ForEach(1...cellCount, id:\.self) { i in
HStack(spacing:3) {
Spacer()
ForEach(1...cellCount, id:\.self) { c in
let a = r()
ZStack {
Button(action:{
if (self.buttonbackg == .white) {
self.buttonbackg = .black
}
}){
Text("\(a)")
.foregroundColor(Color.black)
.frame(width: 28, height: 28)
.background(buttonbackg)
.cornerRadius(5)
.multilineTextAlignment(.center)
.scaledToFit()
.padding(.all, -2)
Spacer()
}
Spacer()
}
}
}
Spacer()
}
Spacer()
}
}
}
//^grid
func numbrand(min: Int, max:Int) -> () -> Int{
//let array: [Int] = Array(min...max)
var numbers: [Int] = []
return {
if numbers.isEmpty {
numbers = Array(min...max)
}
let index = Int(arc4random_uniform(UInt32(numbers.count)))
return numbers.remove(at: index)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Every-time you click a button, view redraws itself and thus your randomArray is created again
You need to create a Struct here which holds the property whether the
button is selected or not.
Give it a try this way:
struct concentrationGame: Hashable {
var id = UUID()
var number: Int = 0
var isSelected: Bool = false
}
class RandomNumbers: ObservableObject {
#Published var randomNumbers : [concentrationGame]
init() {
self.randomNumbers = (1...100).map{_ in arc4random_uniform(100)}.map({ concentrationGame(number: Int($0)) })
}
}
struct ContentView: View {
#ObservedObject var randomNumbers = RandomNumbers()
private var gridItemLayout = [GridItem(.adaptive(minimum: 50))]
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: gridItemLayout, spacing: 20) {
ForEach($randomNumbers.randomNumbers, id: \.self) { $randomNumbers in
Button(String(randomNumbers.number)) { randomNumbers.isSelected.toggle() }
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50)
.foregroundColor(.white)
.background( randomNumbers.isSelected ? .white : .black)
.cornerRadius(10)
}
}
}
.padding()
.ignoresSafeArea(edges: .bottom)
.navigationTitle("Concentration Grid")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Turn Button into Picker

I have this code, where there are three buttons which have values stored in each of them.
When I press the Add button, the values add up and appear on the list. However, I am not able to check which values are being selected.
How can I make the button look like a picker, so that when the user clicks the button A, B, or C, it will show with a checkmark that it has been selected and when the user taps the Add button, the value of the selected button and "gp" should show up on the list? Also, the checkmark should disappear once the Add button is selected so that the user can select another list.
Such as:
If A and B are selected, the list should look like:
A = 2.0, B = 5.0, gp = 7.0.
If A and C are selected, the list should look like:
A= 2.0, C = 7.0, gp = 9.0.
I have tried using Picker and other methods, however, I couldn't get through. I have found this as the best solution. However, I am not able to put a checkmark on the buttons and not able to show the selected values on the list.
import SwiftUI
struct MainView: View {
#State var br = Double()
#State var loadpay = Double()
#State var gp : Double = 0
#State var count: Int = 1
#State var listcheck = Bool()
#StateObject var taskStore = TaskStore()
#State var a = 2.0
#State var b = 5.0
#State var c = 7.0
//var userCasual = UserDefaults.standard.value(forKey: "userCasual") as? String ?? ""
#State var name = String()
func addNewToDo() {
taskStore.tasks.append(Task(id: String(taskStore.tasks.count + 1), toDoItem: "load \(gp)", amount: Double(gp)))
self.gp = 0.0
}
func stepcount() {
count += 1
}
var body: some View {
HStack {
Button(action: { gp += a }) {
Text("A =").frame(width: 70, height: 15)
.foregroundColor(.yellow)
}
.background(RoundedRectangle(cornerRadius: 5))
.foregroundColor(.black)
Button(action: { gp += b }) {
Text("B =") .frame(width: 70, height: 15)
.foregroundColor(.yellow)
}
.background(RoundedRectangle(cornerRadius: 5))
.foregroundColor(.black)
Button(action: { gp += c }) {
Text("C =").frame(width: 70, height: 15)
.foregroundColor(.yellow)
}
.background(RoundedRectangle(cornerRadius: 5))
.foregroundColor(.black)
}
HStack(spacing: 15) {
Button(
String(format: ""),
action: {
print("pay for the shift is ")
gp += loadpay
}
)
Button(
action: {
addNewToDo()
stepcount()
},
label: { Text("Add")}
)
}
Form {
ForEach(self.taskStore.tasks) { task in
Text(task.toDoItem)
}
}
}
}
struct Task : Codable, Identifiable {
var id = ""
var toDoItem = ""
var amount = 0.0
}
class TaskStore : ObservableObject {
#Published var tasks = [Task]()
}
The issue is you are trying to turn a Button into something it is not. You can create your own view that responds to a tap and keeps its state so it knows whether it is currently selected or not. An example is this:
struct MultiPickerView: View {
#State private var selectA = false
#State private var selectB = false
#State private var selectC = false
let A = 2.0
let B = 5.0
let C = 7.0
var gp: Double {
(selectA ? A : 0) + (selectB ? B : 0) + (selectC ? C : 0)
}
var body: some View {
VStack {
HStack {
SelectionButton(title: "A", selection: $selectA)
SelectionButton(title: "B", selection: $selectB)
SelectionButton(title: "C", selection: $selectC)
}
.foregroundColor(.blue)
Text(gp.description)
.padding()
}
}
}
struct SelectionButton: View {
let title: String
#Binding var selection: Bool
var body: some View {
HStack {
Image(systemName: selection ? "checkmark.circle" : "circle")
Text(title)
}
.padding()
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(Color.blue, lineWidth: 4)
)
.padding()
.onTapGesture {
selection.toggle()
}
}
}
You could make it more adaptable by using an Array that has a struct that keeps the value and selected state, and run it though a ForEach, but this is the basics of the logic.

SwiftUI For Each Button in list - not working... sometimes

I have made a custom selection button view in SwiftUI for an app that is being developed. I cant for the life of me work out why sometimes the buttons don't do anything - It is always the last x number of buttons that don't work (which made me think it was related to the 10 view limitation of swift ui however, I've been told this isn't an issue when using a for each loop).
Sometimes it works as expected and others it cuts off the last x number of buttons. Although when it is cutting off buttons it is consistent between different simulators and physical devices. Can anybody see anything wrong here?
I am new to SwiftUI and so could be something simple...
#EnvironmentObject var QuestionManager: questionManager
var listItems: [String]
#State var selectedItem: String = ""
var body: some View {
GeometryReader {geom in
ScrollView{
VStack{
ForEach(Array(listItems.enumerated()), id: \.offset){ item in
Button(action: {
if (selectedItem != item.element) {
selectedItem = item.element
} else {
selectedItem = ""
QuestionManager.tmpAnswer = ""
}
}, label: {
GeometryReader { g in
Text("\(item.element)")
.font(.system(size: g.size.width/22))
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.black)
.lineLimit(2)
.frame(width: g.size.width, height: g.size.height)
.minimumScaleFactor(0.5)
.background(
Rectangle()
.fill((item.element == selectedItem) ? Color(.green) : .white)
.frame(width: g.size.width, height: g.size.height)
.border(Color.gray)
).scaledToFit()
}
.frame(width: geom.size.width*0.92, height: 45)
}).disabled((Int(QuestionManager.answers.year) == Calendar.current.component(.year, from: Date())) ? validateMonth(month: item.offset) : false)
}
}
.frame(width: geom.size.width)
}
}
}
} ```
As #Yrb mentioned, using enumerated() is not a great option in a ForEach loop.
Your issue could be compounded by listItems having duplicate elements.
You may want to restructure your code, something like this approach using a dedicated
item structure, works very well in my tests:
struct MyItem: Identifiable, Equatable {
let id = UUID()
var name = ""
init(_ str: String) {
self.name = str
}
static func == (lhs: MyItem, rhs: MyItem) -> Bool {
lhs.id == rhs.id
}
}
struct ContentView: View {
#EnvironmentObject var QuestionManager: questionManager
// for testing
var listItems: [MyItem] = [MyItem("1"),MyItem("2"),MyItem("3"),MyItem("4"),MyItem("6"),MyItem("7"),MyItem("8"),MyItem("9")]
#State var selectedItem: MyItem? = nil
var body: some View {
GeometryReader {geom in
ScrollView{
VStack{
ForEach(listItems){ item in
Button(action: {
if (selectedItem != item) {
selectedItem = item
} else {
selectedItem = nil
QuestionManager.tmpAnswer = ""
}
}, label: {
GeometryReader { g in
Text(item.name)
.font(.system(size: g.size.width/22))
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.black)
.lineLimit(2)
.frame(width: g.size.width, height: g.size.height)
.minimumScaleFactor(0.5)
.background(
Rectangle()
.fill((item == selectedItem) ? Color(.green) : .white)
.frame(width: g.size.width, height: g.size.height)
.border(Color.gray)
).scaledToFit()
}
.frame(width: geom.size.width*0.92, height: 45)
})
.disabled((Int(QuestionManager.answers.year) == Calendar.current.component(.year, from: Date())) ? validateMonth(item: item) : false)
}
}
.frame(width: geom.size.width)
}
}
}
func validateMonth(item: MyItem) -> Bool {
if let itemOffset = listItems.firstIndex(where: {$0.id == item.id}) {
// ... do your validation
return true
}
return false
}
}

How to have ForEach in view updated when array values change with SwiftUI

I have this code here for updating an array and a dictionary based on a timer:
class applicationManager: ObservableObject {
static var shared = applicationManager()
#Published var applicationsDict: [NSRunningApplication : Int] = [:]
#Published var keysArray: [NSRunningApplication] = []
}
class TimeOpenManager {
var secondsElapsed = 0.0
var timer = Timer()
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
self.secondsElapsed += 1
let applicationName = ws.frontmostApplication?.localizedName!
let applicationTest = ws.frontmostApplication
let appMan = applicationManager()
if let appData = applicationManager.shared.applicationsDict[applicationTest!] {
applicationManager.shared.applicationsDict[applicationTest!] = appData + 1
} else {
applicationManager.shared.applicationsDict[applicationTest!] = 1
}
applicationManager.shared.keysArray = Array(applicationManager.shared.applicationsDict.keys)
}
}
}
It works fine like this and the keysArray is updated with the applicationsDict keys when the timer is running. But even though keysArray is updated, the HStack ForEach in WeekView does not change when values are added.
I also have this view where keysArray is being loaded:
struct WeekView: View {
static let shared = WeekView()
var body: some View {
VStack {
HStack(spacing: 20) {
ForEach(0..<8) { day in
if day == weekday {
Text("\(currentDay)")
.frame(width: 50, height: 50, alignment: .center)
.font(.system(size: 36))
.background(Color.red)
.onTapGesture {
print(applicationManager.shared.keysArray)
print(applicationManager.shared.applicationsDict)
}
} else {
Text("\(currentDay + day - weekday)")
.frame(width: 50, height: 50, alignment: .center)
.font(.system(size: 36))
}
}
}
HStack {
ForEach(applicationManager.shared.keysArray, id: \.self) {dictValue in
Image(nsImage: dictValue.icon!)
.resizable()
.frame(width: 64, height: 64, alignment: .center)
}
}
}
}
}
As mentioned in the comments, applicationManager should be an ObservableObject with #Published properties.
Then, you need to tell your view that it should look for updates from this object. You can do that with the #ObservedObject property wrapper:
struct WeekView: View {
#ObservedObject private var manager = applicationManager.shared
And later:
ForEach(manager.keysArray) {
Replace anywhere you had applicatoinManager.shared with manager within the WeekView
Additional reading: https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-observedobject-to-manage-state-from-external-objects