Scrolling sticker browser on a Messages App sheet causes sheet to move

As someone who learned Swift via SwiftUI, UIKit is completely alien to me, so I apologize if this is actually a very simple issue.

I have a Messages extension that includes a sticker browser within it. In this extension, the MSMessagesAppViewController hosts a SwiftUI View, which in turn hosts a UIViewRepresentable version of MSStickerBrowserView.

The whole Messages App sheet moves with an upward drag, and can switch to its expanded mode, whenever the browser is scrolled to the top (first sticker is at top left), but it doesn't budge when the browser is scrolled to the other end when it should allow the sheet to move upward with the drag.

It seems something is reversed within the gesture priority management that allows a sheet to be moved in the appropriate direction when a contained scrollview is at the appropriate end.

Things I've tried while reaching a diagnosis include:

  1. Limiting the presentation style to compact (the modal still moves, but never succeeds in changing)
  2. Adding competing highPriorityGestures in the SwiftUI view, set at various locations
  3. Inserting a rectangle with allowsHitTesting(false) beneath the browser
  4. Changing firstResponder statuses for all relevant views
  5. Changing GestureResponder priorities (there are no gesture responders in all views examined)

Things I've considered but don't have the technical skills to implement:

  1. Have the view scroll a little downwards programmatically (like what can be done via ScrollViewReader in SwiftUI), but I have no idea how this can be done via MSStickerBrowserView or UIKit in general.
  2. Maybe the MSStickerBrowserView thinks its always in the expanded state (when the sheet is expanded, the end-drags work fine). If this is the case, if there's a way to either fix this misconception (via controller's didTransition) or do away with end drags in general, the problem should go away.

Any pointers would be greatly appreciated!

Accepted Reply

One solution is to stick everything in a SwiftUI TabView (with the .page style). You should be able to use either MSStickerBrowserView or MSStickerViews wrapped in a UIViewRepresentable

struct Playground: View {
    @State private var isSheetPresented = true
    @State private var detent = PresentationDetent.fraction(1/3)
    
    var body: some View {
        Rectangle()
            .fill(Color(.systemGray5))
            .sheet(isPresented: $isSheetPresented) {
                VStack {
                    Text("ScrollView-in-Sheet Experiment")
                        .padding()
                    
                    TabView {
                        ScrollView {
                            VStack(spacing: 0) {
                                ForEach(0...100, id: \.self) { e in
                                    Rectangle()
                                        .fill(.white)
                                        .border(.gray, width: 0.3)
                                        .frame(width: 50)
                                        .overlay { Text(e.description) }
                                }
                            }
                        }
                    }
                    .tabViewStyle(.page(indexDisplayMode: .never))
                }
                .background { Color(.systemGray6) }
                .presentationDetents([.large, .fraction(1/3)], selection: $detent)
            }
    }
}

Replies

I'm glad I added the SwiftUI tag, because I replicated the exact same issue using pure SwiftUI

Unfortunately there doesn't seem to be anything published on how to fix the issue, even in pure SwiftUI--the ScrollView behaves as though the sheet is constantly expanded and transfers the drag gesture to the sheet when scrolled to the top (i.e. when first displayed), causing the user to move the sheet when intending to scroll in any direction.

I can slap a band-aid on this by having ScrollViewReader scroll down a tiny bit upon appearing, but this is far from ideal since the issue re-emerges after the user has scrolled to the top. This is complicated by the fact that you can't disable detents in your Messages App.

A workaround would be to abandon ScrollView (and MSStickerBrowserView) in favor of TabView with pages.

struct Playground: View {
    @State private var detent = PresentationDetent.fraction(1/3)
    @State private var isSheetPresented = true

    var body: some View {
        Rectangle()
            .fill(Color(.systemGray5))
            .sheet(isPresented: $isSheetPresented) {
                VStack {
                    Text("ScrollView-in-Sheet Experiment")
                        .padding()
                    
                    ScrollView {
                        ScrollViewReader { scrollProxy in
                            VStack(spacing: 0) {
                                ForEach(0...10, id: \.self) { i in
                                    Rectangle()
                                        .fill(.white)
                                        .frame(height: 50)
                                        .id(i)
                                        .overlay { Text(i.description) }
                                }
                            }
                        }
                    }
                    .frame(height: 200)
                    .padding()
                }
                .background { Color(.systemGray6) }
                .presentationDetents([.large, .fraction(1/3)], selection: $detent)
            }
    }
}

One solution is to stick everything in a SwiftUI TabView (with the .page style). You should be able to use either MSStickerBrowserView or MSStickerViews wrapped in a UIViewRepresentable

struct Playground: View {
    @State private var isSheetPresented = true
    @State private var detent = PresentationDetent.fraction(1/3)
    
    var body: some View {
        Rectangle()
            .fill(Color(.systemGray5))
            .sheet(isPresented: $isSheetPresented) {
                VStack {
                    Text("ScrollView-in-Sheet Experiment")
                        .padding()
                    
                    TabView {
                        ScrollView {
                            VStack(spacing: 0) {
                                ForEach(0...100, id: \.self) { e in
                                    Rectangle()
                                        .fill(.white)
                                        .border(.gray, width: 0.3)
                                        .frame(width: 50)
                                        .overlay { Text(e.description) }
                                }
                            }
                        }
                    }
                    .tabViewStyle(.page(indexDisplayMode: .never))
                }
                .background { Color(.systemGray6) }
                .presentationDetents([.large, .fraction(1/3)], selection: $detent)
            }
    }
}