Align elements both leading and trailing in same container, without stretching it, in SwiftUI - swift

I'm trying to build a simple chat with a design like the most usual messengers.
A message consists of a user name (if not your own message), the message itself and a timestamp.
I want the width of a message to be determined by the widest child element (most likely the message, but in rare cases maybe the user name or timestamp).
I also want the user name and message to be aligned leading/left and the timestamp to be aligned trailing/right, just like group chats in WhatsApp, Signal or the likes..
I tried weird amounts of H/V/Z-Stacks, which lead to weird happenings. I also tried both adding Spacers and maxLength = .infinity, which lead to the message stretching to the far right with lots of empty space.
My current code has all elements aligned centered:
And this is what I want:
This code is only for the comment, which itself is embedded into a ScrollView/VStack for each one.
HStack() {
if comment.author?.id == currentUserId {
Spacer()
.frame(width: 50)
Spacer()
}
VStack(spacing: 3) {
if comment.author?.id != currentUserId {
Text("Test User")
.font(.body)
.frame(alignment: .leading)
.border(.red)
}
Text("Test message content")
.frame(alignment: .leading)
.border(.red)
Text(date)
.foregroundColor(.gray)
.frame(alignment: .trailing)
.border(.red)
}
.border(.green)
.padding(10)
.background(Color.gray)
.cornerRadius(10)
if comment.author?.id != currentUserId {
Spacer()
Spacer()
.frame(width: 50)
}
}
Also I thought the double Spacers on the left or right side looked kinda weird. I tried using .frame(minWidth: 50), which ended up way too wide and pushes even a small message like the one on the image into a second line.
I'm happy for any ideas for these problems, but I'm trying to go for a simple solution and no complex calculations with alignmentGuides or GeometryReader or similar.

.frame allows you to specify the alignment.
So you could just make the VStack leading and then use .frame on the last element with alignment: .trailing. Keep in mind that the View will then grow to its available space. If you want to prevent that, just add .fixedSize() (or .fixedSize(horizontal: true, vertical: false)) to the parent view.
GroupBox {
VStack(alignment: .leading) {
Text("Test User")
Text("Test Message Content")
Text("9.5.2022, 19:31")
.font(.caption)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.fixedSize()
}
or if you want to keep the VStack on the default alignment
GroupBox {
VStack {
Text("Test User")
.frame(maxWidth: .infinity, alignment: .leading)
Text("Test Message Content")
Text("9.5.2022, 19:31")
.font(.caption)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity, alignment: .trailing)
}
.fixedSize()
}
but it also works using a HStack + Spacer
GroupBox {
VStack(alignment: .leading) {
Text("Test User")
Text("Test Message Content")
HStack {
Spacer()
Text("9.5.2022, 19:31")
.font(.caption)
.foregroundColor(.secondary)
}
}
.fixedSize()
}

Related

How to align the right side of an object to the exact middle of a view SwiftUI

I have an HStack with two Texts.
HStack {
Text("Hello")
Text("World")
}
I want Text("Hello") to be on the left side of the page and Text("World") to be on the right side of the page and for both texts to be equidistant from the exact horizontal center of the page. I added a visual for clarification:Visual
I've tried a bunch of different stuff but I haven't found any way to accomplish this task precisely.
You can use the alignment argument of the frame modifier.
Spacing argument for the HStack is for the spacing in between the texts.
HStack(spacing: 0) {
Text("Hello, World!")
.frame(maxWidth: .infinity, alignment: .trailing)
Text("Hello, World!")
.frame(maxWidth: .infinity, alignment: .leading)
}
I really didn't understand your need but according to your drawing I think you need something like below,
HStack{
Spacer()
Text("Hello")
.background(Color.orange)
Spacer().frame(width: 20)
Text("World")
.background(Color.orange)
Spacer()
}

SwiftUI TabView Embedded In Another View Problem

