SwiftUI truncate content of ForEach - swift

In the following code snippet, the red circles obviously don't fit onto the screen and therefore only the trailing part of the HStack is shown. Instead of that behavior, I want the leading text of the HStack to always be visible and truncate the trailing red circles that don't fit into the available space (replaced with ...).
How can I do this?
struct ContentView: View {
var body: some View {
HStack {
Text("This text should always be visible")
.layoutPriority(1)
Spacer()
ForEach(0..<20) { index in
Image(systemName: "circle.fill")
.font(.body)
.foregroundColor(.red)
}
}
}
}
So I want this:
instead of this:

Can get everything except the ellipses with:
struct ContentView: View {
var body: some View {
HStack {
Text("This text should always be visible")
.fixedSize()
ForEach(0..<20) { index in
Image(systemName: "circle.fill")
.font(.body)
.foregroundColor(.red)
}
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
}
In order to get the ellipses you'll probably have to calculate frame sizes or try to get your images into an attributed string which could get pretty complicated. You might also consider a scroll view or grid.

Related

How to keep an item exactly in the center of an HStack with other items in the HStack

I have two items in an HStack. I want one of them to be perfectly horizontally centered and the other to be on the left side (have an alignment of .leading). Here is my code so far:
HStack {
Text("1")
.frame(alignment: .leading)
Text("Hello")
.frame(alignment: .center)
}
I have figured out that the .overlay method works. However, I still want Text("Hello") to be aware of the position of Text("1") so that if Text("Hello") were a longer string, it would know not to cover up Text("1"), which is exactly what happens when the .overlay function is used. So I'm wondering if there are any other solutions.
You can add one more Text view inside HStack as hidden which have the same content as your left align text view along with Spacer. Something like this.
HStack {
Text("1")
Spacer()
Text("Good Morning, Hello World. This is long message")
.multilineTextAlignment(.center)
Spacer()
Text("1") // Set the same content as left aligned text view
.hidden()
}
.padding(.horizontal)
Preview
Not sure if it's the best way, but you can use a GeometryReader to get the screen width and skip the first half.
To place the text exactly in the center, you should also calculate the width of the text. you can use a simple extension to get the size of your text:
extension String {
public func size(withFont font: UIFont) -> CGSize {
(self as NSString).size(withAttributes: [.font: font])
}
}
Here is the example code:
GeometryReader { geo in
HStack {
Spacer()
.frame(width: (geo.size.width - "center".size(withFont: UIFont.systemFont(ofSize: 18)).width) / 2)
HStack {
Text("center")
Text("another")
}
}
}

applying fixedSize only if there is enough space

I want to build a VStack that is only as wide as the widest element is. If there is (horizontally) not enough space, e.g., the screen is too narrow, the content should shrink.
Here is a simple example:
VStack {
HStack {
Text("title1")
Spacer()
Text("title2")
Spacer()
Text("title3")
}
HStack {
Text("fo0o0o0o0o0o0o0o0oo0o0o0")
Spacer()
Text("fo0o0o0o0o")
Spacer()
Text("fo0")
}
HStack {
Text("fo0o0o0o0o")
Spacer()
Text("fo0o")
Spacer()
Text("fo0")
}
}.fixedSize()
How it should look like, if the VStack does not have to shrink.
How it should look like, if the VStack has to shrink.
If fixedSize is applied, the content obviously does not shrink and goes over the edges of the screen. If fixedSize is not applied, the spacers use all (horizontally) available space and the table is too wide. Hard coding the width is not an option because the content is dynamic and, therefore, unknown.
Basically: if there is enough space, apply fixedSize. If not, don't apply fixedSize and leave it as it is.
How can I archive that behavior with SwiftUI?
I hope I understood what you mean.
If you are using Xcode 14 and can deploy iOS 16 or later, you can simply replace VStack with Grid and HStack with GridRow; this will place the text in columns. Then, you can remove the Spacer() all over. If the text is too long, it will increase the lines inside its own column.
Like this:
Grid {
GridRow {
Text("title1")
Text("title2")
Text("title3")
}
GridRow {
Text("fo0o0o0o0o0o0o0o0")
Text("fo0o0o0o0o")
Text("fo0")
}
GridRow {
Text("fo0o0o0o0o")
Text("fo0o")
Text("fo0")
}
}
Result in the images (short vs. long text):
you could try this approach, using .fixedSize(horizontal: _, vertical: _) and some space calculation logic, together with a #State var.
struct ContentView: View {
#State var dofix = false // <-- here
var body: some View {
VStack {
// for testing, do your space calculation logic
Button("dofix") {
dofix.toggle() // <-- here
}
HStack {
Text("title1")
Spacer()
Text("title2")
Spacer()
Text("title3")
}
HStack {
Text("fo0o0o0o0o0o0o0o0oo0o0o0")
Spacer()
Text("fo0o0o0o0o")
Spacer()
Text("fo0")
}
HStack {
Text("fo0o0o0o0o")
Spacer()
Text("fo0o")
Spacer()
Text("fo0")
}
}
.fixedSize(horizontal: dofix, vertical: dofix) // <-- here
}
}

SwiftUI Image with high height layout

I'm trying to get a basic thing, but I can't make it work!
I would like to have a VStack that contains a text an image and a second text.
Everything should be visible on the screen. So the Image should resize (crop top and bottom) to give spaces for the two texts... but not.
Without .scaledToFill() it's working well, but the image is stretched
The problem (I think) is because the image has a high height!
(I tried with GeometryReader, fixedSize, layoutPriority, but nothing that I tried works)
The image is from Wikipedia: Image link
struct TestView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}.background(Color.blue)
}
}
Simulator screen
What is needed:
Image of what is needed
Thank you :)
Ok, I found a way
Color.clear
.background(Image("image")
.resizable()
.scaledToFill())
.clipped()
Embedding the image as background of a clear Color, the image is limited to the parent size!
Result: https://imgur.com/a/ceZRqSw
struct TestView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}
.background(Color.blue)
.padding(.top)
}
}
You could also use the maxHeight frame parameter to frame the view if you know how big you'd want it to be. You can also do this on the image itself.
struct TestView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}
.background(Color.blue)
.frame(maxHeight: 500) //500 can be any value of your choice
}
}
You can also use the AsyncImage with placeholder to load the image
struct ContentView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
AsyncImage(url: URL(string: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ee/Isabella_of_France_by_Froissart.png/1024px-Isabella_of_France_by_Froissart.png")){ image in
image.resizable()
}placeholder: {
ProgressView()
}
.scaledToFill()
.frame(width: 200, height: 400, alignment: .center)
Text("Text 2")
}
.background(Color.blue)
}
}
It seems like you are looking for a way to consider Safe Area. because at the second image all contents respect to safe area while the first image don't. However the code seems to do so by default unless we indicate to ignore safe area by using .ignoresSafeArea(). Look at the view where you called TextView, You might use ZStack with a blue background that affects its children.
If not you should used scaledToFit instead of scaledToFill because as I see both images, the second one is cropped.
Have you tried
struct TestView: View {
var body: some View {
VStack(spacing: 8) { // You can alternatively increase the spacing and remove the padding for the image
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}.background(Color.blue)
.padding() // Note padding here
}
}

