How to create a segmented control and read values from SwiftUI? - swiftui

struct ContentView: View {
#State private var favoriteColor = 0
var body: some View {
VStack {
Picker(selection: $favoriteColor, label: Text("What is your favorite color?")) {
Text("Red").tag(0)
Text("Green").tag(1)
Text("Blue").tag(2)
}.pickerStyle(SegmentedPickerStyle())
Text("Value: \(favoriteColor)")
}
}
}

You are already nearly there!
First, you make a variable called favoriteColor. You intend to use this variable to get the value from the Picker, aren't you?
Then, you pass that variable to the Picker. Not just as a variable, but as a Binding, with the $ sign in front of it. This will make sure that your variable always stays in sync with the Picker. Briefly said, what Binding does, is that there's actually only one variable, which is shared by your View and the Picker. If you want to know more about this, I believe that there are hundreds of tutorials on this on the internet, so it would not really make sense if I would explain it here yet another time!
So what will happen, is that the Picker updates your variable when a selection is made. But what value will it give your variable for which option?
Well, that's the value you specified in those .tag(...)s. So when the user selects "Red", favoriteColor gets a value of 0, for "Green" it gets 1, for "Blue" it gets 2.
What about updating the UI when the selection changes? Well, SwiftUI manages this automatically -- this is one of the best things of SwiftUI!
But one more thing. When you use the value in a Text like you are doing, you will see something like "Value: 1" at runtime. It would be nice if we could display the name of the color, isn't it?
There's a lot of ways to do this, but one would be to replace Text("Value: \(favoriteColor)") with:
Text("Favorite Color: \(["Red", "Green, "Blue"][favoriteColor])")
What this does, is that it creates an array and takes the value out of it that corresponds with the index of the selected color. So when favoriteColor is 0, the first element of the array is taken , which is "Red".
Hope this is helpful for you!!

Related

SwiftUI TextField axis pushing View down unexpectedly, even when lineLimit set

I'm noticing some unexpected behavior when working with the SwiftUI TextField axis property in iOS 16. The code snippet below shows I have a TextField with a vertical axis set (I expect this should expand the text field to unlimited entry, scrolling down), and I've added a .lineLimit(5, reservesSpace: true) modifier to set the TextField's "box size" to 5 lines, but continue to scroll if I'm beyond 5 lines.
The image below shows the unexpected behavior. Scrolling works as expected until I hit line 10 on an iPhone 14 Pro. Then, even though the TextField box remains the same size, it pushes down any elements below it in the List. You can see the "Completed" Toggle moving down slightly. It's also curious that the "push" happens not when I paste in a line, but when I backspace to type a new number - for example the third image shows the Completed area after I pasted in a new line (the 13th line, but starts saying "Line 10", and when I backspace over the "0" in "10" to write "13", that's when the "push" is triggered (it can be seen in the 4th image). This push will continue until the Views below the TextField are eventually pushed off screen (you can see this happening in the last image). Since I'm in a List, I can, of course, scroll to access the last items, but I hadn't expected the TextField to push items lower, even more so that it starts with Line 10 in a 5 line-limited TextField. Am I misunderstanding these Views and modifiers, or is there a bug in the implementation? Thanks for insight!
List {
Group {
...
Text("Notes:")
.padding(.top)
TextField("Notes", text: $notes, axis: .vertical)
.lineLimit(5, reservesSpace: true)
.textFieldStyle(.roundedBorder)
Toggle("Completed:", isOn: $isCompleted)
.padding(.top)
}
.listRowSeparator(.hidden)
}
.listStyle(.plain)
I don't think that this approach will work, because .textLimit() modifier is used only for Text() view and not for TextField() as you are trying to do.

Copy a list in "onTap" method

I want to update the list items' values in the onTap method to true and false and vice versa to change some widgets according to the item's value if it is true or false,
so, is it okay to do something like this?
I mean, is it okay to copy a list inside onTap or it is not a good practice for the memory.
onTap: () {
List<bool> copyItems = [...items];
copyItems[ind] = !copyItems[ind];
ref.read(openFaqAnsRef.notifier).newList = copyItems;
},
I am not entirely sure what you mean, but:
Would it not be simpler to add a parameter to the copyItems that switches that value?
Besides, copying all of the items of a list can be devastating to the performance of your app. Imagine You have thousands/hundreds of items and someone presses that button 2/3/4 times in a row.

SwiftUI summing Text views dynamically

