I'm attempting to make a widget extension with two text labels and an image. The two labels need to be in the top left and the image in the bottom right. I've been unable to get this working correctly.
Is there a proper way of doing this without having to use a Spacer() or an overlay image which isn't what I need. Very new to SwiftUI. Any help or pointers would be greatly appreciated.
VStack (alignment: .leading){
Text("Title")
.font(.headline)
.fontWeight(.regular)
.lineLimit(1)
Text("subtitle")
.font(.title3)
.fontWeight(.bold)
.lineLimit(1)
.allowsTightening(true)
HStack {
Spacer(minLength: 15)
Image("fish")
.resizable()
.frame(width: 120, height: 80)
}
}
.padding(.top)
.padding(.leading)
There are a bunch of ways to build a layout like that. What you have is almost working, you just need a Spacer between the text and image to shove the image down:
VStack (alignment: .leading) {
Text("Title")
.font(.headline)
.fontWeight(.regular)
.lineLimit(1)
Text("subtitle")
.font(.title3)
.fontWeight(.bold)
.lineLimit(1)
.allowsTightening(true)
Spacer(minLength: 0) // <-- add spacer here
HStack {
Spacer(minLength: 15)
Image("fish")
.resizable()
.frame(width: 120, height: 80)
}
}
.padding(.top)
.padding(.leading)
The only other change I would suggest is avoiding the hard-coded image size, since widgets are different sizes on different phones. You could have the image expand to fit the available width while preserving your image’s 3:2 aspect ratio like this:
VStack(alignment: .leading) {
Text("Title")
.font(.headline)
.fontWeight(.regular)
Text("subtitle")
.font(.title3)
.fontWeight(.bold)
.allowsTightening(true)
Spacer(minLength: 0)
Image("fish")
.resizable()
.aspectRatio(3/2, contentMode: .fit)
}
.lineLimit(1)
.padding(.leading)
.padding(.top)
Or if you want the fixed-size image and would prefer to have the text overlap it on smaller screens, you could use a ZStack like this:
ZStack(alignment: .bottomTrailing) {
Image("fish")
.resizable()
.frame(width: 120, height: 80)
VStack (alignment: .leading) {
Text("Title")
.font(.headline)
.fontWeight(.regular)
Text("subtitle")
.font(.title3)
.fontWeight(.bold)
.allowsTightening(true)
}
.lineLimit(1)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
As I said, there are a bunch of ways to solve this!
I think I've managed to get this working, well as close as I could get it. Seems to be a bit better than before. Mildly satisfied but working now.
ZStack {
VStack (alignment: .leading){
Text("Title")
.font(.body)
.fontWeight(.bold)
.lineLimit(1)
.allowsTightening(true)
Text("subtitle")
.font(.title)
.fontWeight(.regular)
HStack(alignment: .bottom){
Spacer()
Image("fish")
.resizable()
.frame(width: 120, height: 80)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
.offset(x: 5, y: -10)
// padding for images with frame bigger than actual image
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .leading)
}
.padding(12)
}
.background(Color(UIColor.secondarySystemBackground))
Related
I am trying to prevent the background of my HStack from going into my safearea.
There is a ZStack that is setting the background color. I am using green just to be more defined for now.
struct AccountView: View {
var body: some View {
ZStack{
Color(.green)
.ignoresSafeArea()
VStack(spacing: 32){
HStack {
Image(systemName: "person")
.resizable()
.scaledToFill()
.frame(width: 64, height: 64)
.clipShape(Circle())
.padding(.leading)
VStack(alignment: .leading, spacing: 4) {
Text("First Last")
.font(.system(size: 18))
Text("Available")
.foregroundColor(.gray)
.font(.system(size: 14))
}
Spacer()
}
.frame(height: 80)
.background(Color("ItemBG"))
Spacer()
}
}
}
}
We need to use shape as background instead of pure-colour, then it is not spread outside.
Here is simplified demo. Tested with Xcode 13.2 / iOS 15.2
var body: some View {
VStack {
HStack {
Text("Hello")
}
.frame(maxWidth: .infinity, maxHeight: 80)
.background(Rectangle().fill(.yellow)) // << here !!
Spacer()
}
.background(Color.green.ignoresSafeArea())
}
I was able to figure it out...
I had to give by HStack a padding of 1 on the top. Apparently with IOS 15 if it touches the safe area it bleeds.
HStack {
Image(systemName: "person")
.resizable()
.scaledToFill()
.frame(width: 64, height: 64)
.clipShape(Circle())
.padding(.leading)
VStack(alignment: .leading, spacing: 4) {
Text("First Last")
.font(.system(size: 18))
Text("Available")
.foregroundColor(.gray)
.font(.system(size: 14))
}
Spacer()
}
.frame(height: 80)
.background(Color("ItemBG"))
.padding(.top, 1)
So this is a weird one, and it looks like a bug. When placed inside a button, title that cannot fit on one line mis-aligns. It should be aligned to the left, but shows up centered.
Everything latest, XCode 13.2.1
MRE is below, any tips much appreciated!
HStack {
HStack{
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
Text("Some kind of title") // <- aligned properly
.fontWeight(.bold)
.minimumScaleFactor(0.2)
} //: HStack
.padding(.horizontal, 12)
.frame(maxWidth: .infinity, alignment: .leading)
.frame(height: 35)
.font(.headline)
.foregroundColor(Color.green)
.background(Color.green.opacity(0.2))
.cornerRadius(6)
Button(action: {
// some action
}){
HStack{
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
Text("Some kind of title") // <- not aligned properly
.fontWeight(.bold)
.minimumScaleFactor(0.2)
} //: HStack
.padding(.horizontal, 12)
.frame(maxWidth: .infinity, alignment: .leading)
.frame(height: 35)
.font(.headline)
.foregroundColor(Color.green)
.background(Color.green.opacity(0.2))
.cornerRadius(6)
} //: Button
} //: HStack
You can fix it by adding the multilineTextAlignment modifier to the Text view:
Button(action: {
// some action
}){
HStack{
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
Text("Some kind of title")
.fontWeight(.bold)
.minimumScaleFactor(0.2)
.multilineTextAlignment(.leading) // <- will align to the left
} //: HStack
.padding(.horizontal, 12)
.frame(maxWidth: .infinity, alignment: .leading)
.frame(height: 35)
.font(.headline)
.foregroundColor(Color.green)
.background(Color.green.opacity(0.2))
.cornerRadius(6)
} //: Button
As to why it happens, I have a theory. By default Text on buttons is centered, while plain Text by default is left-aligned. I didn't see it explicitly mentioned in the documentation, but a simple experiment shows this behavior:
how are you ?
I need to fit all the content of first zstack (I use two zstack to add a transparent layer over first zstack background) to window size and if the user scroll down, shows additional stacks added below.
I add Spacer() to push the last HStack to bottom and before stacks to the top (in middle must be a dynamic blank space) of window without result.
Can help me please ?
Thanks
ScrollView(.vertical) {
ZStack {
bgColors[weatherDescription]
ZStack {
LinearGradient(gradient: Gradient(colors: [Color(hex: 0x212121, alpha: 0.5), Color(hex: 0x212121, alpha: 0.5)]), startPoint: .top, endPoint: .bottom)
VStack(alignment: .leading) {
Text("Hello!")
.foregroundColor(Color.white)
.fontWeight(.heavy)
.font(.largeTitle)
.padding(.top, 20)
.padding(.bottom, 1)
Text("Hello!")
.foregroundColor(Color.white)
.font(.title)
.padding(.bottom, 1)
Text("Hello!")
.foregroundColor(Color.white)
.font(.title3)
.padding(.bottom, 20)
HStack(alignment: .center, content: {
Text("TEXT1")
.foregroundColor(Color.white)
.font(.title3).fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .leading)
Text("TEXT2")
.foregroundColor(Color.white)
.font(.title3).fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .trailing)
}).frame(maxWidth: .infinity)
Spacer()
HStack(alignment: .center, spacing: 30, content: {
Text("Text1")
.foregroundColor(Color.white)
.font(.system(size: 90))
Text("Text2")
.foregroundColor(Color.white)
.font(.caption)
.animation(.easeIn)
}).frame(width: .infinity, alignment: .bottom)
}.padding() // vstack
}.edgesIgnoringSafeArea(.top) // zstack
}.edgesIgnoringSafeArea(.top) // zstack
// ADITIONALS STACKS VISIBLES ONLY WHEN SCROLL DOWN
}
You just used so many codes, I edited them to less code, here for example you just used so many foregroundColor which you can see in my code I used just one time on parent View.
Also we can not use ScrollView like you used it! ScrollView needs Content to Scroll, you have less Content.
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.red, Color.purple]), startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
VStack(alignment: .leading, spacing: 30) {
Text("Hello!")
.fontWeight(.heavy)
.font(.largeTitle)
Text("Hello!")
.font(.title)
Text("Hello!")
.font(.title3)
HStack {
Text("TEXT1")
.font(.title3)
.fontWeight(.bold)
Spacer()
Text("TEXT2")
.font(.title3)
.fontWeight(.bold)
}
Spacer()
HStack {
Text("Text1")
.font(.system(size: 90))
Spacer()
Text("Text2")
.font(.caption)
}
}
.padding()
}
.foregroundColor(Color.white)
.animation(.easeIn)
}
}
I have a vertical ScrollView with a ForEach loop that has HStack's inside with Text, Image, and Two Text's. I'd like to keep each row's items in alignment with each other. I've attempted to wrap in a VStack with alignment .leading but did nothing. The issue I'm having is the text doesn't line up. Still very new to SwiftUI so any help would be appreciated.
ScrollView(.vertical) {
ForEach(self.daily.forecast, id: \.date) { forecast in
HStack (spacing : 10){
Text(forecast.date ?? "")
.fontWeight(.bold)
Spacer()
RequestImage(Url("IMAGE_URL"))
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30)
.clipped()
Spacer()
Text("\(forecast.maxTemp)")
.fontWeight(.bold)
Text("\(forecast.minTemp)")
.fontWeight(.bold)
}
}
}
.padding(.all, 15)
.onAppear() {
authorize {_ in
loadForecast()
}
}
UPDATED:
I was able to get a bit closer but things still aren't aligned to where I'd like them to be.
ScrollView(.vertical) {
ForEach(self.daily.forecast, id: \.date) { forecast in
HStack (spacing : 10){
Text(date)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 45)
Spacer()
RequestImage(Url("IMAGE_URL"))
.aspectRatio(contentMode: .fit)
.frame(width: 35, height: 35)
.frame(minWidth: 50)
.clipped()
Spacer()
Text(maxTemp)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 45)
Text(minTemp)
.font(.title3)
.fontWeight(.bold)
.foregroundColor(Color.secondary)
.frame(minWidth: 45)
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
}
.padding(.all, 15)
.onAppear() {
authorize {_ in
loadForecast()
}
}
Here is a demo of possible solution. Prepared with Xcode 12.4 / iOS 14.4
struct DemoWeatherLayout: View {
private var dayOfTheWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sut"]
var body: some View {
ScrollView {
VStack {
ForEach(dayOfTheWeek, id: \.self) {
WeatherRowView(day: $0,
icon: ["sun.max", "cloud.sun", "cloud", "cloud.snow"].randomElement()!,
low: Array(-5..<0).randomElement()!,
high: Array(1..<15).randomElement()!)
}
}.padding()
}
}
}
struct WeatherRowView: View {
var day: String
var icon: String
var low: Int
var high: Int
var body: some View {
HStack {
Text(day)
.frame(maxWidth: .infinity, alignment: .leading)
Image(systemName: icon)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
Text("+XXº").foregroundColor(.clear)
.overlay(Text(String(format: "%+d", low)), alignment: .leading)
Text("+XXº").foregroundColor(.clear)
.overlay(Text(String(format: "%+d", high)), alignment: .leading)
}
}
}
}
Three suggestions:
add alignment parameter to HStack:
HStack(alignment: .lastTextBaseline, spacing: 10) { ... //others options: .top, .center, .firstTextBaseline...
Add minLength parameter to Spacer:
Spacer(minLength: 0) but with spacing: 10 in HStack it is a bit redundant. You can remove spacing: 10 for Spacer(minLength: 10)
Add a Spacer(minLength: 0) at the end after Text("\(forecast.minTemp)").fontWeight(.bold)
I'm going to accept #Asperi answer as correct. The UI has indeed changed since to better fit said labels etc. Appreciate everyone's else. The code is very much ugly but works. Not sure if it's 100% correct.
HStack (spacing: 10){
Text(date)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
Spacer()
RequestImage(Url("IMAGE_URL_HERE"))
.aspectRatio(contentMode: .fit)
.frame(width: 35, height: 35)
.frame(minWidth: 55)
.clipped()
Spacer()
Text(maxTemp)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 50)
.background(Color.orange)
.cornerRadius(5)
Text(minTemp)
.font(.title3)
.fontWeight(.bold)
.frame(minWidth: 50)
.background(Color.blue)
.cornerRadius(5)
}
How can I restrict the view to resize only downwards?
I am trying to add more buttons & have the view resize to accommodate the buttons without overlapping the red rectangle above.
This is the code I am currently using:
struct ContentView: View {
var body: some View {
VStack{
RoundedRectangle(cornerRadius: 20)
.frame(width: .infinity, height: 55)
.foregroundColor(.red)
HStack{
Spacer()
ZStack{
RoundedRectangle(cornerRadius: 20)
.foregroundColor(.green)
VStack{
Button(action: {}) {
Text("Test Button")
.foregroundColor(.white)
.font(.headline)
.frame(width: .infinity)
Spacer()
}.padding()
Button(action: {}) {
Text("Test Button")
.foregroundColor(.white)
.font(.headline)
.frame(width: .infinity)
Spacer()
}.padding()
Button(action: {}) {
Text("Test Button")
.foregroundColor(.white)
.font(.headline)
.frame(width: .infinity)
Spacer()
}.padding()
}
}.frame(width: 250, height: 100)
}
Spacer()
}
}
}
Change your .frame(width: 250, height: 100) to .frame(width: 250, height: 100, alignment: .top) Your original example defaults to .center, which is why it expands into the red rectangle.