SwiftUI - weird spacing in HStack with two Texts - ios

I am creating a "News" SwiftUI widget and I need to display rows, each containing time and news header.
I am using a VStack containing HStack's for each item but I am encountering an issue with extra HStack spacing between the two Text's. I need the left Text taking all the width needed for its content and the right Text taking all the width that is left. Seems to work as expected for the longer date (second line) for not for the first one.
In AutoLayout world I would need to play around with Content hugging / Compression resistance priority to achieve what I need but not sure what to do in SwiftUI.
Here's my code for creating the view:
private func createView(from headers: [NewsHeaderEntry.NewsHeader], with first: Int) -> some View {
GeometryReader { metrics in
VStack(alignment: .leading, spacing: 16) {
title
ForEach(headers[0...first - 1]) { header in
HStack(spacing: 4) {
Text(header.newsAt)
.font(.system(size: 12))
.fontWeight(.light)
.background(.green)
.frame(alignment: .leading)
Text(header.title)
.font(.system(size: 14))
.fontWeight(.semibold)
.redacted(reason: header.isPlaceholder ? .placeholder : .init())
.background(.yellow)
.lineLimit(2)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.background(.red)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.horizontal, 8)
}
Spacer()
}
.frame(width: metrics.size.width)
.background(.blue)
}
}
I have also tried setting maxWidth: .infinity for the left Text as well but it resulted in equal widths for both Texts which is not what I need.
Update: Without .padding(.horizontal, 8) the spacing works as expected but I need the horizontal spacing from the design perspective.
[2:

What you're looking for is the default behavior of the stack. Your frame modifiers are the issue:
private func createView(from headers: [NewsHeaderEntry.NewsHeader], with first: Int) -> some View {
GeometryReader { metrics in
VStack(alignment: .leading, spacing: 16) {
title
ForEach(headers[0...first - 1]) { header in
HStack(spacing: 4) {
Text(header.newsAt)
.font(.system(size: 12))
.fontWeight(.light)
.background(.green)
Text(header.title)
.font(.system(size: 14))
.fontWeight(.semibold)
.redacted(reason: header.isPlaceholder ? .placeholder : .init())
.background(.yellow)
.lineLimit(2)
}
.background(.red)
.padding(.horizontal, 8)
}
Spacer()
}
.frame(width: metrics.size.width)
.background(.blue)
}
}

Related

Need to create a chat bubble like Whatsapp with two labels on top of the message in SwiftUI

I'm trying to create a chat bubble like this:
Actual Bubble
Actual Bubble 2.0
This is what I have been able to achieve so far.
My attempt
My attempt
This is my code so far:
import SwiftUI
struct TestingView: View {
var body: some View {
ZStack {
/// header
VStack(alignment: .trailing) {
HStack {
HStack() {
Text("abcd")
}
HStack {
Text("~abcd")
}
}.padding([.trailing, .leading], 15)
.fixedSize(horizontal: false, vertical: true)
/// text
HStack {
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!")
}.padding([.leading, .trailing], 15)
/// timestamp
HStack(alignment: .center) {
Text("12:00 PM")
}.padding(.trailing,15)
}.background(Color.gray)
.padding(.leading, 15)
.frame(maxWidth: 250, alignment: .leading)
}
}
}
struct TestingView_Previews: PreviewProvider {
static var previews: some View {
TestingView()
}
}
The main goal is that I want the two labels on top to be distant relative to the size of the message content. I am not able to separate the two labels far apart i.e one should be on the leading edge of the bubble and the other one on the trailing edge.
Already tried spacer, it pushes them to the very edge, we need to apart them relative to the content size of the message as shown in attached images.
Here is a simplified code.
Regarding Spacer: To achieve your desired result you put both Text views inside of a HStack, and put a Spacer between them. So the Spacer pushes them apart to the leading and trailing edge.
Also I recommend to only use one padding on the surrounding stack.
VStack(alignment: .leading) {
// header
HStack {
Text("+123456")
.bold()
Spacer() // Spacer here!
Text("~abcd")
}
.foregroundStyle(.secondary)
// text
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!")
.padding(.vertical, 5)
// timestamp
Text("12:00 PM")
.frame(maxWidth: .infinity, alignment: .trailing)
}
.padding()
.background(Color.gray.opacity(0.5))
.cornerRadius(16)
.frame(maxWidth: 250, alignment: .leading)
}
We can put that header into overlay of main text, so it will be always aligned by size of related view, and then it is safe to add spacer, `cause it do not push label wider than main text.
Tested with Xcode 13.4 / iOS 15.5
var body: some View {
let padding: CGFloat = 15
ZStack {
/// header
VStack(alignment: .trailing) {
/// text
HStack {
//Text("Hello Everyone") // short test
Text("Hello Everyone, bdhjewbdwebdjewbfguywegfuwyefuyewvfyeuwfvwbcvuwe!") // long test
}
.padding(.top, padding * 2)
.overlay(
HStack { // << here !!
HStack() {
Text("abcd")
}
Spacer()
HStack {
Text("~abcd")
}
}
, alignment: .top)
.padding([.trailing, .leading], padding)
/// timestamp
HStack(alignment: .center) {
Text("12:00 PM")
}.padding(.trailing, padding)
}.background(Color.gray)
.padding(.leading, padding)
.frame(maxWidth: 250, alignment: .leading)
}
}
To separate two components with fairly space in the middle, use HStack{} with Spacer().
This is a sample approach for this case. Code is below the image:
VStack {
HStack {
Text("+92 301 8226")
.foregroundColor(.red)
Spacer()
Text("~Usman")
.foregroundColor(.gray)
}
.padding(.bottom, 5)
.padding(.horizontal, 5)
Text("Testing testingtesting testing testing testingtesting testing testing testing testing testing testing testing testing testing.")
.padding(.horizontal, 5)
HStack {
Spacer()
Text("2:57 AM")
.foregroundColor(.gray)
.font(.subheadline)
}
.padding(.trailing, 5)
}
.frame(width: 300, height: 160)
.background(.white)
.cornerRadius(15)

Text() is adding extra leading trailing padding SwiftUI

Hi Im creating this design, with swiftUI but I can't figure out why the Text() is adding more padding to the string value, I just want it to the leading trailing, like margins
Design:
Result:
Code:
struct ProductDetailSwiftUIView: View {
var body: some View {
ZStack {
ScrollView {
VStack {
Rectangle()
.frame(height: 213)
.foregroundColor(Color.blue)
Text("Fresas Congeladas La Huerta 500g")
.foregroundColor(.black)
.font(Font.bodySemibold(22))
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity)
.background(Color.red)
.padding(.leading, 16)
.padding(.trailing, 16)
}
}.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
print("Hello world🚀")
} label: {
Label("Back", image: "cartLine")
}.overlay(Badge(count: 1))
}
}
}
}
}
At first Text is aligned centered in frame, so
Text("Fresas Congeladas La Huerta 500g")
.foregroundColor(.black)
.font(Font.bodySemibold(22))
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading) // << fix 1 !!
.background(Color.red)
.padding(.leading, 16)
.padding(.trailing, 16)
gives:
At second SwiftUI Text does not allow orphan words. See next for details https://stackoverflow.com/a/71698509/12299030
Remove .frame(maxWidth: .infinity) from your Text() to fix this problem.

CommentViewRow comment text weird spacing behavior...?

I am trying to make an adaptive CommentViewRow for a social app where the whole comment text can be displayed within the row.
So far I am achieving this but I have 3 issues:
1 When the comment-text is short it is being centred within the row even when I specify ".alignment: .leading" in the VStack.
2 When the comment-text uses more than one line there is a mysterious padding between the user's profile picture & the comment-text?? see image below.
3 I am not sure if my .frame modifier is the best way to achieve what I am doing, it seems mickey-mouse, I was reading about .frame(idealWith, idealHeight, etc..) and not sure if that would help.
Any idea on how I can fix this so that each CommentViewRow displays like your average social-media comment view??
Thank you!
struct CommentViewRow: View {
var comment: Comment
var body: some View {
HStack {
KFImage("profilePicture")
// COMMENT
VStack(alignment: .leading, spacing: 5) {
Text("**\(comment.username)** \(comment.comment)")
.font(.caption)
.frame(width: 310)
.fixedSize(horizontal: true, vertical: false)
Text(comment.createdAt.timeAgoDisplay())
.bold()
.font(.caption)
}
Spacer()
}.padding([.leading, .trailing], 10)
}
}
1st option: If you really need that view to be 310 wide
You can change .frame(width: 310) to .frame(width: 310, alignment: .leading)
2nd option: Let the view adjust itself based on content, you just need to specify the alignment (.leading in this case)
struct CommentViewRow: View {
var comment: Comment
var body: some View {
HStack {
KFImage("profilePicture")
VStack(alignment: .leading, spacing: 5) {
Text("**\(comment.username)** \(comment.comment)")
.font(.caption)
Text(comment.createdAt.timeAgoDisplay())
.font(.caption.bold())
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding([.horizontal], 10)
}
}
yes, get rid of the frame:
struct ContentView: View {
var comment = "Thjfhg jhfgjhdfg jdfhgj dfhdfsjjdfgh djdshfg hjdfgjfdh ghjkf gdhjdfgh jkh fjg dfjkhgj dfglkhdfsg"
var body: some View {
HStack {
Image(systemName: "person.circle")
.font(.largeTitle)
VStack(alignment: .leading, spacing: 5) {
Text("\(comment)")
.font(.caption)
Text("3 minutes ago")
.bold()
.font(.caption)
}
Spacer()
}.padding([.leading, .trailing], 10)
}
}

SwiftUI - How to align elements in left, center, and right within HStack?

I'm creating an iOS minesweeper game and want to have a bar at the top with three pieces of information:
The high score (next to a trophy symbol) [LEFT]
The current time (next to a timer symbol) [CENTER]
The number of bombs left (next to a bomb symbol) [RIGHT]
However, the elements are not evenly aligned as you can see in the picture at the bottom- I'm guessing the spacers are automatically the same size. So, because the text on the left side of the screen is wider than that on the right, the middle text is offset to the right.
How can I align these items so the center time/image is perfectly in the middle with the other objects on the left/right?
My code looks like:
struct GameView: View {
#EnvironmentObject var game: Game
#State var labelHeight = CGFloat.zero
var body: some View {
VStack(alignment: .center, spacing: 10) {
Text("MINESWEEPER")
.font(.system(size: 25, weight: .bold, design: .monospaced))
.kerning(3)
.foregroundColor(.red)
HStack(alignment: .center, spacing: 5) {
Image(systemName: "rosette")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.highScoreString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image(systemName: "timer")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.timerString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image("top_bomb")
.resizable()
.aspectRatio(contentMode: .fit)
Text(String(game.bombsRemaining))
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
.overlay(GeometryReader(content: { geometry in
Color.clear
.onAppear(perform: {self.labelHeight = geometry.frame(in: .local).size.height})
}))
}
.frame(width: UIScreen.main.bounds.width * 0.9, height: labelHeight, alignment: .center)
BoardView()
}
}
}
Instead of aligning by spacers, move each part into separate view, say LeftHeader, CenterHeader, and RightHeader and use frame alignment, which gives exact separation by equal sizes, like
HStack {
LeftHeader()
.frame(maxWidth: .infinity, alignment: .leading)
CenterHeader()
.frame(maxWidth: .infinity, alignment: .center)
RightHeader()
.frame(maxWidth: .infinity, alignment: .trailing)
}
This can be achieved using a ZStack , HStack and Spacers.
ZStack{
HStack{
Text("Left")
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right")
}
}
Even if you remove left ( or right ) HStack, it won't affect the other components or the layout.
You can make it look bit nicer by adding paddings like this,
ZStack{
HStack{
Text("Left").padding([.leading])
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right").padding([.trailing])
}
}
I think you can create more one stack.
My solution like:
/**
* spacing is an example
* minWidth calculated by fontSize
*/
HStack(alignment: .center, spacing: 20){
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
}
Hope it useful for you.

SwiftUI number of lines for Text element causing random margin behavior

I'm having a bit of trouble with this particular view. For some reason, it seems like depending on whether the Text(title) element is one line or two lines, I'm getting very odd behavior for the total height of the view. All the elements are dynamically populated so I can't just have a set height. I've ruled out that this is issue not dependent on the image that's loaded, but always occurs when the Text(title) element is one line. As you can see, when the element is only one line, it causes the image to have a padding above it and the 'See more' button to have a padding below it. The first image shows the margin issue (single line for title) and the second shows how the margins aren't there for multi-line title. Any help much appreciated!
https://i.stack.imgur.com/MB6Dt.jpg
https://i.stack.imgur.com/RDKOj.jpg
VStack(alignment: .leading) {
Image(systemName: "camera")
.data(url: URL(string: picture)!)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width * 0.8)
.maxHeight(215)
.clipped()
Group {
HStack {
Text(title)
.bold()
.font(.system(size: 20, weight: .bold))
.padding(.vertical, 5)
Spacer()
}
HStack {
Text(subInformation)
.font(.system(size: 17, weight: .semibold))
.foregroundColor(Color.red)
.padding(.bottom, 5)
Spacer()
}
HStack {
Text(description)
.padding(.bottom, 2)
.foregroundColor(Color.gray)
Spacer()
}
}.padding(.horizontal)
DestinationAd()
.frame(width: UIScreen.main.bounds.width * 0.8, height: 50)
.padding(.bottom, 5)
Link(destination: URL(string: urlToOpen)!, label: {
HStack {
Spacer()
Text("See more")
.padding(.vertical, 5)
Spacer()
}.background(Color.blue)
.cornerRadius(10)
}).padding(.horizontal, 10)
.padding(.bottom, 10)
}.frame(width: UIScreen.main.bounds.width * 0.8)
.background(Color.white)
.cornerRadius(20)
I think that everything works as expected in your example. You are populating VStack with some child elements. VStack wraps it's content by default. In the first case (when the text only has one line) child views take less space that in the second example.
If you don't want to set frame for your VStack, maybe Spacer after the image and before the button could help.

Resources