0

Consider having a screen like on the wireframe consisting of text, an image, and a sheet. When the sheet is dragged down, the image should grow vertically and go out of bounds.

This already works to a certain extent, but the image only scales vertically but not horizontally.

The code I used is the following:

struct SizePreferenceKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue: Value = 0

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

struct ContentView: View {
    @State private var selectedPresentationDetent: PresentationDetent = .fraction(0.6)
    @State private var sheetPosition: CGFloat = 0

    var body: some View {
        GeometryReader { geometry in
            VStack {
                GeometryReader { geometryImage in
                    VStack(alignment: .center) {
                        Spacer()
                        Text("Hello, world!")
                            .padding()

                        AsyncImage(
                            url: URL(string: "https://images.unsplash.com/photo-1551709645-3f16f608bb80?ixlib"),
                            content: { image in
                                image.resizable()
                                    .frame(height: geometryImage.size.height * 0.2 + sheetPosition)
                                    .aspectRatio(contentMode: .fit)
                            },
                            placeholder: {}
                        )
                        .frame(height: geometryImage.size.height * 0.2 + sheetPosition)
                    }
                }
                .frame(height: geometry.size.height * 0.5 + geometry.safeAreaInsets.top)
            }
            .sheet(isPresented: .constant(true)) {
                GeometryReader { sheetGeometry in
                    Color.blue
                        .ignoresSafeArea()
                        .interactiveDismissDisabled()
                        .presentationBackgroundInteraction(.enabled)
                        .presentationDetents([
                            .fraction(0.6),
                            .fraction(0.3)
                        ], selection: $selectedPresentationDetent)
                        .presentationDragIndicator(.visible)
                        .preference(key: SizePreferenceKey.self, value: sheetGeometry.frame(in: .global).minY)
                }
                .onPreferenceChange(SizePreferenceKey.self) { preferences in
                    self.sheetPosition = preferences
                }
            }
        }
    }
}

1 Answer 1

1

Don't take this answer as production-ready, it needs improvements, though, this is my proposition:

  1. Extend CGSize to expose a function to compute the aspect ratio, like this (handle the case of self.height == 0 depending on what the expected behavior is in your app):
extension CGSize {
    func aspectRatio() -> CGFloat {
        return self.width/self.height
    }
}
  1. Add the following property to your component:
@State private var initialImageFrame: CGRect = .zero
  1. Replace the closure of AsyncImage with the following code:
let computedHeight: CGFloat = geometryImage.size.height * 0.2 + sheetPosition
let computedWidth: CGFloat = computedHeight*geometryImage.size.aspectRatio()
                                        
image
    .resizable()
    .frame(
        width: computedWidth,
        height: computedHeight
    )
    .aspectRatio(contentMode: .fit)
    .onAppear {
        self.initialImageFrame = geometryImage.frame(in: .global)
    }

This way you're manually making sure that the aspect ratio is preserved during drag.

  1. Embed the AsyncImage inside a ZStack with the following modifiers:
ZStack {
    //The AsyncImage
}
.clipShape(Rectangle())
.frame(maxWidth: geometry.size.width)

Then you should have a close call to what you're looking for.

Sign up to request clarification or add additional context in comments.

Comments

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.