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.
Related
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.
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.
It's a bit hard to word in the title but here's my situation.
I have a VStack and I need to animate a gap forming below a tapped item in the stack.
I've tried adding .padding(.bottom, isTapped ? 50 : 0) to the items for example, but it doesn't produce the desired result. For example if item X is the item i've tapped this is the behaviour that I get with this method
empty space --> X-1
X-1 --> X
X --> space created
X+1 --> X+1
empty space --> empty space
basically, as I tried to illustrate, it pushes the items above X upwards as well as pushing the items below it downward.
Is there any way to go about doing what I'm trying to do? A way to fix X and the items above it in place so that only the items below will shift?
edit:
Here's a more real example, when the middle item is tapped, it causes padding to be added below it, but unfortunately that doesn't just leave the first 2 where they are and push the third one down. But it moves the first 2 up and the third down. (Also the padding doesn't lay as shown in the example below, but within the Item View itself if that makes a difference)
VStack{
Item().padding(.bottom, isTapped ? 50: 0)
Item().padding(.bottom, isTapped ? 50: 0)
Item().padding(.bottom, isTapped ? 50: 0)
}
The VStack picks its size according to its contents. It sounds like the view containing the VStack has a lot of extra vertical space. Since the VStack is smaller than its superview, it is centered within its superview (leaving the empty space above and below.) When the VStack grows vertically (when the padding is added), it remains centered and will grow in both directions, up and down.
You could pin your VStack at the top of its superview by adding a Spacer below it and wrapping both in a new VStack. Then, when the padding is added to an item, the VStack will remain pinned at the top and any additional space will be added below.
VStack {
VStack {
// Items: X-1, X, X + 1
}
Spacer()
}
If this doesn't solve your problem, please show your code. The alignment and placement of views is dependent on your specific code.
I am trying using
var body: some View {
Text("This is large text. Is there a way that I can unwrap the large text as discussed").lineLimit(2)
}
FYI:
I knew
var body: some View {
Text("This is large text. Is there a way that I can unwrap the large text as discussed").lineLimit(nil)
}
It will wrap the text to say n number of lines.
Call .lineLimit(3) on the Text element. (Technically, it can be called on any View, in which case it will limit the lines of all Text elements in that view.)
From SwiftUI.View:
/// Sets the maximum number of lines that text can occupy in this view.
///
/// The line limit applies to all `Text` instances within this view. For
/// example, an `HStack` with multiple pieces of text longer than three
/// lines caps each piece of text to three lines rather than capping the
/// total number of lines across the `HStack`.
///
/// - Parameter number: The line limit. If `nil`, no line limit applies.
/// - Returns: A view that limits the number of lines that `Text` instances
/// display.
public func lineLimit(_ number: Int?) -> Self.Modified<_EnvironmentKeyWritingModifier<Int?>>
because .linelimit is not working as expected for the time being you can simply wrap the Text element inside a VStack if you have multible text like title and subtitme give that VStack multilineTextAlignment modifier and center that text with the parameter .center or you can add .multilineTextAlignment(.center) modifier directly to the Text element :
VStack{
Text("This is large text. Is there a way that I can unwrap the large text as discussed")
.font(.title3)
.foregroundColor(.white)
}.padding()
.multilineTextAlignment(.center)