SwiftUI: InsetGroupedListStyle List and full-width header - ios

How to make a full-width header using List with InsetGroupedListStyle on iOS?
One way is to use negative padding (as an example in my code), but this doesn't seem like the best solution using fixed value as it may change in the future.
Is there a better way?
Example code:
import SwiftUI
struct ContentView: View {
var body: some View {
List {
Section (header:
VStack {
Text("Header")
.foregroundColor(.white)
}
.frame( // This doesn't remove list's paddings
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
.background(Color.red)
// .padding(.horizontal, -16) // This works, but fixed value is not the best solution.
.textCase(nil)
.font(.body)
) {
Text("Hello, world!")
.padding()
}
}
.listStyle(InsetGroupedListStyle())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

You could set the width of the header to the width of the screen, and the padding (x2) you need could be reduced from the width. To can achieve this using a GeometryReader. For example,
// Geometry reader to get the width
GeometryReader { reader in
List {
Section (header:
VStack {
Text("Header")
.foregroundColor(.white)
}
// Setting the frame of the Header to the size of the screen
// Reducing 20 from the width, giving a padding of 10 on each side
.frame(width: reader.size.width - 20, height: 20, alignment: .leading)
.background(Color.red)
.textCase(nil)
.font(.body)
) {
Text("Hello, world!")
.padding()
}
}
.listStyle(InsetGroupedListStyle())
}
You can see the result below

you could try this:
...
{
Text("Hello, world!").padding()
}.headerProminence(.increased) // <--- here

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)

SwiftUI TextEditor Divider doesn't change Y position based on text-line count?

I am trying to make a SwiftUI TextEditor with a Divider that adapts its position to stay under the bottom-most line of text inside of a edit-bio section of the app.
Note: I have a frame on my TextEditor so that it doesn't take up the whole-screen
Right now the Divider is static and stays in one place. Is there a built-in way to make the divider stay under the bottom most line of text?
I would think the Spacer would have given me this behavior?
Thank you!
struct EditBio: View {
#ObservedObject var editProfileVM: EditProfileViewModel
var body: some View {
VStack(spacing: 10) {
TextEditor(text: $editProfileVM.bio)
.foregroundColor(.white)
.padding(.top, 70)
.padding([.leading, .trailing], 50)
.frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
Divider().frame(height: 1).background(.white)
Spacer()
}
}
}
It is doing exactly what you told it to do. But a background color on your TextEditor. You will see that it has a height of 200 + a spacing of 10 from the VStack.
I changed your code to make it obvious:
struct EditBio: View {
#State var editProfileVM = ""
var body: some View {
VStack(spacing: 10) {
TextEditor(text: $editProfileVM)
.foregroundColor(.white)
.padding(.top, 70)
.padding([.leading, .trailing], 50)
.frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
.background(Color.gray)
Divider().frame(height: 1).background(.red)
Spacer()
}
}
}
to produce this:
You can see the TextEditor naturally wants to be taller than 200, but that is limiting it. Therefore, the Spacer() is not going to cause the TextEditor to be any smaller.
The other problem that setting a fixed frame causes will be that your text will end up off screen at some point. I am presuming what you really want is a self sizing TextEditor that is no larger than it's contents.
That can be simply done with the following code:
struct EditBio: View {
#State var editProfileVM = ""
var body: some View {
VStack(spacing: 10) {
SelfSizingTextEditor(text: $editProfileVM)
// Frame removed for the image below.
// .frame(minWidth: 100, idealWidth: 200, maxWidth: 400, maxHeight: 200, alignment: .center)
.foregroundColor(.white)
// made the .top padding to be .vertical
.padding(.vertical, 70)
.padding([.leading, .trailing], 50)
.background(Color.gray)
Divider().frame(height: 5).background(.red)
Spacer()
}
}
}
struct SelfSizingTextEditor: View {
#Binding var text: String
#State var textEditorSize = CGSize.zero
var body: some View {
ZStack {
Text(text)
.foregroundColor(.clear)
.copySize(to: $textEditorSize)
TextEditor(text: $text)
.frame(height: textEditorSize.height)
}
}
}
extension View {
func readSize(onChange: #escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
func copySize(to binding: Binding<CGSize>) -> some View {
self.readSize { size in
binding.wrappedValue = size
}
}
}
producing this view:

Swift iOS adapt font size to frame

I am trying to adapt the font size of a text element whose length varies greatly so that it fills the whole frame where it is located and no word needs to be truncated (in a iOS app). In the screenshot below, I would like the sentence to reach the buttons at the bottom of the screen.
Please find below the structure of my code. I have seen a few threads on SO dealing with this issue, but I have not been able to replicate the solution, because of my poor skills in Swift... If someone were so kind as to give me a detailed explanation of what lines of code should be inserted and where to place them, I'd be very grateful!
I have tried to adjust the font size so to say manually, by incrementing or decrementing it via a function according to the length of the string that Text receives as its argument, but it only yielded a mess, because long words need their own separate line, which mars my computation.
With thanks,
Julien
import SwiftUI
import AVKit
import UIKit
import AVFoundation
import Foundation
import CoreServices
import CoreData
import MediaPlayer
struct ContentView: View {
// Various functions
#State var audioPlayer: AVAudioPlayer!
var body: some View {
VStack {
var currentSentence = "string of variable length, could be a long or a very short sentence"
Text(currentSentence) // the variable currentSentence contains a string whose length varies greatly
.font(.system(size: 40, weight: .light, design: .serif))
HStack {
Spacer()
// Play button
Button(action: {
// Action
}) {
Image(systemName: "play.square.fill").resizable()
.frame(width: 150.0, height: 200.0, alignment: .bottom)
}
.frame(maxHeight: .infinity, alignment: .bottom)
Spacer()
// Pause button
Button(action: {
// Action
}){
Image(systemName: "square.slash").resizable()
.frame(width: 150.0, height: 200.0)
.aspectRatio(contentMode: .fill)
}
.frame(maxHeight: .infinity, alignment: .bottom)
Spacer()
}
}
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You need to use .minimumScaleFactor on the text and set the text font to the maximum.
struct ContentView: View {
var body: some View {
VStack (spacing: 0) {
var currentSentence = "string of variable length, could be a long or a very short sentence"
Text(currentSentence)
.font(.system(size: 100, weight: .light, design: .serif))
.minimumScaleFactor(0.1)
Spacer()
HStack (alignment: .bottom, spacing: 0) {
Spacer()
// Play button
Button(action: {
// Action
}) {
Image(systemName: "play.square.fill").resizable()
.frame(width: 150.0, height: 200.0, alignment: .bottom)
}
Spacer()
// Pause button
Button(action: {
// Action
}){
Image(systemName: "square.slash").resizable()
.frame(width: 150.0, height: 200.0)
.aspectRatio(contentMode: .fill)
}
Spacer()
}
}
.edgesIgnoringSafeArea([.bottom])
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
}
}
}
In some cases there might be little bit space between text and buttons, It means new line with bigger font can't fit there.

Automatically adjustable view height based on text height in SwiftUI

I am trying to create a view in SwiftUI where the background of the image on the left should scale vertically based on the height of the text on the right.
I tried a lot of different approaches, from GeometryReader to .layoutPriority(), but I haven't managed to get any of them to work.
Current state:
Desired state:
I know that I could imitate the functionality by hardcoding the .frame(100) for the example I posted, but as text on the right is dynamic, that wouldn't work.
This is full code for the view in the screenshot:
import SwiftUI
struct DynamicallyScalingView: View {
var body: some View {
HStack(spacing: 20) {
Image(systemName: "snow")
.font(.system(size: 32))
.padding(20)
.background(Color.red.opacity(0.4))
.cornerRadius(8)
VStack(alignment: .leading, spacing: 8) {
Text("My Title")
.foregroundColor(.white)
.font(.system(size: 13))
.padding(5)
.background(Color.black)
.cornerRadius(8)
Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
.font(.system(size: 13))
}
}
.padding(.horizontal)
}
}
struct DailyFactView_Previews: PreviewProvider {
static var previews: some View {
DynamicallyScalingView()
}
}
Here is a solution based on view preference key. Tested with Xcode 11.4 / iOS 13.4
struct DynamicallyScalingView: View {
#State private var labelHeight = CGFloat.zero // << here !!
var body: some View {
HStack(spacing: 20) {
Image(systemName: "snow")
.font(.system(size: 32))
.padding(20)
.frame(minHeight: labelHeight) // << here !!
.background(Color.red.opacity(0.4))
.cornerRadius(8)
VStack(alignment: .leading, spacing: 8) {
Text("My Title")
.foregroundColor(.white)
.font(.system(size: 13))
.padding(5)
.background(Color.black)
.cornerRadius(8)
Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
.font(.system(size: 13))
}
.background(GeometryReader { // << set right side height
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
}
.onPreferenceChange(ViewHeightKey.self) { // << read right side height
self.labelHeight = $0 // << here !!
}
.padding(.horizontal)
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
This is the answer without workaround.
struct DynamicallyScalingView: View {
var body: some View {
HStack(spacing: 20) {
Image(systemName: "snow")
.frame(maxHeight: .infinity) // Add this
.font(.system(size: 32))
.padding(20)
.background(Color.red.opacity(0.4))
.cornerRadius(8)
VStack(alignment: .leading, spacing: 8) {
Text("My Title")
.foregroundColor(.white)
.font(.system(size: 13))
.padding(5)
.background(Color.black)
.cornerRadius(8)
Text("Dynamic text that can be of different leghts. Spanning from one to multiple lines. When it's multiple lines, the background on the left should scale vertically")
.font(.system(size: 13))
}
.frame(maxHeight: .infinity) // Add this
}
.padding(.horizontal)
.fixedSize(horizontal: false, vertical: true) // Add this
}
}

Make a VStack fill the width of the screen in SwiftUI

Given this code:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Title")
.font(.title)
Text("Content")
.lineLimit(nil)
.font(.body)
Spacer()
}
.background(Color.red)
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
It results in this interface:
How can I make the VStack fill the width of the screen even if the labels/text components don't need the full width?
A trick I've found is to insert an empty HStack in the structure like so:
VStack(alignment: .leading) {
HStack {
Spacer()
}
Text("Title")
.font(.title)
Text("Content")
.lineLimit(nil)
.font(.body)
Spacer()
}
Which yields the desired design:
Is there a better way?
Try using the .frame modifier with the following options:
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello World")
.font(.title)
Text("Another")
.font(.body)
Spacer()
}
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
.background(Color.red)
}
}
This is described as being a flexible frame (see the documentation), which will stretch to fill the whole screen, and when it has extra space it will center its contents inside of it.
With Swift 5.2 and iOS 13.4, according to your needs, you can use one of the following examples to align your VStack with top leading constraints and a full size frame.
Note that the code snippets below all result in the same display, but do not guarantee the effective frame of the VStack nor the number of View elements that might appear while debugging the view hierarchy.
1. Using frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) method
The simplest approach is to set the frame of your VStack with maximum width and height and also pass the required alignment in frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:):
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Title")
.font(.title)
Text("Content")
.font(.body)
}
.frame(
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading
)
.background(Color.red)
}
}
2. Using Spacers to force alignment
You can embed your VStack inside a full size HStack and use trailing and bottom Spacers to force your VStack top leading alignment:
struct ContentView: View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("Title")
.font(.title)
Text("Content")
.font(.body)
Spacer() // VStack bottom spacer
}
Spacer() // HStack trailing spacer
}
.frame(
maxWidth: .infinity,
maxHeight: .infinity
)
.background(Color.red)
}
}
3. Using a ZStack and a full size background View
This example shows how to embed your VStack inside a ZStack that has a top leading alignment. Note how the Color view is used to set maximum width and height:
struct ContentView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Color.red
.frame(maxWidth: .infinity, maxHeight: .infinity)
VStack(alignment: .leading) {
Text("Title")
.font(.title)
Text("Content")
.font(.body)
}
}
}
}
4. Using GeometryReader
GeometryReader has the following declaration:
A container view that defines its content as a function of its own size and coordinate space. [...] This view returns a flexible preferred size to its parent layout.
The code snippet below shows how to use GeometryReader to align your VStack with top leading constraints and a full size frame:
struct ContentView : View {
var body: some View {
GeometryReader { geometryProxy in
VStack(alignment: .leading) {
Text("Title")
.font(.title)
Text("Content")
.font(.body)
}
.frame(
width: geometryProxy.size.width,
height: geometryProxy.size.height,
alignment: .topLeading
)
}
.background(Color.red)
}
}
5. Using overlay(_:alignment:) method
If you want to align your VStack with top leading constraints on top of an existing full size View, you can use overlay(_:alignment:) method:
struct ContentView: View {
var body: some View {
Color.red
.frame(
maxWidth: .infinity,
maxHeight: .infinity
)
.overlay(
VStack(alignment: .leading) {
Text("Title")
.font(.title)
Text("Content")
.font(.body)
},
alignment: .topLeading
)
}
}
Display:
An alternative stacking arrangement which works and is perhaps a bit more intuitive is the following:
struct ContentView: View {
var body: some View {
HStack() {
VStack(alignment: .leading) {
Text("Hello World")
.font(.title)
Text("Another")
.font(.body)
Spacer()
}
Spacer()
}.background(Color.red)
}
}
The content can also easily be re-positioned by removing the Spacer()'s if necessary.
There is a better way!
To make the VStack fill the width of it's parent you can use a GeometryReader and set the frame. (.relativeWidth(1.0) should work but apparently doesn't right now)
struct ContentView : View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("test")
}
.frame(width: geometry.size.width,
height: nil,
alignment: .topLeading)
}
}
}
To make the VStack the width of the actual screen you can use UIScreen.main.bounds.width when setting the frame instead of using a GeometryReader, but I imagine you likely wanted the width of the parent view.
Also, this way has the added benefit of not adding spacing in your VStack which might happen (if you have spacing) if you added an HStack with a Spacer() as it's content to the VStack.
UPDATE - THERE IS NOT A BETTER WAY!
After checking out the accepted answer, I realized that the accepted answer doesn't actually work! It appears to work at first glance, but if you update the VStack to have a green background you'll notice the VStack is still the same width.
struct ContentView : View {
var body: some View {
NavigationView {
VStack(alignment: .leading) {
Text("Hello World")
.font(.title)
Text("Another")
.font(.body)
Spacer()
}
.background(Color.green)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
.background(Color.red)
}
}
}
This is because .frame(...) is actually adding another view to the view hierarchy and that view ends up filling the screen. However, the VStack still does not.
This issue also seems to be the same in my answer as well and can be checked using the same approach as above (putting different background colors before and after the .frame(...). The only way that appears to actually widen the VStack is to use spacers:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
HStack{
Text("Title")
.font(.title)
Spacer()
}
Text("Content")
.lineLimit(nil)
.font(.body)
Spacer()
}
.background(Color.green)
}
}
The simplest way I manage to solve the issue was is by using a ZStack + .edgesIgnoringSafeArea(.all)
struct TestView : View {
var body: some View {
ZStack() {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("Hello World")
}
}
}
}
Way Number 1 -> Using MaxWidth & MaxHeight
import SwiftUI
struct SomeView: View {
var body: some View {
VStack {
Text("Hello, World!")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.red)
}
}
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
SomeView()
}
}
Way Number 2 -> Using Main Screen Bounds
import SwiftUI
struct SomeView: View {
var body: some View {
VStack {
Text("Hello, World!")
}
.frame(maxWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height)
.background(.red)
}
}
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
SomeView()
}
}
Way Number 3 -> Using Geometry Reader
import SwiftUI
struct SomeView: View {
var body: some View {
GeometryReader { geometryReader in
VStack {
Text("Hello, World!")
}
.frame(maxWidth: geometryReader.size.width, maxHeight: geometryReader.size.height)
.background(.red)
}
}
}
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
SomeView()
}
}
Way Number 4 -> Using Spacers
import SwiftUI
struct SomeView: View {
var body: some View {
VStack {
Text("Hello, World!")
HStack{
Spacer()
}
Spacer()
}
.background(.red)
}
}
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
SomeView()
}
}
EDIT: answer updated with simple (better) approach using .frame
Just use frame modifiers!
struct Expand: View {
var body: some View {
VStack(alignment: .leading) {
Text("Title")
.font(.title)
Text("Content")
.lineLimit(nil)
.font(.body)
}
.frame(maxWidth:.infinity,maxHeight:.infinity,alignment:.topLeading)
.background(Color.red)
}
}
note - you don't even need the spacer in the VStack!
note2 - if you don't want the white at top & bottom, then in the background use:
Color.red.edgesIgnoringSafeArea(.all)
use this
.edgesIgnoringSafeArea(.all)
A good solution and without "contraptions" is the forgotten ZStack
ZStack(alignment: .top){
Color.red
VStack{
Text("Hello World").font(.title)
Text("Another").font(.body)
}
}
Result:
You can do it by using GeometryReader
GeometryReader
Code:
struct ContentView : View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("Turtle Rock").frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading).background(Color.red)
}
}
}
}
Your output like:
One more alternative is to place one of the subviews inside of an HStack and place a Spacer() after it:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
HStack {
Text("Title")
.font(.title)
.background(Color.yellow)
Spacer()
}
Text("Content")
.lineLimit(nil)
.font(.body)
.background(Color.blue)
Spacer()
}
.background(Color.red)
}
}
resulting in :
This is a useful bit of code:
extension View {
func expandable () -> some View {
ZStack {
Color.clear
self
}
}
}
Compare the results with and without the .expandable() modifier:
Text("hello")
.background(Color.blue)
-
Text("hello")
.expandable()
.background(Color.blue)
This is what worked for me (ScrollView (optional) so more content can be added if needed, plus centered content):
import SwiftUI
struct SomeView: View {
var body: some View {
GeometryReader { geometry in
ScrollView(Axis.Set.horizontal) {
HStack(alignment: .center) {
ForEach(0..<8) { _ in
Text("🥳")
}
}.frame(width: geometry.size.width, height: 50)
}
}
}
}
// MARK: - Preview
#if DEBUG
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
SomeView()
}
}
#endif
Result
I know this will not work for everyone, but I thought it interesting that just adding a Divider solves for this.
struct DividerTest: View {
var body: some View {
VStack(alignment: .leading) {
Text("Foo")
Text("Bar")
Divider()
}.background(Color.red)
}
}
Login Page design using SwiftUI
import SwiftUI
struct ContentView: View {
#State var email: String = "william#gmail.com"
#State var password: String = ""
#State static var labelTitle: String = ""
var body: some View {
VStack(alignment: .center){
//Label
Text("Login").font(.largeTitle).foregroundColor(.yellow).bold()
//TextField
TextField("Email", text: $email)
.textContentType(.emailAddress)
.foregroundColor(.blue)
.frame(minHeight: 40)
.background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))
TextField("Password", text: $password) //Placeholder
.textContentType(.newPassword)
.frame(minHeight: 40)
.foregroundColor(.blue) // Text color
.background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))
//Button
Button(action: {
}) {
HStack {
Image(uiImage: UIImage(named: "Login")!)
.renderingMode(.original)
.font(.title)
.foregroundColor(.blue)
Text("Login")
.font(.title)
.foregroundColor(.white)
}
.font(.headline)
.frame(minWidth: 0, maxWidth: .infinity)
.background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(40)
.padding(.horizontal, 20)
.frame(width: 200, height: 50, alignment: .center)
}
Spacer()
}.padding(10)
.frame(minWidth: 0, idealWidth: .infinity, maxWidth: .infinity, minHeight: 0, idealHeight: .infinity, maxHeight: .infinity, alignment: .top)
.background(Color.gray)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
⚠️ Important Note!
🛑 All other solutions are just adding a frame around the content!
✅ but this solution changes the actual frame!
Simple and correct extension
You can use this modifier
.flexible(width: true, height: false)
Demo
💡Note how contents are aligned exactly as you assign in the original stack
The code behind this ( FlexibleViewModifier.swift )
extension View {
func flexible(width: Bool, height: Bool) -> some View {
self.modifier(MatchingParentModifier(width: width, height: height))
}
}
struct MatchingParentModifier: ViewModifier {
#State private var intrinsicSize: CGSize = UIScreen.main.bounds.size
private let intrinsicWidth: Bool
private let intrinsicHeight: Bool
init(width: Bool, height: Bool) {
intrinsicWidth = !width
intrinsicHeight = !height
}
func body(content: Content) -> some View {
GeometryReader { _ in
content.modifier(intrinsicSizeModifier(intrinsicSize: $intrinsicSize))
}
.frame(
maxWidth: intrinsicWidth ? intrinsicSize.width : nil,
maxHeight: intrinsicHeight ? intrinsicSize.height : nil
)
}
}
struct intrinsicSizeModifier: ViewModifier {
#Binding var intrinsicSize: CGSize
func body(content: Content) -> some View {
content.readIntrinsicContentSize(to: $intrinsicSize)
}
}
struct IntrinsicContentSizePreferenceKey: PreferenceKey {
static let defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
extension View {
func readIntrinsicContentSize(to size: Binding<CGSize>) -> some View {
background(
GeometryReader {
Color.clear.preference(
key: IntrinsicContentSizePreferenceKey.self,
value: $0.size
)
}
)
.onPreferenceChange(IntrinsicContentSizePreferenceKey.self) {
size.wrappedValue = $0
}
}
}
Here another way which would save time in your projects:
Much less code and reusable in compare to other answers which they are not reusable!
extension View {
var maxedOut: some View {
return Color.clear
.overlay(self, alignment: .center)
}
func maxedOut(color: Color = Color.clear, alignment: Alignment = Alignment.center) -> some View {
return color
.overlay(self, alignment: alignment)
}
}
use case:
struct ContentView: View {
var body: some View {
Text("Hello, World!")
.maxedOut
.background(Color.blue)
Text("Hello, World!")
.maxedOut(color: Color.red)
}
}
Just add Color.clear to the bottom of the VStack, simple as that :)
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Title")
Color.clear
}
.background(Color.red)
}
}

Resources