I want to add Text views together to apply different formatting to different parts of the text. Eg, I can do this:
HStack {
Text("first ").font(.title)
+ Text("second ").font(.largeTitle)
+ Text("third").font(.headline)
}
But my case is dynamic, I want to iterate over an array of strings and for each string, create a Text() with particular formatting. Something like:
HStack {
ForEach(words) { w in
Text(w).font(getFont(w))
}
But the above doesn't sum, and thus is subject to spacing/line-break issues. I really do want to be able to add the Text() objects together.
Any ideas?
Wow, based on the comment from #lorem, I found:
https://swiftui-lab.com/attributed-strings-with-swiftui/
And that did it, but it ain't pretty.

SwiftUI nested List doesn't appear

I'm trying to create a view with two nested List Views. That means the main view has rows and each row also has rows. Because of a reordering requirement (edit mode) and swipe to delete (inner rows only), I can't just use ForEach-loops with a scroll view.
The problem is the following: the inner rows don't show when I tap "+add set" although debugging shows they are actually added. the reason might be that view of the outer row doesn't adapt its height. I know this because if I only use ForEach and no List, the rows do appear. but then I can't use swipe to delete. this is the code for the inner rows (as List view) :
List {
ForEach(self.exercise.sets) { set in
SetRow(set: set, exercise: self.exercise)
}.onDelete { (offsets) in
self.exercise.sets.remove(atOffsets: offsets)
}
}
each outer row has VStack before this List (for the exercise name and the column titles) and a button below. see the screen shots
seems I found a workaround... The outer list looks like this in code:
List() {
Text("New workout").font(.title)
ForEach(self.workoutModel.exercises) {exercise in
ExerciseWorkoutView(exercise: exercise)
}
}
I simply added the following frame modifier to the ExerciseWorkoutView (which is a VStack with a List view in it:
.frame(height: self.minFrameHeight + self.setRowHeight * CGFloat(self.exercise.sets.count)).animation(.default)
minFrameHeight and setRowHeight are constants I set.
self.exercise is an observed object with a sets array as #Published instance variable. that's why the frame height adapts automatically...
if anyone knows a better solution, thanks for posting. I tried several variations of .fixedSize(...) already but it didn't work.

How do I remove the value label from a Google Visualizations gauge?

I'm using the Google Visualizations gauge on a page, but it's important that the value its displaying is not shown as a label right below the needle.
I've found two ways of doing this. Once you navigate through the DOM to the gauge widget, and navigating into the SVG pieces, you can either set the textContent element to be an empty string, or you can delete that whole text label entity, entirely.
function removeLabel(widget) {
var gauge_label_parent = widget.getElementsByTagName("g")[1];
var gauge_label = gauge_label_parent.getElementsByTagName("text")[0];
gauge_label.textContent = "";
// Another way of getting rid of the text: remove the element
// gauge_label_parent.removeChild(gauge_label);
}
The problem is: both of those techniques work only on the first time. If I re-draw the gauge with updated values, then the label re-appears, and trying to remove the label element or set textContent="" does nothing.
So, instead of just being able to update the value with:
data.setValue(0, 1, newValue);
chart.draw(data, options);
I have discovered that I have to change the options a little bit, like:
data.setValue(0, 1, newValue);
options.minorTicks = 3; // Change the options somehow
chart.draw(data, options); // Tell gauge to draw that
options.minorTicks = 2; // Change the options back to what they were
chart.draw(data, options); // Draw again
Here's a fiddle to see how it works or doesn't. Set the fixLabel to true or false depending upon whether you want the label problem to be present or not. Keep in mind that the label will (properly) be missing the first time. It re-appears when you update its value.
http://jsfiddle.net/jemenake/72dMt/2/
So, a few questions:
Any idea why it's doing this?
Is there a way to remove the label without having to go through this option-changing business?
Bonus question: Am I unclear about how minorTicks is supposed to work, or is it broken? The docs say that it's supposed to be the number of minor ticks between majors, but setting it to 2 gives me only 1 minor tick, 3 gives me 2, etc. And, if I set it to 0 or 1, I get "Problem parsing d=''" in the console.
Adding to Madthews answer:
To those of you who are trying to remove the label from the Google Gague Chart.
If you add the following code into your page (i have placed it in the header to keep things tidy) then you can set the font size of the Label to whatever you want.
<style>
* {text-rendering: optimizeLegibility; font-size:100%;}
svg g g {font-size:0px;}
</style>
I have been toying around with this for a while now and the top line of the css above is essential otherwise it will not work!
Ok I'll try to answer:
1) Label options are not managed by current API release
https://google-developers.appspot.com/chart/interactive/docs/gallery/gauge
Workaround: try with this CSS
svg g g {
font-size:0px;
}
http://jsfiddle.net/Madthew/72dMt/17/
Above fiddle explains you the meaning of the minorThicks. It's correct that if you set 2 you get 3 "spaces". Minor thicks represent the number of "BLANK" spaces between two major thicks. In the example you will se the perfect matching between your arrow and the thin line representing the minor thick.
Instead of messing with styles, you could just insert the value with a blank formatted field.
var data = google.visualization.arrayToDataTable([
['Label', 'Value'],
['Unitless Indicator', {v:counter, f:''} ]
]);
This also works with addRows.
jsfiddle here: https://jsfiddle.net/08x2m4vo/2/
Use this. This is also labelled by Google.
var data = google.visualization.arrayToDataTable([
['Label', 'Value'],
['', {v: <Your_own_value_of_the_gauge_meter>, f: ''}],
]);
In place of "<Your_own_value_of_the_gauge_meter>" use your value variable. That's it.
What about this?
['Label', 'Value'],
['', 80]