浏览代码

Working on iOS IME

Nikita Tsukanov 3 年之前
父节点
当前提交
006fe37d0d

+ 5 - 0
samples/MobileSandbox.iOS/AppDelegate.cs

@@ -3,6 +3,7 @@ using UIKit;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.iOS;
+using Avalonia.Logging;
 using Avalonia.Media;
 
 namespace MobileSandbox
@@ -13,5 +14,9 @@ namespace MobileSandbox
     [Register("AppDelegate")]
     public partial class AppDelegate : AvaloniaAppDelegate<App>
     {
+        protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
+        {
+            return builder.LogToTrace(LogEventLevel.Debug, "IOSIME");
+        }
     }
 }

+ 1 - 1
samples/MobileSandbox.iOS/Info.plist

@@ -13,7 +13,7 @@
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
 	<key>MinimumOSVersion</key>
-	<string>10.0</string>
+	<string>13.0</string>
 	<key>UIDeviceFamily</key>
 	<array>
 		<integer>1</integer>

+ 1 - 1
samples/MobileSandbox.iOS/MobileSandbox.iOS.csproj

@@ -3,7 +3,7 @@
     <OutputType>Exe</OutputType>
     <ProvisioningType>manual</ProvisioningType>
     <TargetFramework>net6.0-ios</TargetFramework>
-    <SupportedOSPlatformVersion>10.0</SupportedOSPlatformVersion>
+    <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
     <!-- temporal workaround for our GL interface backend -->
     <UseInterpreter>True</UseInterpreter>
     <RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>

+ 2 - 0
src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs

@@ -49,6 +49,8 @@ namespace Avalonia.Input.TextInput
         /// Returns the text before the cursor. Must return a non-empty string if cursor is not at the end of the text entry
         /// </summary>
         string? TextAfterCursor { get; }
+
+        public void SelectInSurroundingText(int start, int end);
     }
 
     public struct TextInputMethodSurroundingText

+ 0 - 4
src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs

@@ -5,10 +5,6 @@ namespace Avalonia.Input.TextInput
     [Unstable]
     public interface ITextInputMethodImpl
     {
-        ITextInputMethodClient? Client { get; }
-
-        bool IsActive { get; }
-
         void SetClient(ITextInputMethodClient? client);
         void SetCursorRect(Rect rect);
         void SetOptions(TextInputOptions options);

+ 32 - 2
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@@ -51,21 +51,51 @@ namespace Avalonia.Controls
 
         public bool SupportsSurroundingText => true;
 
-        public event EventHandler? SurroundingTextChanged { add { } remove { } }
+        public event EventHandler? SurroundingTextChanged;
 
-        public TextInputMethodSurroundingText SurroundingText => new TextInputMethodSurroundingText { Text =  _presenter?.Text ?? "", CursorOffset = _presenter?.CaretIndex ?? 0, AnchorOffset = _presenter?.SelectionStart ?? 0};
+        public TextInputMethodSurroundingText SurroundingText => new()
+        {
+            Text = _presenter?.Text ?? "",
+            CursorOffset = _presenter?.CaretIndex ?? 0,
+            AnchorOffset = _presenter?.SelectionStart ?? 0
+        };
 
         public string? TextBeforeCursor => null;
         
         public string? TextAfterCursor => null;
+        public void SelectInSurroundingText(int start, int end)
+        {
+            if(_parent == null)
+                return;
+            // TODO: Account for the offset
+            _parent.SelectionStart = start;
+            _parent.SelectionEnd = end;
+        }
 
         private void OnCaretBoundsChanged(object? sender, EventArgs e) => CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
+        
+        private void OnTextBoxPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Property == TextBox.TextProperty || e.Property == TextBox.SelectionStartProperty ||
+                e.Property == TextBox.SelectionEndProperty)
+                SurroundingTextChanged?.Invoke(this, EventArgs.Empty);
+        }
 
 
         public void SetPresenter(TextPresenter? presenter, TextBox? parent)
         {
+            if (_parent != null)
+            {
+                _parent.PropertyChanged -= OnTextBoxPropertyChanged;
+            }
+            
             _parent = parent;
 
+            if (_parent != null)
+            {
+                _parent.PropertyChanged += OnTextBoxPropertyChanged;
+            }
+
             if (_presenter != null)
             {
                 _presenter.CaretBoundsChanged -= OnCaretBoundsChanged;

+ 1 - 1
src/iOS/Avalonia.iOS/Avalonia.iOS.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>net6.0-ios</TargetFramework>
-    <SupportedOSPlatformVersion>10.0</SupportedOSPlatformVersion>
+    <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
     <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
   </PropertyGroup>
   <ItemGroup>

+ 2 - 2
src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs

@@ -20,7 +20,7 @@ namespace Avalonia.iOS
             }
         }
 
-        protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseiOS();
+        protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
         
         [Export("window")]
         public UIWindow Window { get; set; }
