#State variables seem to be getting mistakenly triggered in SwiftUI - swift

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?

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.

SwiftUI Picker not changing selection value

The following picker isn't updating $selection. Regardless of what the Picker shows while running the app, selection.rawValue always returns 0.
What is preventing the Picker from updating the State variable selection?
import SwiftUI
struct OrderPicker: View {
let initialIndex: Int
#State private var selection: Index
enum Index: Int, CaseIterable, Identifiable {
case none = 0,
first = 1,
second = 2,
third = 3
var id: Self { self }
}
init(initialIndex: Int) {
self.initialIndex = initialIndex
_selection = State(initialValue: OrderPicker.Index(rawValue: initialIndex) ?? .none)
}
var body: some View {
Form {
Picker("Order in list", selection: $selection) {
ForEach(Index.allCases) { index in
Text(String(describing: index)).tag(index)
}
}
}
.frame(height: 116)
}
func getOrderIndex() -> Int {
let index = selection.rawValue
return index
}
}
Here is an approach for you:
struct ContentView: View {
#State private var selection: PickerType = PickerType.none
var body: some View {
OrderPicker(selection: $selection)
}
}
struct OrderPicker: View {
#Binding var selection: PickerType
var body: some View {
Form {
Picker("Order in list", selection: $selection) {
ForEach(PickerType.allCases, id: \.self) { item in
Text(item.rawValue)
}
}
.pickerStyle(WheelPickerStyle())
.onChange(of: selection, perform: { newValue in
print(newValue.rawValue, newValue.intValue)
})
}
}
}
enum PickerType: String, CaseIterable {
case none, first, second, third
var intValue: Int {
switch self {
case .none: return 0
case .first: return 1
case .second: return 2
case .third: return 3
}
}
}

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

SwiftUI TextField Number Input

I'm trying to have the user input two numbers and then have those numbers be displayed and also added together. At the moment in order for the state variable to be updated you have the press return. Is there a way to have the state update like it does with text? I also have had the code inputed as a string but haven't been able to convert that to int so the numbers can be added together correctly. If anyone knows how to have it convert properly I would appreciate all the help I can get.
struct ContentView: View {
#State private var numOne: Int = 0
#State private var numTwo: Int = 0
var body: some View {
NavigationView {
VStack {
Form {
Section {
TextField("Number One", value: $numOne, formatter: NumberFormatter())
.keyboardType(.numberPad)
TextField("Number Two", value: $numTwo, formatter: NumberFormatter())
.keyboardType(.numberPad)
}
NavigationLink(
destination: addedView(numOne: $numOne, numTwo: $numTwo),
label: {
Text("Navigate")
}
)
}
}
}
}
}
struct addedView: View {
#Binding var numOne: Int
#Binding var numTwo: Int
#State private var added: Int = 0
var body: some View {
VStack {
Text("\(numOne)")
Text("\(numTwo)")
}
}
}
This shows how to enforce the TextFields be integers, and how to add them together for a result.
The main difference is that the formatter's numberStyle is .decimal. This means you will only get whole numbers / integers.
The result is added while they are Ints, so the numbers are added. If you add when they are both Strings, they will concatenate together. E.g. you want 5 + 10 to be 15, not 510.
You can then pass the result to a child view or NavigationLink if you wish.
struct ContentView: View {
#State private var numOne: Int = 0
#State private var numTwo: Int = 0
private static let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
private var result: Int {
numOne + numTwo
}
var body: some View {
NavigationView {
VStack {
Form {
Section {
TextField("Number One", value: $numOne, formatter: Self.formatter)
.keyboardType(.numberPad)
TextField("Number Two", value: $numTwo, formatter: Self.formatter)
.keyboardType(.numberPad)
}
Section {
Text("Result: \(result)")
}
}
}
}
}
}
Note that the NumberFormatter is created statically, and only once. This is because it is quite expensive to create them, especially twice every render.
This uses Combine to make sure that the text entered is a digit, and then changes it to an Int to use in computations. The return value is a closure that you can use to
struct EditableInt : View {
var intString: String = ""
var onChanged: (Int) -> Void
init(_ int: Int?, onChanged: #escaping (Int) -> Void) {
if let int = int,
let formattedInt = Formatter.numberFormatter.string(from: int as NSNumber){
self.intString = formattedInt
} else {
intString = ""
}
self.onChanged = onChanged
}
#State private var editableInt: String = ""
var body: some View {
TextField(" " + intString + " ", text: $editableInt)
.onReceive(Just(editableInt)) { newValue in
let editableInt = newValue.filter { "0123456789".contains($0) }
if editableInt != intString {
if let returnInt = Int(editableInt) {
onChanged(returnInt)
}
}
}
.onAppear { self.editableInt = self.intString }
}
}
The above is reusable, and is called from another view like this:
struct OtherView: View {
#State var otherViewInt = 0
var body: some View {
EditableInt(otherViewInt) { newInt in
otherViewInt = newInt
}
}
}

SwiftUI #AppStorage doesn't refresh in a function

I am trying to reload the data every .onAppear, but if I change the #AppStorage nearMeter's value in the SettingsView, it isn't updating the value in the reloadNearStops func and using the previous #AppStorage value.
struct SettingsView: View {
#AppStorage(“nearMeter”) var nearMeter: Int = 1
#State var meters = ["100 m","200 m","400 m","500 m","750 m","1 km"]
var body: some View {
………
Picker(selection: $nearMeter, label: HStack {
Text(NSLocalizedString(“near_stops_distance”, comment: ""))
}) {
ForEach(0 ..< meters.count, id: \.self) {
Text(meters[$0])
}
}}}
struct FavouritesView: View {
#AppStorage(“nearMeter”) var nearMeter: Int = 1
func reloadNearStops(nearMeter: Int) {
print(nearMeter)
readNearStopsTimeTable.fetchTimeTable(nearMeter: getLonLatSpan(nearMeter: nearMeter), lat: (locationManager.lastLocation?.coordinate.latitude)!, lon: (locationManager.lastLocation?.coordinate.longitude)!)
}
func getLonLatSpan(nearMeter: Int) -> Double {
let meters = [100,200,400,500,750,1000]
if nearMeter < meters.count {
return Double(meters[nearMeter]) * 0.00001
}
else {
return 0.001
}
}
var body: some View {
.....
……….
.onAppear() {
if locationManager.lastLocation?.coordinate.longitude != nil {
if hasInternetConnection {
reloadNearStops(nearMeter: nearMeter)
}
}
}}
AppStorage won't call a function but onChange can call a function when AppStorage has changed.
struct StorageFunctionView: View {
#AppStorage("nearMeter") var nearMeter: Int = 1
#State var text: String = ""
var body: some View {
VStack{
Text(text)
Button("change-storage", action: {
nearMeter = Int.random(in: 0...100)
})
}
//This will listed for changes in AppStorage
.onChange(of: nearMeter, perform: { newNearMeter in
//Then call the function and if you need to pass the new value do it like this
fetchSomething(value: newNearMeter)
})
}
func fetchSomething(value: Int) {
text = "I'm fetching \(value)"
}
}