SwiftUI set a Binding from the String result of a picker - ios

I am tearing out my hair trying to figure out how to bind the picked value in my SwiftUI view:
The picker needs to be bound to the Int returned from the tags. I need to covert this Int to the String and set the Binding. How?
struct ContentView: View {
#Binding var operatorValueString:String
var body: some View {
Picker(selection: queryType, label: Text("Query Type")) {
ForEach(0..<DM.si.operators.count) { index in
Text(DM.si.operators[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
}
}
How and where can I set my operatorValueString ?
operatorValueString = DM.si.operators[queryType] //won't compile.

You can achieve the result, using your own custom binding that sets the string, whenever the picker's selection changes:
struct ContentView: View {
#State private var operatorString = ""
var body: some View {
VStack {
Subview(operatorValueString: $operatorString)
Text("Selected: \(operatorString)")
}
}
}
struct Subview: View {
#Binding var operatorValueString: String
#State private var queryType: Int = 0
let operators = ["OR", "AND", "NOT"]
var body: some View {
let binding = Binding<Int>(
get: { self.queryType },
set: {
self.queryType = $0
self.operatorValueString = self.operators[self.queryType]
})
return Picker(selection: binding, label: Text("Query Type")) {
ForEach(operators.indices) { index in
Text(self.operators[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
}
}

Related

Supply any string value to swiftUI form in different for textfield

I have two view file. I have textfield. I want to supply string value to the textfield from another view
File 1 :- Place where form is created
struct ContentView: View {
#State var subjectLine: String = ""
var body: some View {
form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine
}
}
}
}
File 2 :- Place where I want to provide value to the string and that will show in the textfield UI
struct CalenderView: View {
#Binding var subjectLine: String
var body : some View {
Button(action: {
subjectLine = "Assigned default value"
}, label: {
Text("Fill textfield")
}
}
})
}
}
This is not working. Any other way we can supply value to the textfield in other view file.
As i can understand you have binding in CalenderView
that means you want to navigate there when you navigate update there.
struct ContentView: View {
#State private var subjectLine: String = ""
#State private var showingSheet: Bool = false
var body: some View {
NavigationView {
Form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine)
}
}
.navigationBarItems(trailing: nextButton)
.sheet(isPresented: $showingSheet) {
CalenderView(subjectLine: $subjectLine)
}
}
}
var nextButton: some View {
Button("Next") {
showingSheet.toggle()
}
}
}
CalendarView
struct CalenderView: View {
#Binding var subjectLine: String
#Environment(\.dismiss) private var dismiss
var body: some View {
Button {
subjectLine = "Assigned default value"
dismiss()
} label: {
Text("Fill textfield")
}
}
}

SwiftUI: Changing a private var with a slider

I have a private variable in a struct which I can only access using a setter and getter. I want to change this variable using a slider, so I am attempting to bind a different var with this var using willSet:
struct MyStruct {
private var myVar: Float? = nil
mutating func setMyVar(newVal: Float) {
if (SomeCondition) { // always true when the slider is in use
myVar = newVal
} else {
// stuff
}
}
func getMyVar() -> Float {
myVar == nil ? 0.0 : myVar!
}
}
struct MyView: View {
#State var myStructToEdit = MyStruct()
#State var tempVar: Double = 0.0 {
willSet {
myStructToEdit.setMyVar(newVal: Float(newValue))
}
}
var body: some View {
VStack {
Text(String(tempVar))
Text(String(myStructToEdit.getMyVar()))
Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
}
}
}
As the slider moves, tempVar changes but MyVar doesn't. What is the correct way to achieve this binding?
Property observers won't work with SwiftUI #State variables.
Use .onChange(of:) to act upon changes to tempVar:
struct MyView: View {
#State var myStructToEdit = MyStruct()
#State var tempVar: Double = 0.0
var body: some View {
VStack {
Text(String(tempVar))
Text(String(myStructToEdit.getMyVar()))
Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
.onChange(of: tempVar) { value in
myStructToEdit.setMyVar(newVal: Float(value))
}
}
}
}
Use Binding(get:set:) to directly set and get the value in your struct:
You don't need tempVar. You can directly set and get the value to and from your struct.
struct ContentView: View {
#State var myStructToEdit = MyStruct()
var body: some View {
VStack {
Text(String(myStructToEdit.getMyVar()))
Slider(value: Binding(get: {
myStructToEdit.getMyVar()
}, set: { value in
myStructToEdit.setMyVar(newVal: value)
}), in: 1.0...20.0, step: 1.0)
}
}
}
or assign the Binding to a let to make it cleaner:
struct ContentView: View {
#State var myStructToEdit = MyStruct()
var body: some View {
let myVarBinding = Binding(
get: { myStructToEdit.getMyVar() },
set: { value in myStructToEdit.setMyVar(newVal: value) }
)
VStack {
Text(String(myStructToEdit.getMyVar()))
Slider(value: myVarBinding, in: 1.0...20.0, step: 1.0)
}
}
}
Note: Since myVarBinding is already a binding, you do not need to use a $ to turn it into a binding.

SwiftUI is it possible to pass range of binding in ForEach?