@@ -28,7 +28,7 @@ namespace Avalonia.iOS
         [Export("application:didFinishLaunchingWithOptions:")]
         public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
         {
-            var builder = AppBuilder.Configure<TApp>();
+            var builder = AppBuilder.Configure<TApp>().UseiOS();
             CustomizeAppBuilder(builder);
 
             var lifetime = new SingleViewLifetime();

+ 29 - 475
src/iOS/Avalonia.iOS/AvaloniaView.Text.cs

@@ -1,500 +1,54 @@
-using System;
-using System.Runtime.InteropServices;
-using Foundation;
-using ObjCRuntime;
+#nullable enable
 using Avalonia.Input.TextInput;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using CoreGraphics;
+using JetBrains.Annotations;
 using UIKit;
 
 namespace Avalonia.iOS;
 
-#nullable enable
-
-[Adopts("UITextInput")]
-[Adopts("UITextInputTraits")]
-[Adopts("UIKeyInput")]
-public class AvaloniaResponder : UIResponder, IUITextInput
+public partial class AvaloniaView
 {
-    private class AvaloniaTextRange : UITextRange
-    {
-        private readonly NSRange _range;
-
-        public AvaloniaTextRange(NSRange range)
-        {
-            _range = range;
-        }
-
-        public override AvaloniaTextPosition Start => new AvaloniaTextPosition((int)_range.Location);
-
-        public override AvaloniaTextPosition End => new AvaloniaTextPosition((int)(_range.Location + _range.Length));
-
-        public NSRange Range => _range;
-
-        public override bool IsEmpty => _range.Length == 0;
-    }
-
-    private class AvaloniaTextPosition : UITextPosition
-    {
-        public AvaloniaTextPosition(nint index)
-        {
-            Index = index;
-        }
-
-        public nint Index { get; }
-        
-        //[Export("inputDelegate")]
-        //public UITextInputDelegate InputDelegate { get; set; }
-
-        [Export("foo")]
-        public void Foo()
-        {
-            
-        }
-    }
-
-    private UIResponder _nextResponder;
-
-    public AvaloniaResponder(AvaloniaView view, ITextInputMethodClient client)
-    {
-        _nextResponder = view;
-        _client = client;
-        _tokenizer = new UITextInputStringTokenizer(this);
-        
-    }
-
-    public override UIResponder NextResponder => _nextResponder;
-
-    public override bool CanPerform(Selector action, NSObject? withSender)
-    {
-        return base.CanPerform(action, withSender);
-    }
-
-    private string _markedText = "";
-    private ITextInputMethodClient? _client;
-    private NSDictionary? _markedTextStyle;
-    private readonly UITextPosition _beginningOfDocument = new AvaloniaTextPosition(0);
-    private readonly UITextInputStringTokenizer _tokenizer;
-
-    public ITextInputMethodClient? Client => _client;
-
-    public bool IsActive => _client != null;
-
-    public override bool CanResignFirstResponder => true;
-
-    public override bool CanBecomeFirstResponder => true;
-
-    public override UIEditingInteractionConfiguration EditingInteractionConfiguration =>
-        UIEditingInteractionConfiguration.Default;
-
-    public override NSString TextInputContextIdentifier => new NSString("Test");
-
-    public override UITextInputMode TextInputMode => UITextInputMode.CurrentInputMode;
-
-    [DllImport("/usr/lib/libobjc.dylib")]
-    extern static void objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg);
-
-    private static readonly IntPtr SelectionWillChange = Selector.GetHandle("selectionWillChange:");
-    private static readonly IntPtr SelectionDidChange = Selector.GetHandle("selectionDidChange:");
-    private static readonly IntPtr TextWillChange = Selector.GetHandle("textWillChange:");
-    private static readonly IntPtr TextDidChange = Selector.GetHandle("textDidChange:");
-        
-    private void ClientOnCursorRectangleChanged(object? sender, EventArgs e)
-    {
-        objc_msgSend(WeakInputDelegate.Handle.Handle, SelectionWillChange, this.Handle.Handle);
-        objc_msgSend(WeakInputDelegate.Handle.Handle, SelectionDidChange, this.Handle.Handle);
-    }
-
-    private void ClientOnSurroundingTextChanged(object? sender, EventArgs e)
-    {
-        objc_msgSend(WeakInputDelegate.Handle.Handle, TextWillChange, Handle.Handle);
-        objc_msgSend(WeakInputDelegate.Handle.Handle, TextDidChange, Handle.Handle);
-    }
-
-    // Traits (Optional)
-    [Export("autocapitalizationType")] public UITextAutocapitalizationType AutocapitalizationType { get; private set; }
-    
-    [Export("autocorrectionType")] public UITextAutocorrectionType AutocorrectionType { get; private set; }
-    [Export("keyboardType")] public UIKeyboardType KeyboardType { get; private set; } = UIKeyboardType.Default;
-
-    [Export("keyboardAppearance")]
-    public UIKeyboardAppearance KeyboardAppearance { get; private set; } = UIKeyboardAppearance.Default;
-    
-    [Export("returnKeyType")] public UIReturnKeyType ReturnKeyType { get; set; }
-    
-    [Export("enablesReturnKeyAutomatically")] public bool EnablesReturnKeyAutomatically { get; set; }
-    [Export("isSecureTextEntry")] public bool IsSecureEntry { get; private set; }
-
-    [Export("spellCheckingType")]
-    public UITextSpellCheckingType SpellCheckingType { get; set; } = UITextSpellCheckingType.Default;
-    
-    [Export("textContentType")]
-    public NSString TextContentType { get; set; }
-  
-    [Export("smartQuotesType")]
-    public UITextSmartQuotesType SmartQuotesType { get; set; } = UITextSmartQuotesType.Default;
-    
-    [Export("smartDashesType")]
-    public UITextSmartDashesType SmartDashesType { get; set; } = UITextSmartDashesType.Default;
-  
-    [Export("smartInsertDeleteType")]
-    public UITextSmartInsertDeleteType SmartInsertDeleteType { get; set; } = UITextSmartInsertDeleteType.Default;
-  
-    [Export("passwordRules")]
-    public UITextInputPasswordRules PasswordRules { get; set; }
-
-    void IUIKeyInput.InsertText(string text)
-    {
-        if (_client == null)
-        {
-            return;
-        }
-
-        if (text == "\n")
-        {
-            // emulate return key released.
-        }
-
-        switch (ReturnKeyType)
-        {
-            case UIReturnKeyType.Done:
-            case UIReturnKeyType.Search:
-            case UIReturnKeyType.Go:
-            case UIReturnKeyType.Send:
-                ResignFirstResponder();
-                return;
-        }
-
-        // TODO replace this with _client.SetCommitText?
-        if (KeyboardDevice.Instance is { })
-        {
-            /*_topLevelImpl.Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice.Instance,
-                0, InputRoot, text));*/
-        }
-    }
-
-    void IUIKeyInput.DeleteBackward()
-    {
-        if (KeyboardDevice.Instance is { })
-        {
-            // TODO: pass this through IME infrastructure instead of emulating a backspace press
-            /*_topLevelImpl.Input?.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance,
-                0, InputRoot, RawKeyEventType.KeyDown, Key.Back, RawInputModifiers.None));
-
-            _topLevelImpl.Input?.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance,
-                0, InputRoot, RawKeyEventType.KeyUp, Key.Back, RawInputModifiers.None));*/
-        }
-    }
-
-    bool IUIKeyInput.HasText => true;
-
-    string IUITextInput.TextInRange(UITextRange range)
-    {
-        var text = _client.SurroundingText.Text;
-
-        if (!string.IsNullOrWhiteSpace(_markedText))
-        {
-            // todo check this combining _marked text with surrounding text.
-            int cursorPos = _client.SurroundingText.CursorOffset;
-            text = text[.. cursorPos] + _markedText + text[cursorPos ..];
-        }
-
-        var start = (int)(range.Start as AvaloniaTextPosition).Index;
-        var end = (int)(range.End as AvaloniaTextPosition).Index;
-
-        var result = text[start .. end];
-
-        return result;
-    }
-
-    void IUITextInput.ReplaceText(UITextRange range, string text)
-    {
-        ((IUITextInput)this).SelectedTextRange = range;
-
-        // todo _client.SetCommitText(text);
-        if (KeyboardDevice.Instance is { })
-        {
-            /*_topLevelImpl.Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice.Instance,
-                0, InputRoot, text));*/
-        }
-    }
-
-    void IUITextInput.SetMarkedText(string markedText, NSRange selectedRange)
-    {
-        _markedText = markedText;
-
-        // todo check this... seems correct
-        _client.SetPreeditText(markedText);
-    }
-
-    void IUITextInput.UnmarkText()
-    {
-        if (string.IsNullOrWhiteSpace(_markedText))
-            return;
-
-        // todo _client.CommitString (_markedText);
-
-        _markedText = "";
-    }
-
-    public UITextRange GetTextRange(UITextPosition fromPosition, UITextPosition toPosition)
-    {
-        if (fromPosition is AvaloniaTextPosition f && toPosition is AvaloniaTextPosition t)
-        {
-            // todo check calculation.
-            var range = new NSRange(Math.Min(f.Index, t.Index), Math.Abs(t.Index - f.Index));
-            return new AvaloniaTextRange(range);
-        }
-
-        return null;
-        //throw new Exception();
-    }
-
-    UITextPosition IUITextInput.GetPosition(UITextPosition fromPosition, nint offset)
-    {
-        if (fromPosition is AvaloniaTextPosition indexedPosition)
-        {
-            var end = indexedPosition.Index + offset;
-            // Verify position is valid in document.
-            //if (end > self.text.length || end < 0) {
-            //    return nil;
-            //}
-
-            return new AvaloniaTextPosition(end);
-        }
-
-        return null;
-    }
-
-    UITextPosition IUITextInput.GetPosition(UITextPosition fromPosition, UITextLayoutDirection inDirection, nint offset)
-    {
-        if (fromPosition is AvaloniaTextPosition f)
-        {
-            var newPosition = f.Index;
-
-            switch (inDirection)
-            {
-                case UITextLayoutDirection.Left:
-                    newPosition -= offset;
-                    break;
-
-                case UITextLayoutDirection.Right:
-                    newPosition += offset;
-                    break;
-            }
-
-            if (newPosition < 0)
-            {
-                newPosition = 0;
-            }
-
-            if (newPosition > _client.SurroundingText.Text.Length)
-            {
-                newPosition = _client.SurroundingText.Text.Length;
-            }
-
-            return new AvaloniaTextPosition(newPosition);
-        }
-
-        throw new Exception();
-    }
-
-    NSComparisonResult IUITextInput.ComparePosition(UITextPosition first, UITextPosition second)
-    {
-        if (first is AvaloniaTextPosition f && second is AvaloniaTextPosition s)
-        {
-            if (f.Index < s.Index)
-                return NSComparisonResult.Ascending;
-
-            if (f.Index > s.Index)
-                return NSComparisonResult.Descending;
-
-            return NSComparisonResult.Same;
-        }
-
-        throw new Exception();
-    }
-
-    nint IUITextInput.GetOffsetFromPosition(UITextPosition fromPosition, UITextPosition toPosition)
-    {
-        if (fromPosition is AvaloniaTextPosition f && toPosition is AvaloniaTextPosition t)
-        {
-            return t.Index - f.Index;
-        }
-
-        throw new Exception();
-    }
-
-    UITextPosition IUITextInput.GetPositionWithinRange(UITextRange range, UITextLayoutDirection direction)
-    {
-        if (range is AvaloniaTextRange indexedRange)
-        {
-            nint position = 0;
-            
-            switch (direction)
-            {
-                case UITextLayoutDirection.Up:
-                case UITextLayoutDirection.Left:
-                    position = indexedRange.Range.Location;
-                    break;
-                
-                case UITextLayoutDirection.Down:
-                case UITextLayoutDirection.Right:
-                    position = indexedRange.Range.Location + indexedRange.Range.Length;
-                    break;
-            }
-
-            return new AvaloniaTextPosition(position);
-        }
-
-        throw new Exception();
-    }
-
-    UITextRange IUITextInput.GetCharacterRange(UITextPosition byExtendingPosition, UITextLayoutDirection direction)
-    {
-        if (byExtendingPosition is AvaloniaTextPosition pos)
-        {
-            NSRange result = new NSRange();
-            
-            switch (direction)
-            {
-                case UITextLayoutDirection.Up:
-                case UITextLayoutDirection.Left:
-                    result = new NSRange(pos.Index - 1, 1);
-                    break;
-
-                case UITextLayoutDirection.Right:
-                case UITextLayoutDirection.Down:
-                    result = new NSRange(pos.Index, 1);
-                    break;
-            }
-
-            return new AvaloniaTextRange(result);
-        }
+    private const string ImeLog = "IOSIME";
+    private Rect _cursorRect;
+    private TextInputOptions? _options;
 
-        throw new Exception();
-    }
-
-    NSWritingDirection IUITextInput.GetBaseWritingDirection(UITextPosition forPosition,
-        UITextStorageDirection direction)
-    {
-        return NSWritingDirection.LeftToRight;
-
-        // todo query and retyrn RTL.
-    }
-
-    void IUITextInput.SetBaseWritingDirectionforRange(NSWritingDirection writingDirection, UITextRange range)
-    {
-        // todo ? ignore?
-    }
-
-    CGRect IUITextInput.GetFirstRectForRange(UITextRange range)
-    {
-        if (_client == null)
-            return CGRect.Empty;
-
-        if (!string.IsNullOrWhiteSpace(_markedText))
-        {
-            return CGRect.Empty;
-        }
-
-        if (range is AvaloniaTextRange r)
-        {
-            // todo add ime apis to get cursor rect.
-            throw new NotImplementedException();
-        }
-
-        throw new Exception();
-    }
-
-    CGRect IUITextInput.GetCaretRectForPosition(UITextPosition? position)
-    {
-        var rect = _client.CursorRectangle;
-
-        return new CGRect(rect.X, rect.Y, rect.Width, rect.Height);
-    }
-
-    UITextPosition IUITextInput.GetClosestPositionToPoint(CGPoint point)
-    {
-        // TODO HitTest text? 
-        throw new System.NotImplementedException();
-    }
-
-    UITextPosition IUITextInput.GetClosestPositionToPoint(CGPoint point, UITextRange withinRange)
-    {
-        // TODO HitTest text? 
-        throw new System.NotImplementedException();
-    }
-
-    UITextRange IUITextInput.GetCharacterRangeAtPoint(CGPoint point)
-    {
-        return null;
-    }
-
-    UITextSelectionRect[] IUITextInput.GetSelectionRects(UITextRange range)
+    private static UIResponder? CurrentAvaloniaResponder { get; set; }
+    public override bool BecomeFirstResponder()
     {
-        return null;
+        var res = base.BecomeFirstResponder();
+        if (res)
+            CurrentAvaloniaResponder = this;
+        return res;
     }
 
-    [Export("textStylingAtPosition:inDirection:")]
-    public NSDictionary GetTextStylingAtPosition(UITextPosition position, UITextStorageDirection direction)
+    public override bool ResignFirstResponder()
     {
-        return null;
+        var res = base.ResignFirstResponder();
+        if (res && ReferenceEquals(CurrentAvaloniaResponder, this))
+            CurrentAvaloniaResponder = null;
+        return res;
     }
 
-    UITextRange? IUITextInput.SelectedTextRange
-    {
-        get
-        {
-            return new AvaloniaTextRange(new NSRange(
-                (nint)Math.Min(_client.SurroundingText.CursorOffset, _client.SurroundingText.AnchorOffset),
-                (nint)Math.Abs(_client.SurroundingText.CursorOffset - _client.SurroundingText.AnchorOffset)));
-        }
-        set
-        {
-            throw new NotImplementedException();
-        }
-    }
+    private bool IsDrivingText => CurrentAvaloniaResponder is TextInputResponder t && ReferenceEquals(t.NextResponder, this);
 
-    NSDictionary? IUITextInput.MarkedTextStyle
+    void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
     {
-        get => _markedTextStyle;
-        set => _markedTextStyle = value;
-    }
-
-    UITextPosition IUITextInput.BeginningOfDocument => _beginningOfDocument;
+        _client = client;
+        if (_client == null && IsDrivingText)
+            BecomeFirstResponder();            
 
-    UITextPosition IUITextInput.EndOfDocument
-    {
-        get
+        if (_client is { })
         {
-            return new AvaloniaTextPosition(_client.SurroundingText.Text.Length + _markedText.Length);
+            new TextInputResponder(this, _client).BecomeFirstResponder();
         }
     }
 
+    void ITextInputMethodImpl.SetCursorRect(Rect rect) => _cursorRect = rect;
 
-    public NSObject? WeakInputDelegate
-    {
-        get;
-        set;
-    }
-
-    NSObject IUITextInput.WeakTokenizer => _tokenizer;
+    void ITextInputMethodImpl.SetOptions(TextInputOptions options) => _options = options;
 
-    UITextRange IUITextInput.MarkedTextRange
+    void ITextInputMethodImpl.Reset()
     {
-        get
-        {
-            if (_client == null || string.IsNullOrWhiteSpace(_markedText))
-            {
-                return null;
-            }
-
-            // todo
-            return new AvaloniaTextRange(new NSRange(
-                (nint)Math.Min(_client.SurroundingText.CursorOffset, _client.SurroundingText.AnchorOffset),
-                (nint)Math.Abs(_client.SurroundingText.CursorOffset - _client.SurroundingText.AnchorOffset)));            
-        }
+        if (IsDrivingText)
+            BecomeFirstResponder();
     }
 }

