diff --git a/RGPopup.Maui/Config.cs b/RGPopup.Maui/Config.cs index b16ffdb..8a4c5ad 100644 --- a/RGPopup.Maui/Config.cs +++ b/RGPopup.Maui/Config.cs @@ -1,9 +1,14 @@ namespace RGPopup.Maui { - internal static class Config + public class Config { public const string InitializationDescriptionUrl = "https://github.com/rotorgames/Rg.Plugins.Popup/wiki/Getting-started#initialization"; - public const string MigrationV1_0_xToV1_1_xUrl = "https://github.com/rotorgames/Rg.Plugins.Popup/wiki/Migration-from-v1.0.x-to-v1.1.x"; + + public static Config Instance { get; } = new Config(); + + public bool FixKeyboardOverlap { get; set; } = true; + + public Action? BackPressHandler { get; set; } } } diff --git a/RGPopup.Maui/Effects/KeyboardOverlapFixEffect.cs b/RGPopup.Maui/Effects/KeyboardOverlapFixEffect.cs new file mode 100644 index 0000000..4ee0f86 --- /dev/null +++ b/RGPopup.Maui/Effects/KeyboardOverlapFixEffect.cs @@ -0,0 +1,5 @@ +namespace RGPopup.Maui.Effects; + +public class KeyboardOverlapFixEffect : RoutingEffect +{ +} \ No newline at end of file diff --git a/RGPopup.Maui/Extensions/AppBuilderExtensions.cs b/RGPopup.Maui/Extensions/AppBuilderExtensions.cs index 7a89930..7251edb 100644 --- a/RGPopup.Maui/Extensions/AppBuilderExtensions.cs +++ b/RGPopup.Maui/Extensions/AppBuilderExtensions.cs @@ -1,14 +1,18 @@ using Microsoft.Maui.Controls.Compatibility.Hosting; using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.LifecycleEvents; +using RGPopup.Maui.Effects; using RGPopup.Maui.Pages; namespace RGPopup.Maui.Extensions; public static class AppBuilderExtensions { - public static MauiAppBuilder UseMauiRGPopup(this MauiAppBuilder builder, Action? backPressHandler = null) + public static MauiAppBuilder UseMauiRGPopup(this MauiAppBuilder builder, Action? configUpdate = null) { + //Update config + configUpdate?.Invoke(Config.Instance); + builder //.UseMauiCompatibility() //This will cause ContentView in Popup not display on Windows platform. .ConfigureLifecycleEvents(lifecycle => @@ -16,7 +20,7 @@ public static MauiAppBuilder UseMauiRGPopup(this MauiAppBuilder builder, Action? #if ANDROID lifecycle.AddAndroid(b => { - b.OnBackPressed(activity => Droid.Popup.SendBackPressed(backPressHandler)); + b.OnBackPressed(activity => Droid.Popup.SendBackPressed(Config.Instance.BackPressHandler)); b.OnCreate((activity, state) => { Droid.Popup.Init(activity); @@ -38,7 +42,14 @@ public static MauiAppBuilder UseMauiRGPopup(this MauiAppBuilder builder, Action? b.OnLaunching((application, args) => Windows.Popup.Init()); }); #endif - }).ConfigureMauiHandlers(handlers => + }) + .ConfigureEffects(effects => + { +#if IOS + effects.Add(); +#endif + }) + .ConfigureMauiHandlers(handlers => { #if ANDROID handlers.AddHandler(typeof(PopupPage), typeof(Droid.Impl.PopupPageHandlerDroid)); diff --git a/RGPopup.Maui/Platforms/iOS/Effects/KeyboardOverlapFixPlatformEffect.cs b/RGPopup.Maui/Platforms/iOS/Effects/KeyboardOverlapFixPlatformEffect.cs new file mode 100644 index 0000000..b96795c --- /dev/null +++ b/RGPopup.Maui/Platforms/iOS/Effects/KeyboardOverlapFixPlatformEffect.cs @@ -0,0 +1,196 @@ +using Foundation; +using Microsoft.Maui.Controls.Platform; +using CoreGraphics; +using UIKit; + +namespace RGPopup.Maui.Effects; +public class KeyboardOverlapFixPlatformEffect : PlatformEffect +{ + private const double KeyboardOverlapAdjust = 0; + private static readonly bool IsIOS15 = DeviceInfo.Version.Major >= 15; + + private UIView? _responderView; + private NSObject? _keyboardShowObserver; + private NSObject? _keyboardShownObserver; + private NSObject? _keyboardHideObserver; + private Thickness? _originalPadding; + private Thickness _currentPadding; + private double _keyboardOverlap; + private nfloat _keyboardHeight; + private bool _keyboardShown; + private bool _pageShiftedUp; + private bool _pageLoaded = false; + + private ContentPage? CurrentPage => Element as ContentPage; + + protected override void OnAttached() + { + if (CurrentPage == null) return; + _pageLoaded = true; + RegisterForKeyboardNotifications(); + } + + protected override void OnDetached() + { + _pageLoaded = false; + UnregisterForKeyboardNotifications(); + } + + private void RegisterForKeyboardNotifications() + { + _keyboardShowObserver ??= + NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, OnKeyboardShow); + _keyboardShownObserver ??= + NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.DidShowNotification, OnKeyboardShown); + _keyboardHideObserver ??= + NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, OnKeyboardHide); + } + + private void UnregisterForKeyboardNotifications() + { + if (_keyboardShowObserver != null) + { + NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardShowObserver); + _keyboardShowObserver.Dispose(); + _keyboardShowObserver = null; + } + if (_keyboardShownObserver != null) + { + NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardShownObserver); + _keyboardShownObserver.Dispose(); + _keyboardShownObserver = null; + } + if (_keyboardHideObserver != null) + { + NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardHideObserver); + _keyboardHideObserver.Dispose(); + _keyboardHideObserver = null; + } + } + + private void OnKeyboardShow(NSNotification notification) + { + if (!_pageLoaded) return; + + var keyboardFrame = UIKeyboard.FrameEndFromNotification(notification); + var keyboardHeight = keyboardFrame.Height; + //Console.WriteLine($"KeyboardWillShowHeight: {keyboardHeight}"); + if (keyboardHeight <= _keyboardHeight) return; + this.CheckOverlap(keyboardHeight); + } + + private void OnKeyboardShown(NSNotification notification) + { + if (!_pageLoaded || _responderView == null || _keyboardShown) return; + + _keyboardShown = true; + var keyboardHeight = IsIOS15 ? _responderView.KeyboardLayoutGuide.LayoutFrame.Height : 0; + if (keyboardHeight <= 0) + { + var keyboardFrame = UIKeyboard.FrameEndFromNotification(notification); + keyboardHeight = keyboardFrame.Height; + } + + //Console.WriteLine($"KeyboardDidShowHeight: {keyboardHeight}"); + if (keyboardHeight <= _keyboardHeight) return; + this.CheckOverlap(keyboardHeight); + } + + private void OnKeyboardHide(NSNotification notification) + { + if (!_pageLoaded) return; + _keyboardHeight = 0; + _keyboardOverlap = 0; + _keyboardShown = false; + _responderView = null; + if (_pageShiftedUp) + { + ShiftPageDown(); + } + } + + private void CheckOverlap(nfloat keyboardHeight) + { + if (_pageShiftedUp) { return; } + var deltaHeight = keyboardHeight - _keyboardHeight; + if (_keyboardOverlap > 0 && deltaHeight > KeyboardOverlapAdjust) + { + _keyboardOverlap += deltaHeight; + _keyboardHeight = keyboardHeight; + this.ShiftPageUp(); + return; + } + + _keyboardHeight = keyboardHeight; + _responderView ??= FindFirstResponder(Control); + if (_responderView == null) return; + _keyboardOverlap = GetOverlapDistance(_responderView, Control, _keyboardHeight, false); + //Console.WriteLine($"KeyboardOverlap: {_keyboardOverlap}"); + if (_keyboardOverlap > 0) + { + ShiftPageUp(); + } + } + + private void ShiftPageUp() + { + if (CurrentPage == null || Control == null) return; + var deltaBottom = _keyboardOverlap + KeyboardOverlapAdjust; + _originalPadding ??= CurrentPage.Padding; + _currentPadding = new Thickness(_originalPadding.Value.Left, _originalPadding.Value.Top, _originalPadding.Value.Right, _originalPadding.Value.Bottom + deltaBottom); + CurrentPage.Dispatcher.Dispatch(() => + { + CurrentPage.Padding = _currentPadding; + }); + _pageShiftedUp = true; + } + + private void ShiftPageDown() + { + if (CurrentPage == null || _originalPadding == null) return; + CurrentPage.Dispatcher.Dispatch(() => + { + CurrentPage.Padding = _originalPadding.Value; + }); + _pageShiftedUp = false; + } + + public static UIView? FindFirstResponder(UIView? view) + { + if (view == null || view.IsFirstResponder) + { + return view; + } + foreach (var subView in view.Subviews) + { + var firstResponder = FindFirstResponder(subView); + if (firstResponder != null) + { + return firstResponder; + } + } + return null; + } + + public static double GetViewRelativeBottom(UIView view, UIView rootView) + { + // https://developer.apple.com/documentation/uikit/uiview/1622424-convertpoint + var viewRelativeCoordinates = rootView.ConvertPointFromView(new CGPoint(0, 0), view); + var activeViewRoundedY = Math.Round(viewRelativeCoordinates.Y, 2); + return activeViewRoundedY + view.Frame.Height; + } + + public static double GetOverlapDistance(UIView activeView, UIView rootView, nfloat keyboardHeight, bool useSafeArea) + { + double bottom = GetViewRelativeBottom(activeView, rootView); + return GetOverlapDistance(bottom, rootView, keyboardHeight, useSafeArea); + } + + private static double GetOverlapDistance(double relativeBottom, UIView rootView, nfloat keyboardHeight, bool useSafeArea) + { + var safeAreaBottom = useSafeArea ? rootView.Window.SafeAreaInsets.Bottom : 0; + var pageHeight = rootView.Frame.Height; + //Console.WriteLine($"relativeBottom:{relativeBottom}, pageHeight:{pageHeight}, keyboardHeight:{keyboardHeight}"); + return relativeBottom - (pageHeight + safeAreaBottom - keyboardHeight); + } +} \ No newline at end of file diff --git a/RGPopup.Maui/Platforms/iOS/Impl/PopupPlatformIos.cs b/RGPopup.Maui/Platforms/iOS/Impl/PopupPlatformIos.cs index 92dea35..beef454 100644 --- a/RGPopup.Maui/Platforms/iOS/Impl/PopupPlatformIos.cs +++ b/RGPopup.Maui/Platforms/iOS/Impl/PopupPlatformIos.cs @@ -1,5 +1,6 @@ using Foundation; using RGPopup.Maui.Contracts; +using RGPopup.Maui.Effects; using RGPopup.Maui.Exceptions; using RGPopup.Maui.Extensions; using RGPopup.Maui.IOS.Extensions; @@ -34,7 +35,10 @@ public event EventHandler OnInitialized public Task AddAsync(PopupPage page) { page.Parent = Application.Current?.MainPage; - + if (Config.Instance.FixKeyboardOverlap && page.Effects.All(x => x.GetType() != typeof(KeyboardOverlapFixEffect))) + { + page.Effects.Add(new KeyboardOverlapFixEffect()); + } page.DescendantRemoved += HandleChildRemoved; var keyWindow = UIApplication.SharedApplication.GetKeyWindow();