I'd like to pass a range of an array in a model inside ForEach.
I recreated an example:
import SwiftUI
class TheModel: ObservableObject {
#Published var list: [Int] = [1,2,3,4,5,6,7,8,9,10]
}
struct MainView: View {
#StateObject var model = TheModel()
var body: some View {
VStack {
ForEach (0...1, id:\.self) { item in
SubView(subList: $model.list[0..<5]) <-- error if I put a range
}
}
}
}
struct SubView: View {
#Binding var subList: [Int]
var body: some View {
HStack {
ForEach (subList, id:\.self) { item in
Text("\(item)")
}
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
The work around
I found is to pass all the list and perform the range inside the subView. But I'd like don't do this because the array is very big:
struct MainView: View {
#StateObject var model = TheModel()
var body: some View {
VStack {
ForEach (0...1, id:\.self) { i in
SubView(subList: $model.list, number: i, dimension: 5)
}
}
}
}
struct SubView: View {
#Binding var subList: [Int]
var number: Int
var dimension: Int
var body: some View {
HStack {
ForEach (subList[number*dimension..<dimension*(number+1)].indices, id:\.self) { idx in
Button(action: {
subList[idx] += 1
print(subList)
}, label: {
Text("num: \(subList[idx])")
})
}
}
}
}
I would pass the model to the subview since it is a class and will be passed by reference and then pass the range as a separate parameter.
Here is my new implementation of SubView
struct SubView: View {
var model: TheModel
var range: Range<Int>
var body: some View {
HStack {
ForEach (model.list[range].indices, id:\.self) { idx in
HStack {
Button(action: {
model.list[idx] += 1
print(model.list)
}, label: {
Text("num: \(model.list[idx])")
})
}
}
}
}
}
Note that I added indices to the ForEach header to make sure we access the array using an index and not with a value from the array.
The calling view would then look like
var body: some View {
VStack {
SubView(model: model, range: (0..<5))
Text("\(model.list.map(String.init).joined(separator: "-"))")
}
The extra Text is just there for testing purposes

Multiple SwiftUI views sharing data members

Here's my situation.... I have a view called SwiftUIView() - it contains a picker and a textbox
You can see it here:
import SwiftUI
import Combine
class section_array: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var first_value = 0 { willSet { objectWillChange.send() } }
var second_value = 0 { willSet { objectWillChange.send() } }}
struct SwiftUIView: View {
#Binding var master_sections: [ContentView.my_Section]
#ObservedObject private var first_value: section_array = section_array()
// #Binding var current_section: ContentView.my_Section
#State private var comment: String = String()
var body: some View {
VStack {
Picker(selection: self.$first_value.first_value , label: Text("Subsection")) {
ForEach(0 ..< self.master_sections.count) {
Text(self.master_sections[$0].name)
}}
TextField("Enter your comment.", text: $comment)
}
}
}
Attempting to utlizie this view here:
ForEach(master_subsections) { result in
SwiftUIView(master_sections: self.$subsections)
}
Causes the right amount of SwiftUIViews to appear, however, they all seem to share the same $comment and $first_value, these are private fields - I need them unique to each occurance of the SwiftUIView().

SwiftUI Picker in a Form doesn't show the selected row

I am trying to have a Picker that shows which option is currently selected.
Try out the following code which correctly selects the right option but the picker does not show which option is selected:
import SwiftUI
struct ContentView: View {
#State var selectedIndex: Int = 0
let strings: [String] = {
var strings: [String] = []
for i in 0..<10 {
strings.append("\(i)")
}
return strings
}()
var body: some View {
NavigationView {
VStack {
Form {
Picker(selection: $selectedIndex,
label: Text("Selected string: \(strings[selectedIndex])")) {
ForEach(0..<strings.count) {
Text(self.strings[$0]).tag($0)
}
}
}
}
.navigationBarTitle("Form Picker",
displayMode: NavigationBarItem.TitleDisplayMode.inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Anyone know what could be wrong? It's observed using Xcode 11.1 and iOS 13.1
I created the simple picker I call "ListPicker" which should fit the bill. I've written it so it works well in a Form; if you need it outside of a Form you will have to tinker with it. If you see any way to improve the code, please add a comment; this is still a learning experience for all of us.
// MARK: - LIST PICKER (PUBLIC)
struct ListPicker<Content: View>: View {
#Binding var selectedItem: Int
var label: () -> Content
var data: [Any]
var selectedLabel: String {
selectedItem >= 0 ? "\(data[selectedItem])" : ""
}
var body: some View {
NavigationLink(destination: ListPickerContent(selectedItem: self.$selectedItem, data: self.data)) {
ListPickerLabel(label: self.label, value: "\(self.selectedLabel)")
}
}
}
// MARK: - INTERNAL
private struct ListPickerLabel<Content: View>: View {
let label: () -> Content
let value: String
var body: some View {
HStack(alignment: .center) {
self.label()
Spacer()
Text(value)
.padding(.leading, 8)
}
}
}
private struct ListPickerContentItem: View {
let label: String
let index: Int
let isSelected: Bool
var body: some View {
HStack {
Text(label)
Spacer()
if isSelected {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}.background(Color.white) // so the entire row is selectable
}
}
private struct ListPickerContent: View {
#Environment(\.presentationMode) var presentationMode
#Binding var selectedItem: Int
var data: [Any]
var body: some View {
List {
ForEach(0..<data.count) { index in
ListPickerContentItem(label: "\(self.data[index])", index: index, isSelected: index == self.selectedItem).onTapGesture {
self.selectedItem = index
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
}
Then you can use it like this:
#State var selectedCar: Int = 0
let cars = ["Jaguar", "Audi", "BMW", "Land Rover"]
Form {
ListPicker(
selectedItem: self.$selectedCar,
label: {
Text("Cars")
},
data: self.cars
)
}

Resources