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
}
}
}
Related
I have a short array of items that I want to display in a segmented picker. I'm passing the selected item (0, by default). The picker displays, but no item is selected, and the picker is unresponsive to clicks (in the simulator). I have a very similar picker that uses percentage values, and it works correctly. I am guessing that the issue has to do with the closure that I'm passing to the ForEach loop, but I am unclear on what syntax I should be using, if that is in fact the issue.
The code is as follows:
#State private var originalUnit = 0
let sourceUnits = ["meters","kilometers","feet","yards","miles"]
var body: some View {
NavigationView {
Form {
Section {
Picker("Unit", selection $originalUnit) {
ForEach(sourceUnits, id: \.self {
Text($0)
}
} .pickerStyle(.segmented)
} header: {
Text("Choose Unit")
}
} .navigationTitle("MyApp")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Any insights on this would be appreciated. Thanks in advance!
You have a type mismatch between your originalUnit (Int) and your sourceUnits (String). Your selection needs to match the type.
struct ContentView: View {
#State private var originalUnit = "meters" //<-- Here
let sourceUnits = ["meters","kilometers","feet","yards","miles"]
var body: some View {
NavigationView {
Form {
Section {
Picker("Unit", selection: $originalUnit) {
ForEach(sourceUnits, id: \.self) {
Text($0)
}
} .pickerStyle(.segmented)
} header: {
Text("Choose Unit")
}
} .navigationTitle("MyApp")
}
}
}
If, for some reason, you really needed originalUnit to be an Int, you could use enumerated (normally not the most efficient method for large collections in a ForEach, but that'll be inconsequential for something this small) so that the id is the index and matches the type (Int) of originalUnit:
struct ContentView: View {
#State private var originalUnit = 0
let sourceUnits = ["meters","kilometers","feet","yards","miles"]
var body: some View {
NavigationView {
Form {
Section {
Picker("Unit", selection: $originalUnit) {
ForEach(Array(sourceUnits.enumerated()), id: \.0) { //<-- .0 is the index (Int)
Text($0.1) //<-- .1 is the original item String
}
} .pickerStyle(.segmented)
} header: {
Text("Choose Unit")
}
} .navigationTitle("MyApp")
}
}
}
You can set tag for your picker's row. Tag is any hashable type.
Refer this code work with any type of object for selection
struct TestView: View {
#StateObject var viewModel = TestViewModel()
var body: some View {
VStack {
Text(viewModel.selectedItem.title)
Picker("Select item", selection: $viewModel.selectedItem) {
ForEach(viewModel.items) { makeRowForItem($0) }
}
}
}
#ViewBuilder
func makeRowForItem(_ item: Item) -> some View {
Text(item.title).tag(item)
}
}
struct Item: Identifiable, Hashable {
var id = UUID().uuidString
var title = "Untitled"
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
class TestViewModel: ObservableObject {
#Published var selectedItem: Item
#Published var items: [Item]
init() {
let list = (1..<10).map { Item(title: "Untitled \($0)") }
items = list
selectedItem = list.first!
}
}
In the following example how can I change the value of activeSheet based on how SwiftUI updates aArrived and bArrived?
struct ContentView: View {
#AppStorage("didAArrive") var aArrived: Bool = false
#AppStorage("didBArrive") var bArrived: Bool = false
enum ActiveSheet: Identifiable {
case aArrived, bArrived
var id: Int {
hashValue
}
}
#State private var activeSheet: ActiveSheet?
var body: some View {
Text("Hello")
.sheet(
item: $activeSheet,
content: { item in
switch item {
case .aArrived:
Text("A arrived")
case .bArrived:
Text("B arrived")
}
}
)
}
}
You can create a custom binding for the sheet which gets its value based on aArrived and bArrived. The binding value will be initialised based on aArrived or bArrived and get updated every time that either one changes.
struct ContentView: View {
#AppStorage("didAArrive") var aArrived: Bool = false
#AppStorage("didBArrive") var bArrived: Bool = false
enum ActiveSheet: Identifiable {
case aArrived, bArrived
var id: Int {
hashValue
}
}
var body: some View {
let sheetBinding = Binding<ActiveSheet?>(
get: {
if aArrived && bArrived {
return ActiveSheet.aArrived
} else if aArrived {
return ActiveSheet.aArrived
} else if bArrived {
return ActiveSheet.bArrived
} else {
return nil
}
},
set: { _ in }
)
VStack(spacing: 20) {
Toggle("A arrived", isOn: $aArrived)
Toggle("B arrived", isOn: $bArrived)
}
.sheet(
item: sheetBinding,
content: { item in
switch item {
case .aArrived:
Text("A arrived")
case .bArrived:
Text("B arrived")
}
}
)
}
}
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)"
}
}
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)
}
}
}
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?