view not changing until next button press - swift

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.

Related

Swift: #State property not updating on change

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
})
}
}
}

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)
}
}
}
}
}

In a List. Left side of mutating operator isn't mutable: 'item' is a 'let' constant

Xcode 12. code and lifecycle are Swiftui.
I've looked at other peoples related questions and it seems like they just dump their entire projects code on here; it's a bit overwhelming to pick what I need from it.
So, I've broken the issue down into the simplest example I can.
The goal is to have numberX iterate when I press the + button.
Thanks in advance!
struct InfoData: Identifiable {
var id = UUID()
var nameX: String
var numberX: Int
}
class ContentX: ObservableObject {
#Published var infoX = [
InfoData(nameX: "Example", numberX: 1)
]
}
struct ContentView: View {
#StateObject var contentX = ContentX()
var body: some View {
List(contentX.infoX) { item in
HStack {
Text("\(item.nameX)")
Spacer()
Button("+") {
item.numberX += 1 //Eror shows up here <<
}
Text("\(item.numberX)")
}
}
}
}
In the syntax that you're using, item is an immutable value, as the error tells you. You can't mutate it, because it doesn't represent a true connection to the array it comes from -- it's just a temporary readable copy that is being used in the List iteration.
If you can upgrade to Xcode 13, you have access to something called element binding syntax in List and ForEach that lets you do this:
struct ContentView: View {
#StateObject var contentX = ContentX()
var body: some View {
List($contentX.infoX) { $item in //<-- Here
HStack {
Text("\(item.nameX)")
Spacer()
Button("+") {
item.numberX += 1
}
Text("\(item.numberX)")
}
}
}
}
This gives you a Binding to item that is mutable, allowing you to change its value(s) and have them reflected in the original array.
Prior to Xcode 13/Swift 5.5, you'd have to define your own way to alter an element in the array. This is one solution:
class ContentX: ObservableObject {
#Published var infoX = [
InfoData(nameX: "Example", numberX: 1)
]
func alterItem(item: InfoData) {
self.infoX = self.infoX.map { $0.id == item.id ? item : $0 }
}
}
struct ContentView: View {
#StateObject var contentX = ContentX()
var body: some View {
List(contentX.infoX) { item in
HStack {
Text("\(item.nameX)")
Spacer()
Button("+") {
var newItem = item
newItem.numberX += 1
contentX.alterItem(item: newItem)
}
Text("\(item.numberX)")
}
}
}
}
Or, another option where a custom Binding is used:
class ContentX: ObservableObject {
#Published var infoX = [
InfoData(nameX: "Example", numberX: 1)
]
func bindingForItem(item: InfoData) -> Binding<InfoData> {
.init {
self.infoX.first { $0.id == item.id }!
} set: { newValue in
self.infoX = self.infoX.map { $0.id == item.id ? newValue : $0 }
}
}
}
struct ContentView: View {
#StateObject var contentX = ContentX()
var body: some View {
List(contentX.infoX) { item in
HStack {
Text("\(item.nameX)")
Spacer()
Button("+") {
contentX.bindingForItem(item: item).wrappedValue.numberX += 1
}
Text("\(item.numberX)")
}
}
}
}

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)
}
}
}
}
}

#State variables seem to be getting mistakenly triggered in SwiftUI

I have the following code for a simple SwiftUI project:
import SwiftUI
enum Unit: String, CaseIterable {
case m = "Meters"
case km = "Kilometers"
}
struct ContentView: View {
// Note that this always has to be an int index to the array used in the picker
#State private var inputUnit = 0
#State private var outputUnit = 1
#State private var inputAmount = ""
let numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.alwaysShowsDecimalSeparator = false
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 2
return formatter
}()
var inputUnitText: String {
Unit.allCases[inputUnit].rawValue
}
var outputUnitText: String {
Unit.allCases[outputUnit].rawValue
}
var outputAmount: Double {
let input: Unit = Unit.allCases[inputUnit]
let output: Unit = Unit.allCases[outputUnit]
switch input {
case .m:
switch output {
case .m: return Double(inputAmount) ?? 0
case .km: return (Double(inputAmount) ?? 0) / 1000
}
case .km:
switch output {
case .km: return Double(inputAmount) ?? 0
case .m: return (Double(inputAmount) ?? 0) * 1000
}
}
}
var body: some View {
NavigationView {
Form {
Section(header: Text("Input Unit")) {
Picker("", selection: $inputUnit) {
ForEach(0..<Unit.allCases.count) {
Text(Unit.allCases[$0].rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Input amount")) {
TextField(inputUnitText, text: $inputAmount)
.keyboardType(.numberPad)
}
Section(header: Text("Output Unit")) {
Picker("", selection: $outputUnit) {
ForEach(0..<Unit.allCases.count) {
Text(Unit.allCases[$0].rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Output amount")) {
Text("\(numberFormatter.string(from: outputAmount as NSNumber)!) \(outputUnitText)")
}
}
.navigationBarTitle("WeConvert")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If I choose a segment on one of the segment controls or update the number the other segment controls seems to be moving. I assume their state is updating. For example:
If I choose an option in the second picker the first picker will also wobble like something has updated. I don't understand this because the states for both pickers are independent. Any ideas what is going on here?