33

In my SwiftUI application, I'm trying to implement a UI similar to this:

I've added the two rows for category 1 and category 2. The result looks like this:

NavigationView {
    VStack(alignment: .leading) {
        CategoryRow(...)
        CategoryRow(...)
        Spacer()
    }
    .navigationBarTitle(Text("Featured"))
}

Now, when added the view for the third category – an VStack with images – the following happens:

This happened, after I replaced Spacer(), with said VStack:

VStack(alignment: .leading) {
    Text("Rivers")
        .font(.headline)
    ForEach(self.categories["Rivers"]!.identified(by: \.self)) { landmark in
        landmark.image(forSize: 200)
    }
}

My CategoryRow is implemented as follows:

VStack(alignment: .leading) {
    Text(title)
        .font(.headline)
    ScrollView {
        HStack {
            ForEach(landmarks) { landmark in
                CategoryItem(landmark: landmark, isRounded: self.isRounded)
            }
        }
    }
}

Question

It seems that the views are compressed. I was not able to find any compression resistance or content hugging priority modifiers to fix this.
I also tried to use .fixedSize() and .frame(width:height:) on CategoryRow.

How can I prevent the compression of these views?


Update

I've tried embedding the whole outer stack view in a scroll view:

NavigationView {
    ScrollView { // also tried List
        VStack(alignment: .leading) {
            CategoryRow(...)
            CategoryRow(...)
            ForEach(...) { landmark in
                landmark.image(forSize: 200)
            }
        }
        .navigationBarTitle(Text("Featured"))
    }
}

...and the result is worse:

LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174

5 Answers5

101

You might prevent the views in VStack from being compressed by using

  .fixedSize(horizontal: false, vertical: true)

For example: I have the following VStack:

VStack(alignment: .leading){
        ForEach(group.items) {
            FeedCell(item: $0)
        }
    }

Which render compressed Text()

VStack with compressed elements

When I add .fixedSize(horizontal: false, vertical: true) it doesn't compress anymore

VStack(alignment: .leading){
        ForEach(group.items) {
            FeedCell(item: $0)
                .fixedSize(horizontal: false, vertical: true)
        }
    }

VStack doesn't compresss content

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
Denis
  • 1,044
  • 1
  • 7
  • 6
  • 1
    Thank you. I had multiline `Text()` and an `Image().resizable()` fighting for vertical space, with this I was able to force the text to take priority and resist compression. – Cloud Jul 14 '20 at 21:08
24

You could try to add a layoutPriority()operator to your first VStack. This is what the documentation says about the method:

In a group of sibling views, raising a view’s layout priority encourages that view to shrink later when the group is shrunk and stretch sooner when the group is stretched.

So it's a bit like the content compression resistance priority in Autolayout. But the default value here is 0, so you just have to set it to 1 to get the desired effect, like this:

VStack(alignment: .leading) {
    CategoryRow(...)
    CategoryRow(...)
    Spacer()
}.layoutPriority(1)
VStack(alignment: .leading) {
    ...
}

Hope it works!

turingtested
  • 6,356
  • 7
  • 32
  • 47
1

It looks like is not enough space for all your views in VStack, and it compresses some of them. You can embed it into the ScrollView

NavigationView {
 ScrollView {
   VStack(alignment: .leading) {
      CategoryRow(...)
      CategoryRow(...)
      /// you images and so on
   }
  }
}
Argas
  • 1,427
  • 1
  • 10
  • 12
0
struct ContentView1: View {
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    CategoryListView {
                        CategoryView()
                    }

                    CategoryListView {
                        SquareCategoryView()
                    }
                    
                    CategoryListView {
                        RectangleCategoryView()
                    }
                }
                .padding()
            }
            .navigationTitle("Featured")
        }
    }
}

struct CategoryListView<Content>: View where Content: View {
    private let viewSize: CGFloat = 150
    var content: () -> Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    var body: some View {
        VStack {
            HStack {
                Text("Category name")
                Spacer()
            }
            ScrollView(.horizontal, showsIndicators: false){
                HStack {
                    ForEach(0..<10) { _ in
                        content()
                    }
                }
            }
        }
    }
}

struct ContentView1_Previews: PreviewProvider {
    static var previews: some View {
        ContentView1()
    }
}

struct CategoryView: View {
    private let viewSize: CGFloat = 150
    var body: some View {
        Circle()
            .fill()
            .foregroundColor(.blue)
            .frame(width: viewSize, height: viewSize)
    }
}

struct RectangleCategoryView: View {
    private let viewSize: CGFloat = 350
    var body: some View {
        Rectangle()
            .fill()
            .foregroundColor(.blue)
            .frame(width: viewSize, height: viewSize * 9 / 16)
    }
}

struct SquareCategoryView: View {
    private let viewSize: CGFloat = 150
    var body: some View {
        Rectangle()
            .fill()
            .foregroundColor(.blue)
            .frame(width: viewSize, height: viewSize)
    }
}

Dharman
  • 30,962
  • 25
  • 85
  • 135
Nilay
  • 327
  • 3
  • 9
-2

I think your topmost view (in the NavigationView) needs to be a List, so that it is scrollable:

NavigationView {
            List {
                ...

Or use a ScrollView.

A stack automatically fits within a screen. If you want your content to exceed this, you would have used a ScrollView or a TableView etc i UIKit

EDIT:

Actually, a little Googling brought this result, which seems to be exactly what you are making: https://developer.apple.com/tutorials/swiftui/composing-complex-interfaces

Marius Waldal
  • 9,537
  • 4
  • 30
  • 44
  • Well I did exactly that, but the same thing happens: the views are compressed, both using a ScrollView and a List. And yes, I have the screenshot from that tutorial, but even though they show that there, they don't implement exactly that UI, but rather 3 category rows. Since each of these isn't very high, they don't reach the bottom of the view and hence, don't get compressed in the first place. – LinusGeffarth Jun 08 '19 at 16:33
  • See my edited question. I also tried to remove the top most stack view altogether, so that the `CategoryRow`s are inside `List`/`ScrollView` directly - no luck. – LinusGeffarth Jun 08 '19 at 18:40