diff --git a/src/iOS/Avalonia.iOS/PlatformSettings.cs b/src/iOS/Avalonia.iOS/PlatformSettings.cs index 0ae290468b..082966f5b2 100644 --- a/src/iOS/Avalonia.iOS/PlatformSettings.cs +++ b/src/iOS/Avalonia.iOS/PlatformSettings.cs @@ -1,8 +1,7 @@ +#nullable enable using System; -using System.Linq; using Avalonia.Media; using Avalonia.Platform; -using Foundation; using UIKit; namespace Avalonia.iOS; @@ -10,7 +9,7 @@ namespace Avalonia.iOS; // TODO: ideally should be created per view/activity. internal class PlatformSettings : DefaultPlatformSettings { - private PlatformColorValues _lastColorValues; + private PlatformColorValues? _lastColorValues; public override PlatformColorValues GetColorValues() { diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs index 6fa65f1265..27bd8faf64 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -24,8 +23,11 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem using (var doc = new UIDocument(url)) { - _filePath = doc.FileUrl?.Path ?? url.FilePathUrl.Path; - Name = doc.LocalizedName ?? System.IO.Path.GetFileName(_filePath) ?? url.FilePathUrl.LastPathComponent; + _filePath = doc.FileUrl?.Path ?? url.FilePathUrl?.Path ?? string.Empty; + Name = doc.LocalizedName + ?? System.IO.Path.GetFileName(_filePath) + ?? url.FilePathUrl?.LastPathComponent + ?? string.Empty; } } diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs index 8862f4e392..e444a90506 100644 --- a/src/iOS/Avalonia.iOS/TextInputResponder.cs +++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs @@ -1,14 +1,13 @@ using System; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using Avalonia.Controls.Presenters; -using Foundation; -using ObjCRuntime; -using Avalonia.Input.TextInput; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Input.TextInput; using Avalonia.Logging; using CoreGraphics; +using Foundation; +using ObjCRuntime; using UIKit; // ReSharper disable InconsistentNaming // ReSharper disable StringLiteralTypo @@ -25,6 +24,9 @@ partial class AvaloniaView [Adopts("UIKeyInput")] partial class TextInputResponder : UIResponder, IUITextInput { + private static AvaloniaEmptyTextPosition? _emptyPosition; + private static AvaloniaEmptyTextPosition EmptyPosition => _emptyPosition ??= new(); + private class AvaloniaTextRange : UITextRange, INSCopying { private UITextPosition? _start; @@ -67,6 +69,15 @@ partial class AvaloniaView public NSObject Copy(NSZone? zone) => new AvaloniaTextPosition(Index); } + private class AvaloniaEmptyTextPosition : UITextPosition, INSCopying + { + public AvaloniaEmptyTextPosition() + { + + } + public NSObject Copy(NSZone? zone) => this; + } + public TextInputResponder(AvaloniaView view, ITextInputMethodClient client) { _view = view; @@ -93,7 +104,25 @@ partial class AvaloniaView public override NSString TextInputContextIdentifier => new NSString(Guid.NewGuid().ToString()); - public override UITextInputMode TextInputMode => UITextInputMode.CurrentInputMode; + public override UITextInputMode TextInputMode + { + get + { + var mode = UITextInputMode.CurrentInputMode; + // Can be empty see https://developer.apple.com/documentation/uikit/uitextinputmode/1614522-activeinputmodes + if (mode is null && UITextInputMode.ActiveInputModes.Length > 0) + { + mode = UITextInputMode.ActiveInputModes[0]; + } + // See: https://stackoverflow.com/a/33337483/20894223 + if (mode is null) + { + using var tv = new UITextView(); + mode = tv.TextInputMode; + } + return mode; + } + } [DllImport("/usr/lib/libobjc.dylib")] private static extern void objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg); @@ -105,8 +134,8 @@ partial class AvaloniaView private readonly AvaloniaView _view; private string? _markedText; - - + + private void SurroundingTextChanged(object? sender, EventArgs e) { Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "SurroundingTextChanged"); @@ -153,9 +182,9 @@ partial class AvaloniaView switch (ReturnKeyType) { case UIReturnKeyType.Done: - case UIReturnKeyType.Go: - case UIReturnKeyType.Send: - case UIReturnKeyType.Search: + case UIReturnKeyType.Go: + case UIReturnKeyType.Send: + case UIReturnKeyType.Search: ResignFirstResponder(); break; } @@ -164,7 +193,7 @@ partial class AvaloniaView TextInput(text); } - + void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back); bool IUIKeyInput.HasText => true; @@ -176,8 +205,8 @@ partial class AvaloniaView 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]; + if (string.IsNullOrEmpty(_markedText)) + result = s.Text[r.StartIndex..r.EndIndex]; else { var span = new CombinedSpan3(s.Text.AsSpan().Slice(0, s.CursorOffset), @@ -214,7 +243,7 @@ partial class AvaloniaView void IUITextInput.UnmarkText() { Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.UnmarkText"); - if(_markedText == null) + if (_markedText == null) return; var commitString = _markedText; _markedText = null; @@ -239,15 +268,15 @@ partial class AvaloniaView 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!; + 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!; @@ -261,14 +290,14 @@ partial class AvaloniaView { var pos = (AvaloniaTextPosition)fromPosition; Logger.TryGet(LogEventLevel.Debug, ImeLog) - ?.Log(null, "IUIKeyInput.GetPosition {start} {direction} {offset}", pos.Index, inDirection, (int)offset); + ?.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) { @@ -348,7 +377,7 @@ partial class AvaloniaView CGRect IUITextInput.GetFirstRectForRange(UITextRange range) { - + Logger.TryGet(LogEventLevel.Debug, ImeLog)? .Log(null, "IUITextInput:GetFirstRectForRange"); // TODO: Query from the input client @@ -377,11 +406,11 @@ partial class AvaloniaView if (presenter is { }) { var hitResult = presenter.TextLayout.HitTestPoint(new Point(point.X, point.Y)); - + return new AvaloniaTextPosition(hitResult.TextPosition); } - return null; + return EmptyPosition; } UITextPosition IUITextInput.GetClosestPositionToPoint(CGPoint point, UITextRange withinRange) @@ -440,7 +469,7 @@ partial class AvaloniaView NSDictionary? IUITextInput.MarkedTextStyle { get => null; - set {} + set { } } UITextPosition IUITextInput.BeginningOfDocument => _beginningOfDocument; @@ -478,7 +507,7 @@ partial class AvaloniaView var res = base.ResignFirstResponder(); if (res && ReferenceEquals(CurrentAvaloniaResponder, this)) { - + Logger.TryGet(LogEventLevel.Debug, "IOSIME") ?.Log(null, "Resigned first responder"); _client.SurroundingTextChanged -= SurroundingTextChanged;