Swift: #State property not updating on change - swift

Every time I press the button an instance of the School struct is created. I want to show that change on the screen but the button doesn't update. What am I missing to make it work?
import SwiftUI
struct School {
static var studentCount = 0
static func add(student: String) {
print("\(student) joined the school.")
studentCount += 1
}
}
struct ReviewClosuresAndStructs: View {
#State private var test = School.studentCount
var body: some View {
VStack {
Button(action: {
School.add(student: "Tyler")
}, label: {
Text("Press me. \n Students: \(test)")
})
}
}
}
struct ReviewClosuresAndStructs_Preview: PreviewProvider {
static var previews: some View {
ReviewClosuresAndStructs()
}
}

struct School {
var studentCount = 0 // <= here
mutating func add(student: String) { // <= here
print("\(student) joined the school.")
studentCount += 1
}
}
struct ReviewClosuresAndStructs: View {
#State private var test = School() // <= here
var body: some View {
VStack {
Button(action: {
test.add(student: "Tyler")
}, label: {
Text("Press me. \n Students: \(test.studentCount)") // <= here
})
}
}
}

Related

view not changing until next button press

I'm trying to change views after an array goes unchanged for one iteration cycle. However, it takes one extra button press to change views.
import SwiftUI
struct ContentView: View {
#State private var numbers: [String] = ["4", "1", "2", "5", "3"]
#State private var numbersCheck: [String] = []
#State private var index: Int = 0
#State private var done: Bool = false
var body: some View {
if done {
DoneView()
} else {
HStack {
Button(action: {
click(swap: false)
}) {
Text(numbers[index])
}
Button(action: {
click(swap: true)
}) {
Text(numbers[index + 1])
}
}
}
}
func click(swap: Bool) {
if index == 0 {
numbersCheck = numbers
}
if swap {
numbers.swapAt(index, index + 1)
}
if index < numbers.count - 2 {
index += 1
} else {
if numbersCheck != numbers {
index = 0
} else {
done = true
}
}
}
}
struct DoneView: View {
var body: some View {
Text("Done!")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I've tried different approaches to changing views (e.g. different structs, one struct/different view bodies, binding variables, etc.), but nothing's working.

I want to create a loop that creates 10 different strings and use that strings to show 10 different cards

import Foundation
class Card: Identifiable, ObservableObject {
var id = UUID()
var result: String = ""
init() {
for _ in 1...10 {
let suit = ["D","K","B","S"][Int(arc4random() % 4]
let numStr = ["1","2","3","4"][Int(arc4random() % 4]
result = suit + numStr
}
}
}
import SwiftUI
struct EasyModeView: View {
#ObservedObject var cardModel: Card
var body : some View {
HStack {
ForEach((1...10, id: \.self) { _ in
Button { } label: { Image(cardModel.result) } } }
}
}
I created loop but it always shows me 10 same cards and i want all 10 different.
My Images in Assets are named same as combination of two variables example "D1","D2","S1","S2" etc.
Here is a right way of what you are trying:
struct ContentView: View {
var body: some View {
EasyModeView()
}
}
struct EasyModeView: View {
#StateObject var cardModel: Card = Card()
var body : some View {
HStack {
ForEach(cardModel.results) { card in
Button(action: {}, label: {
Text(card.result)
.padding(3.0)
.background(Color.yellow.cornerRadius(3.0))
})
}
}
Button("re-set") { cardModel.reset() }.padding()
}
}
struct CardType: Identifiable {
let id: UUID = UUID()
var result: String
}
class Card: ObservableObject {
#Published var results: [CardType] = [CardType]()
private let suit: [String] = ["D","K","B","S"]
init() { initializing() }
private func initializing() {
if (results.count > 0) { results.removeAll() }
for _ in 0...9 { results.append(CardType(result: suit.randomElement()! + String(describing: Int.random(in: 1...4)))) }
}
func reset() { initializing() }
}
Result:
You are using only one card, instead of creating 10. Fix like this
struct EasyModeView: View {
var body : some View {
HStack {
ForEach(0..<10) { _ in
Button { } label: {
Image(Card().result)
}
}
}
}
}

Picker on select

I am trying to run a function after a user has selected a picker option.
The idea is that the user can set a default station, so I need to be able to send the selected value to a function so I can save it inside the core data module. How can I achieve this?
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedFrameworkIndex, label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}.onReceive([self.frameworks].publisher.first()) { value in
self.doSomethingWith(value: value)
}
}
}
.navigationBarTitle("Settings")
}
}
}
Instead of using onRecive try using onChange.
Your code:
.onReceive([self.frameworks].publisher.first()) { value in
self.doSomethingWith(value: value)
}
Correction:
.onChange(of: $selectedFrameworkIndex, perform: { value in
self.doSomethingWith(value: value)
})
this will trigger every time $selectedFrameworkIndex is changed.
I would use a simple .onChange(of:) call on the picker. It will pick up on the change of the #State var and allow you to act on it. Use it like this:
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedFrameworkIndex, label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}
// Put your function call in here:
.onChange(of: selectedFrameworkIndex) { value in
print(value)
}
}
}
}
.navigationBarTitle("Settings")
}
}
}
yours:
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedFrameworkIndex, label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}.onReceive([self.frameworks].publisher.first()) { value in
self.doSomethingWith(value: value)
}
}
}
.navigationBarTitle("Settings")
}
}
}
add a variable that checks to see if selectedFrameworkIndex has changed.
change:
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
#State private var savedFrameworkIndex = 0
func handler<Value: Any>(val: Value) -> Value {
if selectedFrameworkIndex != savedFrameworkIndex {
self.doSomethingWith(value: self.frameworks[selectedFrameworkIndex])
}
return val
}
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: handler(val: $selectedFrameworkIndex), label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}
}
}
.navigationBarTitle("Settings")
}
}
}