How do I avoid app zoom when keyboard opens?

When I click my SwiftUI text field and the keyboard opens, the app zooms out (shown in video).
I have two questions about this behaviour:
Why does this happen?
How do I avoid this happening?
Here is my code:
struct BestillView: View { // This view is put inside a tab view with .ignoresSafeArea
#State var navn = ""
#State var varsling = true
var body: some View {
NavigationView {
ZStack {
Color("BackgroundColor")
.ignoresSafeArea()
VStack {
Image("Liquid") // This is my image overlayed on the background, i suspect this may be the only element that actually gets zoomed out
.resizable()
.aspectRatio(contentMode: .fit)
.ignoresSafeArea()
Spacer()
}
VStack {
ZStack(alignment: .leading) { // This is where the text field i'm having trouble with is
Color("UnselectedColor")
.frame(height: 50)
.cornerRadius(20.0)
if navn.isEmpty { // I have a separate text element as the placeholder text so i can give it a custom color
Text("Navn")
.foregroundColor(Color("AccentColor"))
.padding()
}
TextField("", text: $navn)
.padding()
}
.frame(width: 300)
Spacer()
.frame(height: 20.0)
// I removed the rest of my code, I don't think it should be necessary in this question - it's only a NavigationLink and a Toggle
}
}
}
}
}
You have .ignoresSafeArea() on your Image, but you actually need it on the VStack that contains the Image. The VStack is shrinking to fit the keyboard’s safe area, which squeezes the image too.
The view is actually not shrinking; the image is shrinking - because as the view moves up, it has less height to fit.
You can update your code as:
Image("Liquid")
.aspectRatio(contentMode: .fill)
and it will keep the size same - as the width will remain same.

How to make edgesIgnoringSafeArea in swiftUI work?

The following code makes a view where a rectangle is at the bottom of the view. I put .edgesIgnoringSafeArea(.bottom) so the rectangle goes all the way down but it doesn't work. Im simulating this on an Iphone 11 and always leaves a blank space below.
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
List {
Text("Hello, World!")
}
Spacer()
Rectangle()
.frame(height: 150)
.edgesIgnoringSafeArea(.bottom)
}
}
}
}
The rectangle is inside a VStack, and the VStack doesn't ignore the safe area. Even if the rectangle ignores the safe area, it can't extend beyond its parent to fill the whole screen.
You should put edgesIgnoringSafeArea after the VStack, and the rectangle will naturally fill the VStack, hence filling the whole screen.
var body: some View {
NavigationView {
VStack {
List {
Text("Hello, World!")
}
Spacer()
Rectangle()
.frame(height: 150)
}
.edgesIgnoringSafeArea(.bottom)
}
}