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
}
}
}
}
}
Don't take this answer as production-ready, it needs improvements, though, this is my proposition:
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
}
}
@State private var initialImageFrame: CGRect = .zero
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.
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.