How to Stretch a View to Its Parent Frame with Swiftui

How do I stretch a View to its parent frame with SwiftUI?

You can use GeometryReader and wrap your ScrollView into it and set content width to the geometry's width size. A GeometryReader:

returns a flexible preferred size to its parent layout.

So your code would be something like below:

HStack(spacing: 1) {
Rectangle().frame(width:20).foregroundColor(.red).frame(width:20)
GeometryReader { geometry in
ScrollView {
VStack {
ForEach(0..<5) { index in
Rectangle().frame(minWidth: 50, maxWidth: .infinity, minHeight: 50, maxHeight: 50)
}
}.relativeWidth(1)
.frame(width: geometry.size.width)
}
}
Rectangle().foregroundColor(.red).frame(width:20)
}

How to scale an image to fill the parent view without affecting the layout in SwiftUI?

Here is a way to do it without using GeometryReader. By making the image an .overlay() of another view, that view can handle the clipping with the .clipped() modifier:

struct ContentView: View {
var body: some View {
ImageContainerView()
.frame(width: 300, height: 140)
.border(.red, width: 2)
}
}

struct ImageContainerView: View {
var body: some View {
Color.clear
.overlay (
Image("image")
.resizable()
.aspectRatio(contentMode: .fill)
.border(.blue, width: 2)
)
.clipped()
}
}

Also, pass in the name of the image so that ImageContainerView and be reused with other images.

SwiftUI Make parent view stretch to fit child view

Instead of wrapping the entire ZStack with the GeometryReader, wrap only the Rectangle where you need the size:

struct ItemDetailsRowView: View {
var itemDetails: ItemDetials

var body: some View {
ZStack(alignment: .leading) {
GeometryReader { geometry in
Rectangle().frame(width: geometry.size.width * itemDetails.stat/itemDetails.maxOfStat,
height: geometry.size.height)
.opacity(0.3)
.foregroundColor(Color(itemDetails.color))
}

HStack {
VStack(alignment: .leading) {
Text(itemDetails.title)
.font(.system(size: 20))
Text(itemDetails.rowOne)
.font(.system(size: 12))
.foregroundColor(.gray)
Text(itemDetails.rowTwo)
.font(.system(size: 12))
.foregroundColor(.gray)
}

}
}
}
}

Sample Image

Adjust Image Size of Image to Parent View Size

If I understood correctly the intention was to fill proportionally, so

  ZStack{
Image(backgoundImage!)
.resizable() // for resizing
.scaledToFill() // << here !! // for filling image on ImageView

but in that case it can spread out of bounds, so it needs to be clipped in place of .frame applied, so either

ImageCard("image")
.frame(decide the size of the image)
.clipped() // << here !!

or, better, as already described inject dimension inside card and apply it there, like

    Image(backgoundImage!)
.resizable() // for resizing
.scaledToFill() // << here !!
.frame(decide the size of the image)
.clipped() // << here !!
.cornerRadius(5)
.shadow(color: .gray, radius: 6, x: 0, y: 3)
}

How to make width of view equal to superview in SwiftUI

You need to use .frame(minWidth: 0, maxWidth: .infinity) modifier

Add the next code

        Button(action: tap) {
Text("Button")
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.red)
}
.padding(.horizontal, 20)

Padding modifiers will allow you to have some space from the edge.

Keep in mind that the order of modifiers is essential. Because modifiers are functions that are wrapping the view below (they do not change properties of views)

Make a VStack fill the width of the screen in SwiftUI

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.

How to make view the size of another view in SwiftUI

I have written a detailed explanation about using GeometryReader, view preferences and anchor preferences. The code below uses those concepts. For further information on how they work, check this article I posted: https://swiftui-lab.com/communicating-with-the-view-tree-part-1/

The solution below, will properly animate the underline:

Sample Image

I struggled to make this work and I agree with you. Sometimes, you just need to be able to pass up or down the hierarchy, some framing information. In fact, the WWDC2019 session 237 (Building Custom Views with SwiftUI), explains that views communicate their sizing continuously. It basically says Parent proposes size to child, childen decide how they want to layout theirselves and communicate back to the parent. How they do that? I suspect the anchorPreference has something to do with it. However it is very obscure and not at all documented yet. The API is exposed, but grasping how those long function prototypes work... that's a hell I do not have time for right now.

I think Apple has left this undocumented to force us rethink the whole framework and forget about "old" UIKit habits and start thinking declaratively. However, there are still times when this is needed. Have you ever wonder how the background modifier works? I would love to see that implementation. It would explain a lot! I'm hoping Apple will document preferences in the near future. I have been experimenting with custom PreferenceKey and it looks interesting.

Now back to your specific need, I managed to work it out. There are two dimensions you need (the x position and width of the text). One I get it fair and square, the other seems a bit of a hack. Nevertheless, it works perfectly.

