Skip to content

Commit

Permalink
Fix The popup with Entry can not show above of the keyboard. #9
Browse files Browse the repository at this point in the history
  • Loading branch information
microspaze committed Apr 17, 2024
1 parent aeed320 commit b54df9f
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 6 deletions.
9 changes: 7 additions & 2 deletions RGPopup.Maui/Config.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
5 changes: 5 additions & 0 deletions RGPopup.Maui/Effects/KeyboardOverlapFixEffect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace RGPopup.Maui.Effects;

public class KeyboardOverlapFixEffect : RoutingEffect
{
}
17 changes: 14 additions & 3 deletions RGPopup.Maui/Extensions/AppBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
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<Config>? configUpdate = null)
{
//Update config
configUpdate?.Invoke(Config.Instance);

builder
//.UseMauiCompatibility() //This will cause ContentView in Popup not display on Windows platform.
.ConfigureLifecycleEvents(lifecycle =>
{
#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);
Expand All @@ -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<KeyboardOverlapFixEffect, KeyboardOverlapFixPlatformEffect>();
#endif
})
.ConfigureMauiHandlers(handlers =>
{
#if ANDROID
handlers.AddHandler(typeof(PopupPage), typeof(Droid.Impl.PopupPageHandlerDroid));
Expand Down
196 changes: 196 additions & 0 deletions RGPopup.Maui/Platforms/iOS/Effects/KeyboardOverlapFixPlatformEffect.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
6 changes: 5 additions & 1 deletion RGPopup.Maui/Platforms/iOS/Impl/PopupPlatformIos.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit b54df9f

Please sign in to comment.