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.
Related
So let's say that I have the following code:
Text("(Open)")
.font(.caption2.weight(.bold))
I want to set .foregroundColor(.green) to just "Open" and not the parentheses inside view.
Is there a way to target just a specific piece inside a Text() view and apply modifiers to it?
You can use the addition operator + with Text like you would on regular strings like so :
Text("(").font(.caption2.weight(.bold)) +
Text("Open").font(.caption2.weight(.bold)).foregroundColor(.green) +
Text(")").font(.caption2.weight(.bold))
I want to fill a forEach loop on a specified field.
I tried to fit it in a space by trying VStack and HStach codes, but it didn't work. Plus, I tried .scaledToFill() and .fixedSize(), still didn't work.
In the SwiftUI Spacer() documentation:
https://developer.apple.com/documentation/swiftui/spacer
we can see:
"A flexible space that expands along the major axis of its containing stack layout, or on both axes if not contained in a stack."
What is the mechanism that is used by the Spacer to know if it is inside a VStack or HStack?
I also see one of the initialisers uses that:
init(minLength: CGFloat? = nil)
"The minimum length this spacer can be shrunk to, along the axis or axes of expansion"
How can I know that "axes of expansion"?
My app has like 5 predefined spacing constants, in an enumeration, and I would like to create a custom initialiser (or static method) that returns a Spacer() with one of my space constants that is used as a min and max at the same time, but in the current axis.
For example, I can do something like:
extension Spacer {
static func spacing(_ spacing: SpaceEnum) -> some View {
return Spacer().frame(width: spacing.value, height: spacing.value)
}
}
The problem with that, is that the spacer is a square, it doesn't know if we are inside a VStack or HStack.
How can I achieve that?
Ideally, I would like to have something like:
Spacer(space: .small)
that inside a VStack does this:
Spacer().frame(height: SpaceEnum.small.value)
Or
Spacer(space: .extraLarge)
that inside an HStack does this:
Spacer().frame(width: SpaceEnum.extraLarge.value)
Any ideas or suggestions?
Maybe, somehow I have to use GeometryReader or alignmentGuide, I don't know.
Using a square is not a good idea, it is causing issues when if the VStack width is smaller than the space. Or when the HStack height is smaller than the custom space constant.
Thanks a lot.
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!!
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.