+ 0 - 113
src/iOS/Avalonia.iOS/AvaloniaView.cs

@@ -26,7 +26,6 @@ namespace Avalonia.iOS
         private EmbeddableControlRoot _topLevel;
         private TouchHandler _touches;
         private ITextInputMethodClient _client;
-        private bool _isActive;
 
         public AvaloniaView()
         {
@@ -155,117 +154,5 @@ namespace Avalonia.iOS
             get => (Control)_topLevel.Content;
             set => _topLevel.Content = value;
         }
-
-        ITextInputMethodClient ITextInputMethodImpl.Client => _client;
-
-        bool ITextInputMethodImpl.IsActive => _isActive;
-
-        private AvaloniaResponder _currentResponder;
-
-        void ITextInputMethodImpl.SetClient(ITextInputMethodClient client)
-        {
-            if (_client != null)
-            {
-                //_client.CursorRectangleChanged -= ClientOnCursorRectangleChanged;
-            }
-
-            _client = client;
-
-            if (_client is { })
-            {
-                if (_currentResponder != null && _currentResponder.IsFirstResponder)
-                {
-                    _currentResponder.ResignFirstResponder();
-                    _currentResponder = null;
-                }
-
-                _currentResponder = new AvaloniaResponder(this, _client);
-                //_client.CursorRectangleChanged += ClientOnCursorRectangleChanged;
-
-                //_client.SurroundingTextChanged += ClientOnSurroundingTextChanged;
-                _currentResponder.BecomeFirstResponder();
-
-                var x = _currentResponder.IsFirstResponder;
-            }
-            else
-            {
-                _currentResponder.ResignFirstResponder();
-            }
-        }
-
-        void ITextInputMethodImpl.SetCursorRect(Rect rect)
-        {
-            // maybe this will be cursor / selection rect?
-        }
-
-        void ITextInputMethodImpl.SetOptions(TextInputOptions options)
-        {
-            /*IsSecureEntry = false;
-
-            switch (options.ContentType)
-            {
-                case TextInputContentType.Normal:
-                    KeyboardType = UIKeyboardType.Default;
-                    break;
-
-                case TextInputContentType.Alpha:
-                    KeyboardType = UIKeyboardType.AsciiCapable;
-                    break;
-
-                case TextInputContentType.Digits:
-                    KeyboardType = UIKeyboardType.PhonePad;
-                    break;
-
-                case TextInputContentType.Pin:
-                    KeyboardType = UIKeyboardType.NumberPad;
-                    IsSecureEntry = true;
-                    break;
-
-                case TextInputContentType.Number:
-                    KeyboardType = UIKeyboardType.PhonePad;
-                    break;
-
-                case TextInputContentType.Email:
-                    KeyboardType = UIKeyboardType.EmailAddress;
-                    break;
-
-                case TextInputContentType.Url:
-                    KeyboardType = UIKeyboardType.Url;
-                    break;
-
-                case TextInputContentType.Name:
-                    KeyboardType = UIKeyboardType.NamePhonePad;
-                    break;
-
-                case TextInputContentType.Password:
-                    KeyboardType = UIKeyboardType.Default;
-                    IsSecureEntry = true;
-                    break;
-
-                case TextInputContentType.Social:
-                    KeyboardType = UIKeyboardType.Twitter;
-                    break;
-
-                case TextInputContentType.Search:
-                    KeyboardType = UIKeyboardType.WebSearch;
-                    break;
-            }
-
-            if (options.IsSensitive)
-            {
-                IsSecureEntry = true;
-            }
-
-            ReturnKeyType = (UIReturnKeyType)options.ReturnKeyType;
-            AutocorrectionType = UITextAutocorrectionType.Yes;
-            SpellCheckingType = UITextSpellCheckingType.Yes;
-            KeyboardAppearance = UIKeyboardAppearance.Alert;*/
-        }
-
-
-        void ITextInputMethodImpl.Reset()
-        {
-            _currentResponder?.ResignFirstResponder();
-        }
     }
 }