For a few time now, I have been playing with SwiftUI. I'd like to make it clear that I do have a Java and JS background, but no any Swift experience.
So the idea is:
I'd like to have a view (referred later on as main view) where the screen is divided into two parts (views). The top one will be used just like a header - picture with maybe a footer (the frosted footer in the screenshots).
Then the second part of the screen I want it to be another View that is actually a TabView. See attached screenshots of unscrolled view of the main view and
scrolled view of the main view
Currently, what I have now is:
struct CarDetailsView: View {
let car: Car
var body: some View {
ScrollView {
HeadingView(car: car)
CarDescriptionView(car: car)
.offset(y: -36)
}
.ignoresSafeArea()
}
}
Which works fine (as in the screenshots), where HeadingView is the picture with the footer, and then CarDescriptionView is the "second" view of the screen with the description. This is also the part that I'd like to make it TabView.
I thought, I can just wrap the CarDescription View inside a TabView, just like that:
struct CarDetailsView: View {
let car: Car
var body: some View {
ScrollView {
HeadingView(car: car)
TabView {
CarDescriptionView(car: car)
.offset(y: -36)
.tabItem {
Label("Description", systemImage: "car")
}
}
}
.ignoresSafeArea()
}
}
However, then I am getting this thing happening here
Note, the structure of my other View are:
struct HeadingView: View {
var car: Car
var body: some View {
VStack(spacing: 0) {
Text("25 300EUR")
.foregroundColor(.white)
.font(.title)
.bold()
.padding(.horizontal, 12)
.padding(.vertical, 4)
.background(Color.init(hexadecimal: "66B054"))
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
.padding(16)
HStack {
Image(systemName: "car")//todo: make logo go here
.padding(.leading, 16)
.padding(.trailing, 16)
VStack(alignment: .leading, spacing: 4) {
Text("\(car.make) \(car.model)")
.font(.title2)
.bold()
HStack {
Text("\(String(car.odometer / 1000))k km |")
.font(.caption)
Text("\(String(car.year)) |")
.font(.caption)
Text("\(String(car.engine.horsepower))bhp |")
.font(.caption)
Text("20km away")
.font(.caption)
}
}
Spacer()
}
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.top, 12)
.padding(.bottom, 38)
.background(
VisualEffectBlurView(blurStyle: .systemThinMaterialDark)
)
}
.frame(maxWidth: .infinity, minHeight: 500, maxHeight: 500)
.background(
Image(car.images[0])
.resizable()
.aspectRatio(contentMode: .fill)
)
.mask(
RoundedRectangle(cornerRadius: 0, style: .continuous)
)
}
}
and
struct CarDescriptionView: View {
let car: Car
var body: some View {
ScrollView {
Text("Description")
.font(.title)
.bold()
.padding(.top, 32)
.padding(.leading, 16)
.padding(.bottom, 16)
.frame(maxWidth: .infinity, alignment: .leading)
Text(car.description)
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
Text(car.description + "\n")
.font(.body)
.padding(.horizontal)
}
.background(.white)
.mask(
RoundedRectangle(cornerRadius: 30, style: .continuous)
)
}
}
Please note that I have also tried to place the TabView inside the CarDetailsView, but again with the same result.
Changing the ScrollView to VStack in the main component results in somewhat working view. However, then I do not get any scrolling of the header View (the car image), but only I am able to scroll within the tabbed view.
As I said at the beginning, I am really new to this, so I am not even sure if the concept is correct or not.

My TextField in Swift UI is Unresponsive to taps and clicks, text cannot be input, is there an issue with the code?

