@@ -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