+ 40 - 0
src/iOS/Avalonia.iOS/CombinedSpan3.cs

@@ -0,0 +1,40 @@
+using System;
+using Avalonia.Controls.Documents;
+
+namespace Avalonia.iOS;
+
+internal ref struct CombinedSpan3<T>
+{
+    public ReadOnlySpan<T> Span1, Span2, Span3;
+
+    public CombinedSpan3(ReadOnlySpan<T> span1, ReadOnlySpan<T> span2, ReadOnlySpan<T> span3)
+    {
+        Span1 = span1;
+        Span2 = span2;
+        Span3 = span3;
+    }
+
+    public int Length => Span1.Length + Span2.Length + Span3.Length;
+
+    void CopyFromSpan(ReadOnlySpan<T> from, ref int offset, ref Span<T> to)
+    {
+        if(to.Length == 0)
+            return;
+        if (offset < from.Length)
+        {
+            var copyNow = Math.Min(from.Length - offset, to.Length);
+            from.Slice(offset, copyNow).CopyTo(to);
+            to = to.Slice(copyNow);
+            offset = 0;
+        }
+        else
+            offset -= from.Length;
+    }
+    
+    public void CopyTo(Span<T> to, int offset)
+    {
+        CopyFromSpan(Span1, ref offset, ref to);
+        CopyFromSpan(Span2, ref offset, ref to);
+        CopyFromSpan(Span3, ref offset, ref to);
+    }
+}

