4

Okay, so I'm having a problem with the TabView height. This is a basic version of my code.

struct MainView: View {
  var body: some View {
    ScrollView {
        VStack {
            ProfileHStack { ... }
            BioVStack { ... }
            ButtonHStack { ... }
            IconHStack { ... }
            
            TabView {
                GridLayout()
                //Other screens...
            }
            .tabViewStyle(PageTabViewStyle())
        }
    }
  }
}

My grid layout view looks like this.

struct GridLayout: View {
  let columns = [GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1)]

  var body: some View {
    ScrollView  {
        LazyVGrid(columns: columns, spacing: 1) {
            ForEach(0..<30) { n in
                Image("placeholder-image")
                    .resizable()
                    .scaledToFill()
                    .frame(maxWidth: UIScreen.main.bounds.width/3, minHeight: UIScreen.main.bounds.width/3)
                    .clipped()
            }
        }
    }
  }
}

Here a screen shot what my full code looks like.

enter image description here

As you can see the tabview height is getting messed up. It only works with a fixed height. Im trying to make the tabview height as big as the content in my grid layout. How would I be able to achieve this.

Edit:

This is how my full code looks to achieve the screenshot, so you can copy and paste it in Xcode and see if someone can get it to work.

struct ProfileScreen: View {
@State var selectedButton: ProfileButton = .post
@State var tabHeight: CGFloat = 0

@State var didTapProfileTab = false
let width = UIScreen.main.bounds.width / 4
let columns = [GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1)]
var body: some View {
    NavigationView {
        ScrollView {
            //MARK: User Image & Followers
            HStack(spacing: 16) {
                UserProfileImageView(imageName: "gal-gadot", width: UIScreen.main.bounds.width/4, lineWidth: 8)
                Spacer()
                UserProfileInfo(numberOfPosts: "1,570", numberOfFollows: "67.1M", numberOfFollowing: "1,047")
            }
            .padding(.horizontal)
            
            //MARK: User Name, Profession & Bio
            VStack(alignment: .leading) {
                Spacer()
                    .frame(maxWidth: .infinity)
                Text("Gal Gadot")
                    .font(.headline)
                    .fontWeight(.semibold)
                
                Text("Actress")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                
                Text("🌈 meet GOODLES - Mac & Cheese that is just gooder!")
                    .lineLimit(4)
            }
            .padding(.horizontal)
            
            //MARK: Follow & Message Button
            HStack {
                Button {
                    
                } label: {
                    Text("Follow")
                        .fontWeight(.semibold)
                }
                .frame(maxWidth: .infinity, minHeight: 44)
                .background(K.AssetColor.purple)
                .tint(.white)
                .cornerRadius(8)
                
                Button {
                    
                } label: {
                    Text("Message")
                        .fontWeight(.semibold)
                }
                .frame(maxWidth: .infinity, minHeight: 44)
                .foregroundColor(.primary)
                .cornerRadius(8)
                .overlay(RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)).stroke(.secondary))
            }
            .padding(.horizontal)
            .padding(.bottom)
            
            //MARK: Buttons
            HStack(spacing: 0) {
                ImageButton(systemName: "squareshape.split.3x3", assignedButton: .post, selectedButton: $selectedButton) {
                    selectedButton = .post
                }
                
                ImageButton(systemName: "film", assignedButton: .reels, selectedButton: $selectedButton) {
                    selectedButton = .reels
                }
                
                ImageButton(systemName: "video", assignedButton: .video, selectedButton: $selectedButton) {
                    selectedButton = .video
                }
                
                ImageButton(systemName: "person.2", assignedButton: .tag, selectedButton: $selectedButton) {
                    selectedButton = .tag
                }
            }
            .padding(.bottom, -6)
            
            TabView {
                GridLayout()
            }
            .tabViewStyle(PageTabViewStyle())
        }
        .navigationTitle("gal_gadot")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                Image(systemName: "chevron.left")
                    .font(.system(size: 24))
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                Image(systemName: "ellipsis")
                    .padding(.trailing, 8)
                
            }
            
        }
    }
}
}

The ProfileButton looks like this.

enum ProfileButton {
case post,reels,video,tag
}

UserProfileImage