The x position of the text I solved it by creating a custom horizontal alignment. More information on that check session 237 (at minute 19:00). Although I recommend you watch the whole thing, it sheds a lot of light on how the layout process works.

The width, however, I'm not so proud of... ;-) It requires DispatchQueue to avoid updating the view while being displayed. UPDATE: I fixed it in the second implementation down below

First implementation

extension HorizontalAlignment {
private enum UnderlineLeading: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[.leading]
}
}

static let underlineLeading = HorizontalAlignment(UnderlineLeading.self)
}

struct GridViewHeader : View {

@State private var activeIdx: Int = 0
@State private var w: [CGFloat] = [0, 0, 0, 0]

var body: some View {
return VStack(alignment: .underlineLeading) {
HStack {
Text("Tweets").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 0))
Spacer()
Text("Tweets & Replies").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 1))
Spacer()
Text("Media").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 2))
Spacer()
Text("Likes").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 3))
}
.frame(height: 50)
.padding(.horizontal, 10)
Rectangle()
.alignmentGuide(.underlineLeading) { d in d[.leading] }
.frame(width: w[activeIdx], height: 2)
.animation(.linear)
}
}
}

struct MagicStuff: ViewModifier {
@Binding var activeIdx: Int
@Binding var widths: [CGFloat]
let idx: Int

func body(content: Content) -> some View {
Group {
if activeIdx == idx {
content.alignmentGuide(.underlineLeading) { d in
DispatchQueue.main.async { self.widths[self.idx] = d.width }

return d[.leading]
}.onTapGesture { self.activeIdx = self.idx }

} else {
content.onTapGesture { self.activeIdx = self.idx }
}
}
}
}

Update: Better implementation without using DispatchQueue

My first solution works, but I was not too proud of the way the width is passed to the underline view.

I found a better way of achieving the same thing. It turns out, the background modifier is very powerful. It is much more than a modifier that can let you decorate the background of a view.

The basic steps are:

  1. Use Text("text").background(TextGeometry()). TextGeometry is a custom view that has a parent with the same size as the text view. That is what .background() does. Very powerful.
  2. In my implementation of TextGeometry I use GeometryReader, to get the geometry of the parent, which means, I get the geometry of the Text view, which means I now have the width.
  3. Now to pass the width back, I am using Preferences. There's zero documentation about them, but after a little experimentation, I think preferences are something like "view attributes" if you like. I created my custom PreferenceKey, called WidthPreferenceKey and I use it in TextGeometry to "attach" the width to the view, so it can be read higher in the hierarchy.
  4. Back in the ancestor, I use onPreferenceChange to detect changes in the width, and set the widths array accordingly.

It may all sound too complex, but the code illustrates it best. Here's the new implementation:

import SwiftUI

extension HorizontalAlignment {
private enum UnderlineLeading: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[.leading]
}
}

static let underlineLeading = HorizontalAlignment(UnderlineLeading.self)
}

struct WidthPreferenceKey: PreferenceKey {
static var defaultValue = CGFloat(0)

static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}

typealias Value = CGFloat
}

struct GridViewHeader : View {

@State private var activeIdx: Int = 0
@State private var w: [CGFloat] = [0, 0, 0, 0]

var body: some View {
return VStack(alignment: .underlineLeading) {
HStack {
Text("Tweets")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 0))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[0] = $0 })

Spacer()

Text("Tweets & Replies")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 1))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[1] = $0 })

Spacer()

Text("Media")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 2))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[2] = $0 })

Spacer()

Text("Likes")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 3))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[3] = $0 })

}
.frame(height: 50)
.padding(.horizontal, 10)
Rectangle()
.alignmentGuide(.underlineLeading) { d in d[.leading] }
.frame(width: w[activeIdx], height: 2)
.animation(.linear)
}
}
}

struct TextGeometry: View {
var body: some View {
GeometryReader { geometry in
return Rectangle().fill(Color.clear).preference(key: WidthPreferenceKey.self, value: geometry.size.width)
}
}
}

struct MagicStuff: ViewModifier {
@Binding var activeIdx: Int
let idx: Int

func body(content: Content) -> some View {
Group {
if activeIdx == idx {
content.alignmentGuide(.underlineLeading) { d in
return d[.leading]
}.onTapGesture { self.activeIdx = self.idx }

} else {
content.onTapGesture { self.activeIdx = self.idx }
}
}
}
}

Make ScrollView content fill its parent in SwiftUI

A simple way for you, using frame(maxWidth: .infinity)

ScrollView(.vertical) {
VStack {
ForEach(0..<100) {
Text("Item \($0)")
}
}
.frame(maxWidth: .infinity)
}


Related Topics



Leave a reply



Submit