i have a form that works correctly but would like to add a toggle to indicated if a garment is used or new. if it is new toggle on, if its used a condtion box will show where the user can input the garments condition.
when then toggle is on, i would like to set the value to static string value of "New"
code i have tried:
Form{
VStack {
Toggle(isOn: self.$itemNew) {
Text("is the item new?")
self.$Cond == "New"
}
if !itemNew {
TextField("Item Condition:", text: self.$Cond) {}
}
}
}
that code didnt seem to work, any pointers on where i should be looking to solve this one?
Thanks
You don't have to use a $, you can create a Binding manually like this:
Toggle(isOn: Binding(get: {self.cond == "New" ? true : false},
set: {
if newValue == true{
self.cond == "New"
}else{
self.cond == "Not really new"
}
})) {
Text("is the item new?")
}
And the self.cond is still must be a #State variable to trigger View update its visual presentation.
But your case isn't look very ordinary for that instrument. Is there other possible values of the string?
Related
Im going through SwiftUI Controls in detail and writing example code for each init.
Theres two sets of inits for Picker.
One where you fill your list from an array etc. and what you pick goes into selection: binding.
But whats the second set of inits for?
Theyre under section 'Creating a picker for a collection'
I can fill the array from a collection using both these inits ok
But how do you get the selected item using the 2nd set of inits?
The selection: param is no longer a binding to an ivar but a Keypath to fill the list.
My question is how do I get the selected item using the 2nd set of inits.
See inits here:
https://developer.apple.com/documentation/swiftui/picker
For Picker there are 6 inits.
3 under 'Creating a picker'
these are ok. I fill the list from an array for example and store the selected item in a single result specified by the selection: param. It binds the result to one ivar.
There are also 3 inits under 'Creating a picker for a collection'
I got this to display the items from a collection
e.g. I modified the example code in the apple docs. The code in the docs doesn't compile so apple may be missing stuff.
import SwiftUI
enum Thickness: String, CaseIterable, Identifiable {
case thin
case regular
case thick
var id: String { rawValue }
}
//to use in ist must be Hashable
struct Border: Identifiable {
var color: Color
var thickness: Thickness
//Identifiable > Hashable > id > String
//var id: String { return "\(color.hashValue)" }
let id = UUID()
}
extension Color{
func colorName() -> String{
if self == Color.black{
return "black"
}
else if self == Color.red{
return "red"
}
else{
return "UNHANDLED"
}
}
}
struct CLCPickers_selection_FromCollection_View: View {
#State private var selectedObjectBorders = [
Border(color: .black, thickness: .thin),
Border(color: .red, thickness: .thick)
]
var body: some View {
VStack{
//------------------------------------------------------------------
Picker(
"Border Thickness",
sources: $selectedObjectBorders,
selection: \.thickness
) {
//------------------------------------------------------------------
//I added
//id: \.self
//Picker: the selection "thin" is invalid and does not have an associated tag, this will give undefined results.
//------------------------------------------------------------------
ForEach(Thickness.allCases,
id: \.self)
{ thickness in
Text(thickness.rawValue)
}
}
//------------------------------------------------------------------
Divider()
//------------------------------------------------------------------
//This just lists the colors in the arrays of Border
//QUESTION - how do I find out the currenly selected one?
//normaly selection: in the picker would be bound to the picked item
//but for this init selection: is a keypath
//selection: \.thickness
//so I can fill the Picker list using the keypath into the Border array.
//BUT HOW DO I FIND OUT THE CURRENTLY SELECTED ITEM?
//theres no binding?
//is there a .selectedItem property some where?
List(selectedObjectBorders) {
Text("\($0.color.colorName())")
}
}
}
}
Question was answered but poster removed it for some reason.
answer: this picker init which set the thinkness ivar of EVERY Border object in the collection.
To see the change I should have displayed the result to show thickness.rawvalue to see the change in every Border object
List(selectedObjectBorders) { border in
HStack{
Text("\(border.color.colorName())")
Text("\(border.thickness.rawValue)") //<<- will change when you select an item. All will match.
}
}
I am trying to change the swipeAction from "Paid" to "UnPaid" based on payment status and somehow seems to be failing. Error: "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
Appreciate any help
struct ContentView: View {
var data: [Data] = [data1, data2, data3, data4]
#State var swipeLabel = true
var body: some View {
let grouped = groupByDate(data)
List {
ForEach(Array(grouped.keys).sorted(by: >), id: \.self) { date in
let studentsDateWise = grouped[date]!
Section(header:Text(date, style: .date)) {
ForEach(studentsDateWise, id:\.self) { item in
HStack {
Text(item.name)
padding()
Text(item.date, style: .time)
if(item.paymentStatus == false) {
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color.red)
} else {
Image(systemName: "banknote")
.foregroundColor(Color.green)
}
} // HStack ends here
.swipeActions() {
if(item.paymentStatus) {
Button("Paid"){}
} else {
Button("UnPaid"){}
}
}
} // ForEach ends here...
} // section ends here
} // ForEach ends here
} // List ends here
} // var ends here
}
The body func shouldn't do any grouping or sorting. You need to prepare your data first into properties and read from those in body, e.g. in an onAppear block. Also if your Data is a struct you can't use id: \.self you need to either specify a unique identifier property on the data id:\.myUniqueID or implement the Indentifiable protocol by either having an id property or an id getter that computes a unique identifier from other properties.
I would suggest separating all this code into small Views with a small body that only uses one or a two properties. Work from bottom up. Then eventually with one View works on an array of dates and another on an array of items that contains the small Views made earlier.
You should probably also learn that if and foreach in body are not like normal code, those are converted into special Views. Worth watching Apple's video Demystify SwiftUI to learn about structural identity.
As far as I read about conditional Views this code should work. But it doesn't.
struct Consts {
static let myCondition = false //no difference if true or false
}
struct ContentView: View {
#State var toggle: Bool = false
var body: some View {
List() {
Text("Short Text is in first line")
Text("Second Line Text is a little longer but not much")
if Consts.myCondition {
Text("This is a conditional text. As in: when the user hasn't purchased the option he / she don't need a hint how to use this feature.")
// } else {
// Text("else doesn't help either.")
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
Text("Here we have a longer Text. Dont know what to type any more.")
Text("More text which is longer than a few lines.")
Text("Not so long Text")
}
.navigationTitle("Hints & Settings")
}
}
It compiles without warnings or errors. It loads up and displays fine, on simulator and on device. But every time I scroll the List upwards from the end, as soon as this if condition { Text() } should become visible the app crashes with
Fatal error: file SwiftUI, line 0
2021-03-07 06:36:26.548126+0100 WTFif WatchKit Extension[23163:641812] Fatal error: file SwiftUI, line 0
This is not limited to watchOS. It reproduces the same way in iOS, just the Texts have to be longer so that the if condition { Text() } becomes invisible when scrolling.
I have worked around the error with an array, conditional ranges and two ForEach() blocks.
struct ContentView: View {
let myHints = ["Short Text is in first line",
"Second Line Text is a little longer but not much",
"This is a conditional text. As in: when the user hasn't purchased the option he / she don't need to hint how to use this feature.",
"Here we have a longer Text. Dont know what to type any more.",
"More text which is longer than a few lines.",
"Not so long Text"]
var myUpperRange: ClosedRange<Int> {
if Consts.myCondition {
return 0...1
} else {
return 0...2
}
}
var myLowerRange: ClosedRange<Int> {
return 3...5
}
#State var toggle: Bool = false
var body: some View {
List() {
ForEach (myUpperRange, id: \.self) { i in
Text(myHints[i])
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
ForEach (myLowerRange, id: \.self) { i in
Text(myHints[i])
}
}
.navigationTitle("Hints & Settings")
}
}
My question basically is: am I not getting it or did I find a bug in Xcode / SwiftUI? Should my code above work? What could I have done different to make it work with the simple list of Texts?
Background: I also have a TabView with an if condition { MyTab() } which works without crashing. Do I have to worry that this might crash in the same way? Should I work around this as well before shipping?
PS: I am using Xcode 12.4 (12D4e)
Apparently this has been fixed by iOS 15 Beta 4: on my test device I had the error prior to the Beta 4 update, using a test app compiled with Xcode 13 Beta 3. After the update to iOS 15 Beta 4 the error is gone.
So I think it’s reasonable to say that the update to iOS 15 Beta 4 did fix the error.
[RESOLVED]
I am using a codable struct which stores the object values retrieved from an API call so I have amended my TextField using Cenk Belgin's example, I've also removed extra bits I've added in so if anyone else is trying to do the same thing then they won't have pieces of code from my app that aren't required.
TextField("Product Code", text: $item.ProdCode)
.onReceive(item.ProdCode.publisher.collect()) {
self.item.ProdCode = String($0.prefix(5))
}
Here is one way, not sure if it was mentioned in the other examples you gave:
#State var text = ""
var body: some View {
TextField("text", text: $text)
.onReceive(text.publisher.collect()) {
self.text = String($0.prefix(5))
}
}
The text.publisher will publish each character as it is typed. Collect them into an array and then just take the prefix.
From iOS 14 you can add onChange modifier to the TextField and use it like so :
TextField("Some Placeholder", text: self.$someValue)
.onChange(of: self.someValue, perform: { value in
if value.count > 10 {
self.someValue = String(value.prefix(10))
}
})
Works fine for me.
You can also do it in the Textfield binding directly:
TextField("Text", text: Binding(get: {item.ProCode}, set: {item.ProCode = $0.prefix(5).trimmingCharacters(in: .whitespacesAndNewlines)}))
Ok -
I want a picker view to pick one operator: "=","<",">"
This operator will be sent as a binding:
#Binding var op:String
My Picker:
Picker(selection: binding, label: Text("Query Type")) {
ForEach(0..<self.operators.count) { index in
Text(self.operators[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
.padding()
Now My Binding with CallBack:
let binding = Binding<Int>(
get: {
return self.pickerSelection
},
set: {
//pickerSelection = $0
print("SETTTING: \($0)")
self.op = self.operators[self.pickerSelection]
self.queryCallback()
})
So, I can set the pickers perfectly. BUT, when I go back to edit my data, the picker never can choose the existing bound operator, say "<"
I put in the init an:
pickerSelection = operators.firstIndex(opValue)
However this will just start an infinite loop as pickerSelection is a #State variable
Anyone have a solution?
This is a method that works. It uses Combine to make an observable one can use to trigger the needed events. Also I see how useful Combine is with SwiftUI
https://stackoverflow.com/a/57519105/810300