UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day 94: Layout and Geometry challenge #1 (Moonshot)

Forums > 100 Days of SwiftUI

Hi folks,

I'm a bit stuck on challenge #1 of Day 94. Paul asks us to add code to Moonshot's MissionView so that the mission badge shrinks as you scroll down. I've written the code below, and the badge shrinks, but I can't work out how to get the frame around it to shrink as you scroll so you get lots of whitespace. At the moment the start of my MissionView looks like this:

GeometryReader { fullView in
    ScrollView(.vertical) {
        VStack {
            GeometryReader { geo in
                Image(self.mission.image)
                    .resizable()
                    .scaledToFit()
                    .frame(maxWidth: geo.size.width * (0.8 + (geo.frame(in: .global).midY - geo.size.height / 2) / (geo.size.height / 2)))
                    .padding(.top)
                    .accessibility(label: Text("Mission patch for \(mission.displayName)"))
            }
            .frame(width: fullView.size.width * 0.7, height: fullView.size.width * 0.7, alignment: .center)

I think I need to tweak the final .frame modifier but I can't work out how, since if I don't specify a width or height SwiftUI doesn't assign it any room at all (the badge just disappears), and I can't use geo because it doesn't exist outside the innermost loop. Can anyone help?

3      

I ended up finding a very useful tutorial which was designed for something slightly different and retrofitting some of the advice in that to get it to work how I wanted it to. I'm posting it here in the hopes it's useful to someone!

The code above now looks like this:

let frameHeight: CGFloat = 300

var body: some View {
    GeometryReader { fullView in
        ScrollView(.vertical) {
            VStack {
                GeometryReader { geometry in
                    HStack {
                        Spacer()
                        Image(self.mission.image)
                            .resizable()
                            .scaledToFit()
                            .frame(width: fullView.size.width * 0.7)
                            .padding(.top)
                            .offset(x: 0, y: getOffsetForMissionPatch(for: geometry))
                            .scaleEffect(getScaleOfMissionPatch(for: geometry))
                            .accessibility(label: Text("Mission patch for \(mission.displayName)"))
                        Spacer()
                    }
                }
                .frame(height: self.frameHeight, alignment: .center)

That lets me have a really neat effect to make the mission patch scale in and out as I scroll up/down, and looks really smooth.

func getOffsetForMissionPatch(for geometry: GeometryProxy) -> CGFloat {
    let scale = getScaleOfMissionPatch(for: geometry)
    return self.frameHeight * (1 - scale) * 0.95
}

func getScaleOfMissionPatch(for geometry: GeometryProxy) -> CGFloat {
    let offset = geometry.frame(in: .global).minY
    let halfHeight = self.frameHeight / 2

    // This value was found by just printing the minY of .global at the start
    let startingOffset: CGFloat = 91

    let minimumSizeAtOffset = startingOffset - halfHeight
    let scale = 0.8 + 0.2 * (offset - minimumSizeAtOffset) / halfHeight

    if scale < 0.8 {
        return 0.8
    } else if scale > 1.2 {
        return 1.2
    }

    return scale
}

4      

Hi

Here is my take on that, that might be useful for anyone

var body: some View {
        GeometryReader { missionView in
            ScrollView(.vertical) {
                VStack {
                    GeometryReader { logoImage in
                        Image(self.mission.image)
                            .resizable()
                            .scaledToFit()
                            .frame(width:
                                    max(missionView.size.width * 0.7 * 0.4,
                                        min(missionView.size.width * 0.7,
                                            logoImage.frame(in: .global).maxY - missionView.frame(in: .global).minY)),
                                   height: missionView.size.width * 0.7,
                                   alignment: .bottom
                            )
                            .position(x: logoImage.size.width / 2, y: logoImage.size.height / 2)
                            .padding(.top)
                            .accessibility(label: Text("Logo of \(self.mission.displayName)"))
                    }
                    .frame(height: missionView.size.width * 0.7)

                    Text(self.mission.displayName)
                        .font(.largeTitle)
                        .padding()

5      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.