Hi I'm an intermediate level engineer with swift and swift UI.
Currently, I am having problems with the TextField(), it is unresponsive to taps and clicks, and hence the keyboard does not show up,
However, it does show up when it is one of two main elements within a VStack, but anymore than that and it does not work,
Is there an issue with the code? and can anyone find a fix or a work around for this?
Thanks, Miles
{
#State var textField = ""
var body: some View {
returnJoinModalContent()
}
func returnJoinModalContent() -> some View{
let accentColor = Color.blue
return VStack(alignment: .center){
HStack{
Text("Join Game")
.bold()
.font(.largeTitle)
.padding()
Spacer()
Button(action:{
//self.hideModal()
}){
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 50,height: 50)
.foregroundColor(accentColor)
.padding()
}
}
.padding(.bottom,170)
VStack(alignment: .leading){
TextField("Enter Game Code",text: self.$textField)
.font(.largeTitle)
Rectangle()
.frame(height: 6)
.foregroundColor(accentColor)
}
.padding()
HStack{
Button(action:{
//self.joinGame()
}){
ZStack{
RoundedRectangle(cornerRadius: 90)
.frame(width: 200,height: 70)
.foregroundColor(accentColor)
Text("Ready!")
.bold()
.font(.title)
.foregroundColor(Color.white)
}
}
}
.padding(.vertical,20)
HStack{
Image(systemName: "info.circle")
.resizable()
.frame(width:60,height:60)
.foregroundColor(accentColor)
Text("Ask your host for the game code, it can be found at the bottom of the player lobby")
.padding()
Spacer()
}
.padding(.top,60)
.padding(.horizontal,20)
Spacer()
}
.padding()
}
}
This seems to be one of those things in swiftUI that is difficult to track down. I found the problem is your ready button. More specifically the label being a ZStack with a colored rounded rectangle as the background. If you replace your button's label with the following your text field should be selectable.
Text("Ready!")
.bold()
.font(.title)
.foregroundColor(Color.white)
.frame(width: 200,height: 70)
.background(accentColor)
.mask(RoundedRectangle(cornerRadius: 90))
I've found that overlay on the TextField or the container of TextField might cause it to be unresponsive
you should remove it and replace it with ZStack

how to create a fit HStack with equal space between it's object

I wanna create an HStack with levitating space between objects, but equal between each one and also it must fit on horizontal, Some of like that
I used Spacer() between objects, like that
HStack {
Text("Object1")
Spacer()
Text("Object2")
Spacer()
Text("ASASASA")
Spacer()
Text("asa")
}
.padding()
But there is just a question, Is there any way better than for making my code cleaner??
Maybe something like this? You might want to set the alignment: to what you need.
HStack(spacing: 20) {
Group {
Text("Object1")
Text("Object2")
Text("ASASASA")
Text("asa")
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
}

VStack inside ForEach loop not respecting alignment parameter in WatchOS

I have a VStack with alignment .leading inside of a ForEach loop inside a ScrollView and the alignment parameter is not being respected. If I have a single VStack outside of the ForEach loop the alignment parameter is being used and the view is displayed correctly.
ScrollView{
// THIS DISPLAYS CORRECTLY
VStack(alignment: .leading){
Text("namename")
.font(.headline)
.lineLimit(1)
Text("namename name")
.font(.subheadline)
.lineLimit(1)
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.gray.opacity(0.20))
.cornerRadius(5)
.padding(.bottom)
ForEach(self.list_of_names, id: \.self) { item in
// THIS DOES NOT DISPLAY CORRECTLY
VStack(alignment: .leading){
Text(item)
.font(.headline)
.lineLimit(1)
Text(item + " name")
.font(.subheadline)
.lineLimit(1)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.gray.opacity(0.20))
.cornerRadius(5)
.padding(.bottom)
}
}
.padding()
The first row has the correct alignment and it's outside the ForEach loop, meanwhile the other rows are inside the loop. The blue lines represent me highlighting each VStack inside the ForEach loop
The VStacks created in the ForEach loop do seem to be conforming to the alignment parameter. Aligning the text to the leading edge only applies to the shorter items in the VStack.
In the top VStack, "namename" at the top is only that far to the left because "namename name" below is that much wider from the center of the ScrollView. So the top VStack is not the proper way to fully achieve the alignment that you want to the ScrollView.
What you're looking for:
Leave the alignment parameter for VStack.
Add the alignment parameter to the frame of the VStack.
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
I'm a bit late, but another solution I usually use is wrapping the values in an HStack and adding a Spacer. Something like this will achieve the desired effect:
var body: some View {
ScrollView {
ForEach(self.list_of_names, id: \.self) { item in
HStack {
VStack(alignment: .leading) {
Text(item)
.font(.headline)
.lineLimit(1)
Text(item + " name")
.font(.subheadline)
.lineLimit(1)
}
Spacer()
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.gray.opacity(0.20))
.cornerRadius(5)
.padding(.bottom)
}
}
.padding()
}