Description
I am using a custom UIScrollView with custom content in a popup view that transitions from the bottom of the screen. However, each time it animates, the content within the UIScrollView appears to offset its position when reaching the top or bottom safe area of the screen during the transition.
The issue occurs at the beginning of the animation when the UIView transitions from the bottom of the screen, right in the area of the device's safe area. Therefore, I assume the problem is related to how the UIView's content interacts with the vertical safe areas of the device.
You can observe the issue in these images:
Replication of the problem
The problem is simplified in the example below. To keep the code as simple as possible, I am not using any coordinators or additional settings for the UIScrollView in the example below.
I tried different approaches like setting the ignoreSafeArea() modifiers on UIView, setting the fixed height of the content of the UIScrollView or setting the contentInsetAdjustmentBehavior to .never for the UIScrollView but nothing solves the issue.
Testd on: XCode Version 14.2, iOS Simulator iPhone 13 Pro with iOS 16.2
struct ContentView: View {
@State private var isUIViewShowed: Bool = true
var body: some View {
VStack {
Button("Toggle the UIView", action: {
withAnimation(.linear(duration: 4)){
self.isUIViewShowed.toggle()
}
})
.frame(height: 50)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray.opacity(0.4))
// UIView popup
.overlay(alignment: .top, content: {
if isUIViewShowed {
myUIView
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.transition(.move(edge: .bottom))
// doesn't help
// .ignoresSafeArea()
}
})
}
private var myUIView: some View {
MyUIView(content: {
Text("Some content here")
// doesn't help
// .edgesIgnoringSafeArea(.vertical)
// doesn't help
// .ignoresSafeArea()
})
.frame(height: 100, alignment: .center)
}
}
fileprivate struct MyUIView<Content: View> : UIViewRepresentable {
private let content: Content
init( @ViewBuilder content: @escaping ()->Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
let hostedView = UIHostingController(rootView: content).view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
// setting specific frame size doesn't help
// hostedView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
// doesn't help either
// scrollView.contentInsetAdjustmentBehavior = .never
// doesn't help either
// scrollView.insetsLayoutMarginsFromSafeArea = false
scrollView.addSubview(hostedView)
return scrollView
}
func updateUIView(_ uiView: UIScrollView, context: Context) {}
// I don't use coordinator here to keep the code as simple as possible
func makeCoordinator() -> Coordinator {}
}
What am I missing here? Is there a better approach to defining a custom UIView in SwiftUI that prevents such a behavior?