+ 71 - 0
src/iOS/Avalonia.iOS/TextInputResponder.Properties.cs

@@ -0,0 +1,71 @@
+#nullable enable
+using Avalonia.Input.TextInput;
+using Foundation;
+using UIKit;
+
+namespace Avalonia.iOS;
+
+partial class AvaloniaView
+{
+    partial class TextInputResponder
+    {
+        [Export("autocapitalizationType")]
+        public UITextAutocapitalizationType AutocapitalizationType { get; private set; }
+
+        [Export("autocorrectionType")]
+        public UITextAutocorrectionType AutocorrectionType => UITextAutocorrectionType.Yes;
+
+        [Export("keyboardType")]
+        public UIKeyboardType KeyboardType =>
+            _view._options == null ?
+                UIKeyboardType.Default :
+                _view._options.ContentType switch
+                {
+                    TextInputContentType.Alpha => UIKeyboardType.AsciiCapable,
+                    TextInputContentType.Digits or TextInputContentType.Number => UIKeyboardType.NumberPad,
+                    TextInputContentType.Pin => UIKeyboardType.NumberPad,
+                    TextInputContentType.Email => UIKeyboardType.EmailAddress,
+                    TextInputContentType.Url => UIKeyboardType.Url,
+                    TextInputContentType.Name => UIKeyboardType.NamePhonePad,
+                    TextInputContentType.Social => UIKeyboardType.Twitter,
+                    TextInputContentType.Search => UIKeyboardType.WebSearch,
+                    _ => UIKeyboardType.Default
+                };
+
+        [Export("keyboardAppearance")]
+        public UIKeyboardAppearance KeyboardAppearance => UIKeyboardAppearance.Alert;
+
+        [Export("returnKeyType")] public UIReturnKeyType ReturnKeyType => UIReturnKeyType.Default;
+
+        [Export("enablesReturnKeyAutomatically")]
+        public bool EnablesReturnKeyAutomatically { get; set; }
+
+        [Export("isSecureTextEntry")] public bool IsSecureEntry =>
+            _view._options?.ContentType is TextInputContentType.Password or TextInputContentType.Pin 
+            || (_view._options?.IsSensitive ?? false);
+
+        [Export("spellCheckingType")] public UITextSpellCheckingType SpellCheckingType => UITextSpellCheckingType.Yes;
+
+        [Export("textContentType")] public NSString TextContentType { get; set; } = new NSString("text/plain");
+
+        [Export("smartQuotesType")]
+        public UITextSmartQuotesType SmartQuotesType { get; set; } = UITextSmartQuotesType.Default;
+
+        [Export("smartDashesType")]
+        public UITextSmartDashesType SmartDashesType { get; set; } = UITextSmartDashesType.Default;
+
+        [Export("smartInsertDeleteType")]
+        public UITextSmartInsertDeleteType SmartInsertDeleteType { get; set; } = UITextSmartInsertDeleteType.Default;
+
+        [Export("passwordRules")] public UITextInputPasswordRules PasswordRules { get; set; } = null!;
+
+        public NSObject? WeakInputDelegate
+        {
+            get;
+            set;
+        }
+
+        NSObject IUITextInput.WeakTokenizer => _tokenizer;
+        
+    }
+}

