@@ -11,7 +11,7 @@ namespace Syncfusion.Maui.Toolkit.BottomSheet
1111 /// <summary>
1212 /// Represents the <see cref="BottomSheetBorder"/> that defines the layout of bottom sheet.
1313 /// </summary>
14- internal class BottomSheetBorder : SfBorder , ITouchListener
14+ internal partial class BottomSheetBorder : SfBorder , ITouchListener
1515 {
1616 #region Fields
1717
@@ -26,14 +26,34 @@ internal class BottomSheetBorder : SfBorder, ITouchListener
2626 UIView ? _scrollableView ;
2727
2828 /// <summary>
29- /// Indicates whether pressed occurs inside a scrollable view .
29+ /// Determines whether the touch should be processed .
3030 /// </summary>
31- bool _isPressed = false ;
31+ bool _canProcessTouch = true ;
3232
3333 /// <summary>
34- /// Determines whether the touch should be processed .
34+ /// Represents a scrollable view under the finger (UIScrollView/UICollectionView) .
3535 /// </summary>
36- bool _canProcessTouch = true ;
36+ UIScrollView ? _iosScrollView ;
37+
38+ /// <summary>
39+ /// Determines whether the touch started in a scrollable view.
40+ /// </summary>
41+ bool _iosInsideScrollable ;
42+
43+ /// <summary>
44+ /// Determines whether we have handed off touch to the bottom sheet.
45+ /// </summary>
46+ bool _iosHandoff ;
47+
48+ /// <summary>
49+ /// Stores the last Y position.
50+ /// </summary>
51+ double _iosLastY ;
52+
53+ /// <summary>
54+ /// A small epsilon value to avoid jitter at scroll edges.
55+ /// </summary>
56+ nfloat _epsilon = 1.0f ;
3757
3858#endif
3959
@@ -94,6 +114,47 @@ bool IsScrollableView(UIView? view)
94114 return false ;
95115 }
96116
117+ /// <summary>
118+ /// Determines if the inner UIScrollView can scroll in the direction of the finger movement.
119+ /// </summary>
120+ /// <param name="sv">The scrollable view to check.</param>
121+ /// <param name="dy">The scroll direction.</param>
122+ /// <returns>True if inner view is scrollable, otherwise false.</returns>
123+ bool CanInnerScroll ( UIScrollView sv , double dy )
124+ {
125+ if ( sv is not null )
126+ {
127+ // Insets (AdjustedContentInset is correct with safe area / content inset)
128+ nfloat topInset = sv . AdjustedContentInset . Top ;
129+ nfloat bottomInset = sv . AdjustedContentInset . Bottom ;
130+
131+ // Effective scrollable range
132+ nfloat visibleHeight = sv . Bounds . Height ;
133+ nfloat contentHeight = sv . ContentSize . Height ;
134+ // If content is shorter than the viewport, there's nothing to scroll.
135+ if ( contentHeight <= visibleHeight - ( topInset + bottomInset ) )
136+ {
137+ return false ;
138+ }
139+
140+ nfloat minOffsetY = - topInset ;
141+ nfloat maxOffsetY = ( nfloat ) Math . Max ( 0 , contentHeight - visibleHeight + bottomInset ) ;
142+ nfloat y = sv . ContentOffset . Y ;
143+
144+ if ( dy < 0 ) // finger up => scroll down
145+ {
146+ return y < ( maxOffsetY - _epsilon ) ;
147+ }
148+
149+ if ( dy > 0 ) // finger down => scroll up
150+ {
151+ return y > ( minOffsetY + _epsilon ) ;
152+ }
153+ }
154+
155+ return false ;
156+ }
157+
97158#endif
98159
99160 #endregion
@@ -125,35 +186,77 @@ public void OnTouch(Internals.PointerEventArgs e)
125186
126187 if ( IsScrollableView ( hitView ) )
127188 {
128- _canProcessTouch = false ; // Only disable bottom sheet swipe if touch is inside scrollable view
129- _isPressed = true ;
130- return ;
131- }
132- else
133- {
134- _canProcessTouch = true ; // Allow bottom sheet swipe
189+ _iosScrollView = _scrollableView as UIScrollView ;
190+ _iosInsideScrollable = _iosScrollView is not null ;
191+ _iosHandoff = false ;
192+ _iosLastY = e . TouchPoint . Y ;
193+
194+ if ( _iosInsideScrollable )
195+ {
196+ // Start inside a scrollable: let it consume initially.
197+ // DO NOT forward Pressed to the sheet yet.
198+ return ;
199+ }
135200 }
136201 }
137-
138202 }
139203 else if ( e . Action == PointerActions . Moved )
140204 {
141- // When moved is called multiple times, this flag helps us prevent the bottom sheet scrolling
142- if ( _isPressed )
143- {
144- return ;
145- }
205+ if ( _iosInsideScrollable && _iosScrollView is not null )
206+ {
207+ double dy = e . TouchPoint . Y - _iosLastY ;
208+
209+ // While inner can scroll in this direction, don't route to sheet.
210+ if ( CanInnerScroll ( _iosScrollView , dy ) )
211+ {
212+ _iosLastY = e . TouchPoint . Y ;
213+ return ; // list keeps consuming
214+ }
215+
216+ // Edge reached => hand off to sheet once
217+ if ( ! _iosHandoff && _bottomSheetRef ? . TryGetTarget ( out var bottomSheetPressed ) == true )
218+ {
219+ bottomSheetPressed . OnHandleTouch ( PointerActions . Pressed , e . TouchPoint ) ;
220+ _iosHandoff = true ;
221+
222+ // Route this first move to the sheet as well
223+ bottomSheetPressed . OnHandleTouch ( PointerActions . Moved , e . TouchPoint ) ;
224+ _iosLastY = e . TouchPoint . Y ;
225+ return ;
226+ }
227+
228+ // Already handed off => keep sending moves to sheet here; skip common forward
229+ if ( _iosHandoff && _bottomSheetRef ? . TryGetTarget ( out var bottomSheetMoved ) == true )
230+ {
231+ bottomSheetMoved . OnHandleTouch ( PointerActions . Moved , e . TouchPoint ) ;
232+ _iosLastY = e . TouchPoint . Y ;
233+ return ;
234+ }
235+
236+ _iosLastY = e . TouchPoint . Y ;
237+ return ;
238+ }
146239 }
147240 else if ( e . Action == PointerActions . Released || e . Action == PointerActions . Exited || e . Action == PointerActions . Cancelled )
148241 {
149- _canProcessTouch = true ;
150-
151- // Early return to avoid bottom sheet position update after the scroll occured in a scrollable view
152- if ( _isPressed )
153- {
154- _isPressed = false ;
155- return ;
156- }
242+ // If we started in a scrollable and never handed off, do not forward release to sheet
243+ if ( _iosInsideScrollable && ! _iosHandoff )
244+ {
245+ _iosInsideScrollable = false ;
246+ _iosScrollView = null ;
247+ _iosHandoff = false ;
248+ return ;
249+ }
250+
251+ // If we did hand off, forward release here and reset; skip common forward
252+ if ( _iosHandoff && _bottomSheetRef ? . TryGetTarget ( out var bottomSheetReleased ) == true )
253+ {
254+ bottomSheetReleased . OnHandleTouch ( PointerActions . Released , e . TouchPoint ) ;
255+ _iosInsideScrollable = false ;
256+ _iosScrollView = null ;
257+ _iosHandoff = false ;
258+ return ;
259+ }
157260 }
158261#endif
159262
0 commit comments