passing parameter to a SwiftUI Sheet

I need to pass a parameter calledFrom to a Sheet in SwiftUI.
Strangely, the parameter is not used on the first call, but it works on the following ones.
import SwiftUI
struct ContentView: View {
#State var showSheet = false
#State var calledFrom = -1
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(isPresented: $showSheet) { Dialog(calledFrom: calledFrom) }
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){print("Button \(i) pressed"); calledFrom = i; showSheet = true }
}
}
struct Dialog: View {
var calledFrom : Int
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack{
Text("Called from Button \(calledFrom)")
Button("close"){presentationMode.wrappedValue.dismiss()}
}
.padding()
}
}
You have to use sheet(item:) to get the behavior you're looking for. In iOS 14, the sheet view is calculated before the #State changes:
struct ActiveItem : Identifiable {
var calledFrom: Int
var id: Int { return calledFrom }
}
struct ContentView: View {
#State var activeItem : ActiveItem?
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(item: $activeItem) { item in
Dialog(calledFrom: item.calledFrom)
}
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){
print("Button \(i) pressed");
activeItem = ActiveItem(calledFrom: i)
}
}
}

Deselect all other Button selection if new one is selected

I have this code :
import SwiftUI
struct PlayButton: View {
#Binding var isClicked: Bool
var body: some View {
Button(action: {
self.isClicked.toggle()
}) {
Image(systemName: isClicked ? "checkmark.circle.fill" : "circle")
}
}
}
struct ContentView: View {
#State private var isPlaying: Bool = false
var players : [String] = ["Crown" , "King" , "Queen" , "Prince"]
var body: some View {
VStack {
ForEach(players, id: \.self) { player in
HStack {
Text(player)
PlayButton(isClicked: $isPlaying)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I want to deselect all other previously selected buttons if i select a new one. For example , if i select King and select queen , then King is deselected. How can i do that
What i have done. I honestly could not come with a solution .
I understand this might look like a lot more code to provide the answer but my assumption is you are trying to make a real world app. A real world app should be testable and so my answer is coming from a place where you can test your logic separate from your UI. This solution allows you to use the data to drive what your view is doing from a model perspective.
import SwiftUI
class PlayerModel {
let name: String
var isSelected : Bool = false
init(_ name: String){
self.name = name
}
}
class AppModel: ObservableObject {
let players : [PlayerModel] = [PlayerModel("Crown") , PlayerModel("King") ,PlayerModel("Queen") ,PlayerModel("Prince")]
var activePlayerIndex: Int?
init(){
}
func selectPlayer(_ player: PlayerModel){
players.forEach{
$0.isSelected = false
}
player.isSelected = true
objectWillChange.send()
}
}
struct PlayButton: View {
let isSelected: Bool
let action : ()->Void
var body: some View {
Button(action: {
self.action()
}) {
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
}
}
}
struct ContentView: View {
#ObservedObject var model = AppModel()
var body: some View {
VStack {
ForEach(model.players, id: \.name) { player in
HStack {
Text(player.name)
PlayButton(isSelected: player.isSelected, action: { self.model.selectPlayer(player) })
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
PlayerView()
}
}
For a single selection, at a time you can pass selectedData to PlayButton view
struct PlayButton: View {
#Binding var selectedData: String
var data: String
var body: some View {
Button(action: {
selectedData = data
}) {
Image(systemName: data == selectedData ? "checkmark.circle.fill" : "circle")
}
}
}
struct ContentView: View {
#State private var selectedPlayer: String = ""
private var players : [String] = ["Crown" , "King" , "Queen" , "Prince"]
var body: some View {
VStack {
ForEach(players.indices) { index in
let obj = players[index]
HStack {
Text(obj)
PlayButton(selectedData: $selectedPlayer, data: obj)
}
}
}
}
}