+ 471 - 0
src/iOS/Avalonia.iOS/TextInputResponder.cs

@@ -0,0 +1,471 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using Foundation;
+using ObjCRuntime;
+using Avalonia.Input.TextInput;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Logging;
+using CoreGraphics;
+using UIKit;
+// ReSharper disable InconsistentNaming
+// ReSharper disable StringLiteralTypo
+
+namespace Avalonia.iOS;
+
+#nullable enable
+
+partial class AvaloniaView
+{
+
+    [Adopts("UITextInput")]
+    [Adopts("UITextInputTraits")]
+    [Adopts("UIKeyInput")]
+    partial class TextInputResponder : UIResponder, IUITextInput
+    {
+        private class AvaloniaTextRange : UITextRange, INSCopying
+        {
+            private UITextPosition? _start;
+            private UITextPosition? _end;
+            public int StartIndex { get; }
+            public int EndIndex { get; }
+
+            public AvaloniaTextRange(int startIndex, int endIndex)
+            {
+                if (startIndex < 0)
+                    throw new ArgumentOutOfRangeException(nameof(startIndex));
+
+                if (endIndex < startIndex)
+                    throw new ArgumentOutOfRangeException(nameof(endIndex));
+
+                StartIndex = startIndex;
+                EndIndex = endIndex;
+            }
+
+            public override bool IsEmpty => StartIndex == EndIndex;
+
+            public override UITextPosition Start => _start ??= new AvaloniaTextPosition(StartIndex);
+            public override UITextPosition End => _end ??= new AvaloniaTextPosition(EndIndex);
+            public NSObject Copy(NSZone? zone)
+            {
+                return new AvaloniaTextRange(StartIndex, EndIndex);
+            }
+        }
+
+        private class AvaloniaTextPosition : UITextPosition, INSCopying
+        {
+            public AvaloniaTextPosition(int index)
+            {
+                if (index < 0)
+                    throw new ArgumentOutOfRangeException(nameof(index));
+                Index = index;
+            }
+
+            public int Index { get; }
+            public NSObject Copy(NSZone? zone) => new AvaloniaTextPosition(Index);
+        }
+
+        public TextInputResponder(AvaloniaView view, ITextInputMethodClient client)
+        {
+            _view = view;
+            NextResponder = view;
+            _client = client;
+            _tokenizer = new UITextInputStringTokenizer(this);
+        }
+
+        public override UIResponder NextResponder { get; }
+
+        private readonly ITextInputMethodClient _client;
+        private int _inSurroundingTextUpdateEvent;
+        private readonly UITextPosition _beginningOfDocument = new AvaloniaTextPosition(0);
+        private readonly UITextInputStringTokenizer _tokenizer;
+
+        public ITextInputMethodClient? Client => _client;
+
+        public override bool CanResignFirstResponder => true;
+
+        public override bool CanBecomeFirstResponder => true;
+
+        public override UIEditingInteractionConfiguration EditingInteractionConfiguration =>
+            UIEditingInteractionConfiguration.Default;
+
+        public override NSString TextInputContextIdentifier => new NSString(Guid.NewGuid().ToString());
+
+        public override UITextInputMode TextInputMode => UITextInputMode.CurrentInputMode;
+
+        [DllImport("/usr/lib/libobjc.dylib")]
+        private static extern void objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg);
+
+        private static readonly IntPtr SelectionWillChange = Selector.GetHandle("selectionWillChange:");
+        private static readonly IntPtr SelectionDidChange = Selector.GetHandle("selectionDidChange:");
+        private static readonly IntPtr TextWillChange = Selector.GetHandle("textWillChange:");
+        private static readonly IntPtr TextDidChange = Selector.GetHandle("textDidChange:");
+        private readonly AvaloniaView _view;
+        private string? _markedText;
+
+        
+        
+        private void SurroundingTextChanged(object? sender, EventArgs e)
+        {
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "SurroundingTextChanged");
+            if (WeakInputDelegate == null)
+                return;
+            _inSurroundingTextUpdateEvent++;
+            try
+            {
+                objc_msgSend(WeakInputDelegate.Handle.Handle, TextWillChange, Handle.Handle);
+                objc_msgSend(WeakInputDelegate.Handle.Handle, TextDidChange, Handle.Handle);
+                objc_msgSend(WeakInputDelegate.Handle.Handle, SelectionWillChange, this.Handle.Handle);
+                objc_msgSend(WeakInputDelegate.Handle.Handle, SelectionDidChange, this.Handle.Handle);
+            }
+            finally
+            {
+                _inSurroundingTextUpdateEvent--;
+            }
+        }
+
+        private void KeyPress(Key ev)
+        {
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "Triggering key press {key}", ev);
+            _view._topLevelImpl.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
+                RawKeyEventType.KeyDown, ev, RawInputModifiers.None));
+
+            _view._topLevelImpl.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot,
+                RawKeyEventType.KeyUp, ev, RawInputModifiers.None));
+        }
+
+        private void TextInput(string text)
+        {
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "Triggering text input {text}", text);
+            _view._topLevelImpl.Input(new RawTextInputEventArgs(KeyboardDevice.Instance!, 0, _view.InputRoot, text));
+        }
+
+        void IUIKeyInput.InsertText(string text)
+        {
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.InsertText {text}", text);
+
+            if (text == "\n")
+            {
+                KeyPress(Key.Enter);
+                return;
+            }
+
+            TextInput(text);
+        }
+        
+        void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back);
+
+        bool IUIKeyInput.HasText => true;
+
+        string IUITextInput.TextInRange(UITextRange range)
+        {
+            var r = (AvaloniaTextRange)range;
+            var s = _client.SurroundingText;
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.TextInRange {start} {end}", r.StartIndex, r.EndIndex);
+
+            string result = "";
+            if(string.IsNullOrEmpty(_markedText))
+                result = s.Text[r.StartIndex .. r.EndIndex];
+            else
+            {
+                var span = new CombinedSpan3<char>(s.Text.AsSpan().Slice(0, s.CursorOffset),
+                    _markedText,
+                    s.Text.AsSpan().Slice(s.CursorOffset));
+                var buf = new char[r.EndIndex - r.StartIndex];
+                span.CopyTo(buf, r.StartIndex);
+                result = new string(buf);
+            }
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "result: {res}", result);
+
+            return result;
+        }
+
+        void IUITextInput.ReplaceText(UITextRange range, string text)
+        {
+            var r = (AvaloniaTextRange)range;
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUIKeyInput.ReplaceText {start} {end} {text}", r.StartIndex, r.EndIndex, text);
+            _client.SelectInSurroundingText(r.StartIndex, r.EndIndex);
+            TextInput(text);
+        }
+
+        void IUITextInput.SetMarkedText(string markedText, NSRange selectedRange)
+        {
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUIKeyInput.SetMarkedText {start} {len} {text}", selectedRange.Location,
+                    selectedRange.Location, markedText);
+
+            _markedText = markedText;
+            _client.SetPreeditText(markedText);
+        }
+
+        void IUITextInput.UnmarkText()
+        {
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.UnmarkText");
+            if(_markedText == null)
+                return;
+            var commitString = _markedText;
+            _markedText = null;
+            _client.SetPreeditText(null);
+            if (string.IsNullOrWhiteSpace(commitString))
+                return;
+            TextInput(commitString);
+        }
+
+        public UITextRange GetTextRange(UITextPosition fromPosition, UITextPosition toPosition)
+        {
+            var f = (AvaloniaTextPosition)fromPosition;
+            var t = (AvaloniaTextPosition)toPosition;
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.GetTextRange {start} {end}", f.Index, t.Index);
+
+            return new AvaloniaTextRange(f.Index, t.Index);
+        }
+
+        UITextPosition IUITextInput.GetPosition(UITextPosition fromPosition, nint offset)
+        {
+            var pos = (AvaloniaTextPosition)fromPosition;
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)
+                ?.Log(null, "IUIKeyInput.GetPosition {start} {offset}", pos.Index, (int)offset);
+
+             var res = GetPositionCore(pos, offset);
+             Logger.TryGet(LogEventLevel.Debug, ImeLog)
+                 ?.Log(null, $"res: " + (res == null ? "null" : (int)res.Index));
+             return res!;
+        }
+
+        private AvaloniaTextPosition? GetPositionCore(AvaloniaTextPosition pos, nint offset)
+        {
+            
+            var end = pos.Index + (int)offset;
+            if (end < 0)
+                return null!;
+            if (end > DocumentLength)
+                return null;
+            return new AvaloniaTextPosition(end);
+        }
+
+        UITextPosition IUITextInput.GetPosition(UITextPosition fromPosition, UITextLayoutDirection inDirection,
+            nint offset)
+        {
+            var pos = (AvaloniaTextPosition)fromPosition;
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)
+                ?.Log(null, "IUIKeyInput.GetPosition {start} {direction} {offset}", pos.Index,  inDirection, (int)offset);
+
+            var res = GetPositionCore(pos, inDirection, offset);
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)
+                ?.Log(null, $"res: " + (res == null ? "null" : (int)res.Index));
+            return res!;
+        }
+        
+        private AvaloniaTextPosition? GetPositionCore(AvaloniaTextPosition fromPosition, UITextLayoutDirection inDirection,
+            nint offset)
+        {
+            var f = (AvaloniaTextPosition)fromPosition;
+            var newPosition = f.Index;
+
+            switch (inDirection)
+            {
+                case UITextLayoutDirection.Left:
+                    newPosition -= (int)offset;
+                    break;
+
+                case UITextLayoutDirection.Right:
+                    newPosition += (int)offset;
+                    break;
+            }
+
+            if (newPosition < 0)
+                return null!;
+
+            if (newPosition > DocumentLength)
+                return null!;
+
+            return new AvaloniaTextPosition(newPosition);
+        }
+
+        NSComparisonResult IUITextInput.ComparePosition(UITextPosition first, UITextPosition second)
+        {
+            var f = (AvaloniaTextPosition)first;
+            var s = (AvaloniaTextPosition)second;
+            if (f.Index < s.Index)
+                return NSComparisonResult.Ascending;
+
+            if (f.Index > s.Index)
+                return NSComparisonResult.Descending;
+
+            return NSComparisonResult.Same;
+        }
+
+        nint IUITextInput.GetOffsetFromPosition(UITextPosition fromPosition, UITextPosition toPosition)
+        {
+            var f = (AvaloniaTextPosition)fromPosition;
+            var t = (AvaloniaTextPosition)toPosition;
+            return t.Index - f.Index;
+        }
+
+        UITextPosition IUITextInput.GetPositionWithinRange(UITextRange range, UITextLayoutDirection direction)
+        {
+            var r = (AvaloniaTextRange)range;
+
+            if (direction is UITextLayoutDirection.Right or UITextLayoutDirection.Down)
+                return r.End;
+            return r.Start;
+        }
+
+        UITextRange IUITextInput.GetCharacterRange(UITextPosition byExtendingPosition, UITextLayoutDirection direction)
+        {
+            var p = (AvaloniaTextPosition)byExtendingPosition;
+            if (direction is UITextLayoutDirection.Left or UITextLayoutDirection.Up)
+                return new AvaloniaTextRange(0, p.Index);
+
+            return new AvaloniaTextRange(p.Index, DocumentLength);
+        }
+
+        NSWritingDirection IUITextInput.GetBaseWritingDirection(UITextPosition forPosition,
+            UITextStorageDirection direction)
+        {
+            return NSWritingDirection.LeftToRight;
+
+            // todo query and retyrn RTL.
+        }
+
+        void IUITextInput.SetBaseWritingDirectionforRange(NSWritingDirection writingDirection, UITextRange range)
+        {
+            // todo ? ignore?
+        }
+
+        CGRect IUITextInput.GetFirstRectForRange(UITextRange range)
+        {
+            
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUITextInput:GetFirstRectForRange");
+            // TODO: Query from the input client
+            var r = _view._cursorRect;
+
+            return new CGRect(r.Left, r.Top, r.Width, r.Height);
+        }
+
+        CGRect IUITextInput.GetCaretRectForPosition(UITextPosition? position)
+        {
+            // TODO: Query from the input client
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUITextInput:GetCaretRectForPosition");
+            var rect = _client.CursorRectangle;
+
+            return new CGRect(rect.X, rect.Y, rect.Width, rect.Height);
+        }
+
+        UITextPosition IUITextInput.GetClosestPositionToPoint(CGPoint point)
+        {
+            // TODO: Query from the input client
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUITextInput:GetClosestPositionToPoint");
+            return new AvaloniaTextPosition(0);
+        }
+
+        UITextPosition IUITextInput.GetClosestPositionToPoint(CGPoint point, UITextRange withinRange)
+        {
+            // TODO: Query from the input client
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUITextInput:GetClosestPositionToPoint");
+            return new AvaloniaTextPosition(0);
+        }
+
+        UITextRange IUITextInput.GetCharacterRangeAtPoint(CGPoint point)
+        {
+            // TODO: Query from the input client
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUITextInput:GetCharacterRangeAtPoint");
+            return new AvaloniaTextRange(0, 0);
+        }
+
+        UITextSelectionRect[] IUITextInput.GetSelectionRects(UITextRange range)
+        {
+            // TODO: Query from the input client
+            Logger.TryGet(LogEventLevel.Debug, ImeLog)?
+                .Log(null, "IUITextInput:GetSelectionRect");
+            return new UITextSelectionRect[0];
+        }
+
+        [Export("textStylingAtPosition:inDirection:")]
+        public NSDictionary GetTextStylingAtPosition(UITextPosition position, UITextStorageDirection direction)
+        {
+            return null!;
+        }
+
+        UITextRange? IUITextInput.SelectedTextRange
+        {
+            get
+            {
+                return new AvaloniaTextRange(
+                    Math.Min(_client.SurroundingText.CursorOffset, _client.SurroundingText.AnchorOffset),
+                    Math.Max(_client.SurroundingText.CursorOffset, _client.SurroundingText.AnchorOffset));
+            }
+            set
+            {
+                if (_inSurroundingTextUpdateEvent > 0)
+                    return;
+                if (value == null)
+                    _client.SelectInSurroundingText(_client.SurroundingText.CursorOffset,
+                        _client.SurroundingText.CursorOffset);
+                else
+                {
+                    var r = (AvaloniaTextRange)value;
+                    _client.SelectInSurroundingText(r.StartIndex, r.EndIndex);
+                }
+            }
+        }
+
+        NSDictionary? IUITextInput.MarkedTextStyle
+        {
+            get => null;
+            set {}
+        }
+
+        UITextPosition IUITextInput.BeginningOfDocument => _beginningOfDocument;
+
+        private int DocumentLength => _client.SurroundingText.Text.Length + (_markedText?.Length ?? 0);
+        UITextPosition IUITextInput.EndOfDocument => new AvaloniaTextPosition(DocumentLength);
+
+        UITextRange IUITextInput.MarkedTextRange
+        {
+            get
+            {
+                if (string.IsNullOrWhiteSpace(_markedText))
+                    return null!;
+                return new AvaloniaTextRange(_client.SurroundingText.CursorOffset, _client.SurroundingText.CursorOffset + _markedText.Length);
+            }
+        }
+
+        public override bool BecomeFirstResponder()
+        {
+            var res = base.BecomeFirstResponder();
+            if (res)
+            {
+                Logger.TryGet(LogEventLevel.Debug, "IOSIME")
+                    ?.Log(null, "Became first responder");
+                _client.SurroundingTextChanged += SurroundingTextChanged;
+                CurrentAvaloniaResponder = this;
+            }
+
+            return res;
+        }
+
+
+        public override bool ResignFirstResponder()
+        {
+            var res = base.ResignFirstResponder();
+            if (res && ReferenceEquals(CurrentAvaloniaResponder, this))
+            {
+                
+                Logger.TryGet(LogEventLevel.Debug, "IOSIME")
+                    ?.Log(null, "Resigned first responder");
+                _client.SurroundingTextChanged -= SurroundingTextChanged;
+                CurrentAvaloniaResponder = null;
+            }
+
+            return res;
+        }
+    }
+}