4

I am working through a sample app to familiarize myself with Swift and SwiftUI and am wanting to combine the ideas of composite layouts using custom views and GeometryReader to determine the screen-size of the device.

The page I'm trying to build is a scrollable list of friends where each row item is a button that can be selected to expand the list to see more details of each friend. I've built the button and all containing views as a FriendView to use inside of my FriendListView. I have defined 2 GeometryReader views within the FriendListView but do not know how I can define the size of each row from within the FriendView to make it maintain the appropriate size.

The need for this comes from not every image that could be set by a user later on for their friend will have the same dimensions so I want to keep it consistent. I understand I could set a .frame() modifier and hardcode the width but I would like it to be relational to the screen to be consistent across all phone screens.

Example: When a row is not expanded, the image should take up half of the device width. When it is expanded I would want it to take up one quarter of the device screen width.

Screenshot

Application View

Sample Code

struct FriendListView: View {
    var pets = ["Woofer", "Floofer", "Booper"]

    var body: some View {
        GeometryReader { geo1 in
            NavigationView {
                GeometryReader { geo2 in
                    List(self.pets, id: \.self) { pet in
                            FriendView(name: pet)

                    }// End of List
                }
                .navigationBarTitle(Text("Furiends"))

            }// End of NavigationView
        }// End of GeometryReader geo1
    }// End of body
}
struct FriendView: View {
    @State private var isExpanded = false
    var randomPic = Int.random(in: 1...2)

    var name =  ""
    var breed = "Dawg"
    var body: some View {

            Button(action: self.toggleExpand) {
                HStack {
                    VStack {
                    Image("dog\(self.randomPic)")
                        .resizable()
                        .renderingMode(.original)
                        .scaledToFill()
                        .clipShape(Circle())

                       if self.isExpanded == false {
                        Text(self.name)
                            .font(.title)
                            .foregroundColor(.primary)
                       }
                   }

                   if self.isExpanded == true {
                       VStack {
                        Text(self.name).font(.title)
                        Text(self.breed).font(.headline)
                       }
                       .foregroundColor(.primary)
                   }
               }
            }// End of Button
    }
    func toggleExpand() {
        isExpanded.toggle()
    }
}
Mdank
  • 103
  • 6

1 Answers1

2

You can define a GeometryReader in the top level FriendListView and pass the screen width to the FriendView:

struct FriendListView: View {
    var pets = ["Woofer", "Floofer", "Booper"]

    var body: some View {
        GeometryReader { geo in
            NavigationView {
                List(self.pets, id: \.self) { pet in
                    FriendView(name: pet, screenWidth: geo.size.width)
                }
                .navigationBarTitle(Text("Furiends"))
            }
        }
    }
}

Then use the .frame to set the maximum width for an image.

struct FriendView: View {
    ...

    var screenWidth: CGFloat

    var imageWidth: CGFloat {
        isExpanded ? screenWidth / 4 : screenWidth / 2
    }

    var body: some View {
        Button(action: self.toggleExpand) {
            HStack {
                VStack {
                    Image("dog\(self.randomPic)")
                        .resizable()
                        .renderingMode(.original)
                        .scaledToFill()
                        .clipShape(Circle())
                        .frame(width: imageWidth, height: imageWidth)  // <- add frame boundaries

                    if self.isExpanded == false {
                        Text(self.name)
                            .font(.title)
                            .foregroundColor(.primary)
                    }
                }

                if self.isExpanded == true {
                    VStack {
                        Text(self.name).font(.title)
                        Text(self.breed).font(.headline)
                    }
                    .foregroundColor(.primary)
                }
            }
        }
    }

    func toggleExpand() {
        isExpanded.toggle()
    }
}

In case you'd want your picture to be centered you can use Spacer:

HStack {
    Spacer()
    FriendView(name: pet, screenWidth: geo.size.width)
    Spacer()
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • This helps me understand how to pass the width into the new view. I've tried different parameters into the frame modifier, including the one you provided, but still have the images rendering as different widths.Any suggestion for how I should be structuring the frame modifier? – Mdank Jun 13 '20 at 16:47
  • I'm not sure exactly... The example I've provided was tested and it works for me. I chose two images (one square and one rectangle-shaped). Both were the same size in the above View. Did you try my whole code or just parts of it? (I used just one GeometryReader). – pawello2222 Jun 13 '20 at 16:58
  • Yup I removed the extra GeometryReader and think it has something to do with the clipShape.Same behavior as the screenshot I put in the original post. I've uploaded everything files to the repo if you'd like to try with these exact pictures. https://github.com/MichaelWDanko/furiends – Mdank Jun 13 '20 at 17:24
  • Maybe I didn't understand you correctly. You have pictures of dogs which may have different sizes. You want to show them in List where for every item (dog) you display its image. When an item is not expanded you want its image to have a width of screen.width / 2. And if it's expanded it's supposed to be 1/4. When all items are in the same state (expanded or not) all of them are supposed to be of the same size and in circle shape. Did I miss anything? – pawello2222 Jun 13 '20 at 17:34
  • 1
    Yes that is correct. I am seeing minor differences in the circle shape. Apologies if I haven’t explained that clearly. Appreciate the help – Mdank Jun 13 '20 at 17:48
  • Oh, I see. For that you can set the constraint on the `height` as well. I'll update my answer. – pawello2222 Jun 13 '20 at 18:33
  • 1
    That was great! I did have to change it to use `width:height` vs `maxWidth:maxHeight` but I was finally able to get to line up. I know it was a small tweak but appreciate your help. I marked your answer as the one to resolve this question. – Mdank Jun 13 '20 at 19:44