struct UserProfileImageView: View {
let imageName: String
let width: CGFloat
let lineWidth: CGFloat

var body: some View {
    ZStack {
        LinearGradient(colors: [K.AssetColor.purple,K.AssetColor.yellow], startPoint: .bottomLeading, endPoint: .topTrailing)
        Circle()
            .foregroundColor(.white)
            .frame(width: width + lineWidth, height: width + lineWidth)
        Image(imageName)
            .resizable()
            .scaledToFill()
            .clipShape(Circle())
            .frame(width: width, height: width)
    }
    
    .frame(width: width + lineWidth * 2, height: width + lineWidth * 2)
    .clipShape(Circle())
}
}

UserProfileInfo

struct UserProfileInfo: View {
let numberOfPosts: String
let numberOfFollows: String
let numberOfFollowing: String
var body: some View {
    HStack(spacing: 0) {
        Group {
            VStack(alignment: .center) {
                Text(numberOfPosts)
                    .fontWeight(.semibold)
                    .font(.headline)
                Text("Posts")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
            Spacer()
            VStack(alignment: .center) {
                Text(numberOfFollows)
                    .fontWeight(.semibold)
                    .font(.headline)
                Text("Followers")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
            Spacer()
            VStack(alignment: .center) {
                Text(numberOfFollowing)
                    .fontWeight(.semibold)
                    .font(.headline)
                Text("Following")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
    }
}
}

ImageButton

struct ImageButton: View {
let systemName: String
let assignedButton: ProfileButton
@Binding var selectedButton: ProfileButton
let action: () -> Void

var body: some View {
    VStack {
        Button(action: action) {
            Image(systemName: systemName)
                .font(.title2)
                .foregroundColor(selectedButton == assignedButton ? K.AssetColor.purple : K.AssetColor.purple.opacity(0.5))
        }
        Spacer()
        
        Rectangle()
            .frame(minWidth: UIScreen.main.bounds.width / 4, maxHeight: 1)
            .foregroundColor(selectedButton == assignedButton ? K.AssetColor.purple : K.AssetColor.purple.opacity(0.5))
    }
    
}
}

Update:

So now the problem is when I have another screen with a different height.

 TabView {
    //Height 1000
    Screen1(height: $height)
    //Height 300
    Screen2(height: $height)
 }
 .tabViewStyle(PageTabViewStyle())
 .frame(height: height)

So I included this on both screens.

@Binding var height: CGFloat

.background(
        GeometryReader { geo in
            Color.clear
                .preference(
                    key: HeightPreferenceKey.self,
                    value: geo.size.height
                )
        }
        .onPreferenceChange(HeightPreferenceKey.self) { height in
            self.height = height
        }
    )

The onPreferenceChange only gets called once so when I switch from screen 2 to screen 1, screen 1 height is now the height of screen 2. Now I had to add this to both screens for it to work.

@State var screenHeight: CGFloat

Instead of setting the self.height I set the screenHeight in onPreferenceChange. Then I set the self.height to screenHeight in onAppear.

Now the only thing now is that when I just drag a little to screen2 the height of screen1 one changes to screen2. Here is a visual example..

3
  • Please give a full exemple code, so we can easily reproduce Commented Nov 19, 2021 at 14:25
  • @Hikosei added my full code so it can be replicated. Commented Nov 19, 2021 at 15:37
  • @LuisRamirez, have you managed to solve it? If yes, could you please point me in the right direction? Commented Dec 22, 2022 at 15:18

2 Answers 2

4

Firstly, you should remove the inner ScrollView. It's not a good idea to have a ScrollView in a ScrollView, because it doesn't make sense to be scrolling in something already scrollable.

I solved this by reading the actual height of the LazyVStack with GeometryReader, set that value to a @Binding so it is propagated up, then set the height of the TabView to this value.

Code:

struct ProfileScreen: View {
    /* ... */

    @State private var height: CGFloat = 0

    var body: some View {
        NavigationView {
            ScrollView {
                //MARK: User Image & Followers
                /* ... */

                //MARK: User Name, Profession & Bio
                /* ... */

                //MARK: Follow & Message Button
                /* ... */

                //MARK: Buttons
                /* ... */

                TabView {
                    GridLayout(height: $height)
                }
                .tabViewStyle(PageTabViewStyle())
                .frame(height: height)
            }
            /* ... */
        }
    }
}
struct GridLayout: View {
    let columns = [GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1)]

    @Binding var height: CGFloat

    var body: some View {
        LazyVGrid(columns: columns, spacing: 1) {
            /* ... */
        }
        .background(
            GeometryReader { geo in
                Color.clear
                    .preference(
                        key: HeightPreferenceKey.self,
                        value: geo.size.height
                    )
            }
            .onPreferenceChange(HeightPreferenceKey.self) { height in
                self.height = height
            }
        )
    }
}
struct HeightPreferenceKey: PreferenceKey {
    static let defaultValue: CGFloat = 0

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

thanks for ur answer, it works but I came across some other problems. Can you check the update that I added to my question.
@LuisRamirez You can swap self.height = height in the preference change to self.height = max(self.height, height) for both instead.
I switch it but now screen2 is the same size as screen1.
@LuisRamirez Yeah... to avoid that is complicated. Because if you look at how Instagram does it, they change the size when you start scrolling if you move to a smaller view. That's a lot of work and I gave it a go for a while but it would take long enough that I'd just be writing the whole code and I won't be doing that, sorry :/
This doesn't seem to work for List
0

SwiftUI can't calculate size of scrollView inside in scrollView. UIKit also too...

You can change top scrollView to VStack to fix this problem. Also you have problem of resizing upper views, you can fix it size by .fixedSize()

Example of code:

var body: some View {
    NavigationView {
        VStack {
            //MARK: User Image & Followers
            HStack(spacing: 16) {
                UserProfileImageView(imageName: "gal-gadot", width: UIScreen.main.bounds.width/4, lineWidth: 8)
                Spacer()
                UserProfileInfo(numberOfPosts: "1,570", numberOfFollows: "67.1M", numberOfFollowing: "1,047")
            }
            .fixedSize(horizontal: false, vertical: true)
            .padding(.horizontal)
            
            //MARK: User Name, Profession & Bio
            VStack(alignment: .leading) {
                Spacer()
                    .frame(maxWidth: .infinity)
                Text("Gal Gadot")
                    .font(.headline)
                    .fontWeight(.semibold)
                
                Text("Actress")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                
                Text("🌈 meet GOODLES - Mac & Cheese that is just gooder!")
                    .lineLimit(4)
            }
            .fixedSize(horizontal: false, vertical: true)
            .padding(.horizontal)
            
            //MARK: Follow & Message Button
            HStack {
                Button {
                    
                } label: {
                    Text("Follow")
                        .fontWeight(.semibold)
                }
                .frame(maxWidth: .infinity, minHeight: 44)
                .background(Color.purple)
                .tint(.white)
                .cornerRadius(8)
                
                Button {
                    
                } label: {
                    Text("Message")
                        .fontWeight(.semibold)
                }
                .frame(maxWidth: .infinity, minHeight: 44)
                .foregroundColor(.primary)
                .cornerRadius(8)
                .overlay(RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)).stroke(.secondary))
            }
            .fixedSize(horizontal: false, vertical: true)
            .padding(.horizontal)
            .padding(.bottom)
            
            //MARK: Buttons
            HStack(spacing: 0) {
                ImageButton(systemName: "squareshape.split.3x3", assignedButton: .post, selectedButton: $selectedButton) {
                    selectedButton = .post
                }
                
                ImageButton(systemName: "film", assignedButton: .reels, selectedButton: $selectedButton) {
                    selectedButton = .reels
                }
                
                ImageButton(systemName: "video", assignedButton: .video, selectedButton: $selectedButton) {
                    selectedButton = .video
                }
                
                ImageButton(systemName: "person.2", assignedButton: .tag, selectedButton: $selectedButton) {
                    selectedButton = .tag
                }
            }
            .fixedSize(horizontal: false, vertical: true)
            .padding(.bottom, -6)
            
            TabView {
                GridLayout()
            }
            .tabViewStyle(PageTabViewStyle())
        }
        .navigationTitle("gal_gadot")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                Image(systemName: "chevron.left")
                    .font(.system(size: 24))
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                Image(systemName: "ellipsis")
                    .padding(.trailing, 8)
                
            }
            
        }
    }
}

1 Comment

thanks for your answer. However I would like it to be scrollable not just the GridLayout.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.