From 7f6f2bf81d8280129848219957bed32c594aba59 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Wed, 15 Mar 2017 07:44:01 -0500 Subject: [PATCH] [uikit] Fix contest between UITextField.Ended[WithReason] events. Fixes #53174 (#1875) iOS 10 added a new `textFieldDidEndEditing:reason:` to `UITextField`. It gets called, if set, before the older `textFieldDidEndEditing`. This cause a problem for code that does add events for both since the internal `*Delegate` does override both (to allow events) so the call might be "lost" without additional logic. This fix makes sure that even using one (or the two) events will work across all versions of iOS. reference: https://bugzilla.xamarin.com/show_bug.cgi?id=53174 --- src/UIKit/UIEnums.cs | 1 + src/UIKit/UITextField.cs | 202 ++++++++++++++++++++++++++++++++++++++- src/uikit.cs | 3 +- 3 files changed, 204 insertions(+), 2 deletions(-) diff --git a/src/UIKit/UIEnums.cs b/src/UIKit/UIEnums.cs index d2cde8242f..79ddfb464c 100644 --- a/src/UIKit/UIEnums.cs +++ b/src/UIKit/UIEnums.cs @@ -1969,6 +1969,7 @@ namespace XamCore.UIKit { [iOS (10,0), TV (10,0), NoWatch] [Native] public enum UITextFieldDidEndEditingReason : nint { + Unknown = -1, // helper value (not in headers) Committed, [NoiOS] Cancelled diff --git a/src/UIKit/UITextField.cs b/src/UIKit/UITextField.cs index dd6d0423c5..fd1d46df3c 100644 --- a/src/UIKit/UITextField.cs +++ b/src/UIKit/UITextField.cs @@ -7,10 +7,210 @@ // Copyright 2009, Novell, Inc. // -#if !WATCH +#if !WATCH && !COREBUILD + +using System; +using XamCore.Foundation; +using XamCore.ObjCRuntime; namespace XamCore.UIKit { + + public partial class UITextFieldEditingEndedEventArgs : EventArgs { + public UITextFieldEditingEndedEventArgs (UITextFieldDidEndEditingReason reason) + { + this.Reason = reason; + } + public UITextFieldDidEndEditingReason Reason { get; set; } + } + + public delegate bool UITextFieldChange (UITextField textField, NSRange range, string replacementString); + + public delegate bool UITextFieldCondition (UITextField textField); + public partial class UITextField : IUITextInputTraits { + + internal virtual Type GetInternalEventDelegateType + { + get { return typeof (_UITextFieldDelegate); } + } + + internal virtual _UITextFieldDelegate CreateInternalEventDelegateType () + { + return (_UITextFieldDelegate)(new _UITextFieldDelegate()); + } + + internal _UITextFieldDelegate EnsureUITextFieldDelegate () + { +#if XAMCORE_2_0 + if (Delegate != null) + UIApplication.EnsureEventAndDelegateAreNotMismatched (Delegate, GetInternalEventDelegateType); + _UITextFieldDelegate del = Delegate as _UITextFieldDelegate; + if (del == null){ + del = (_UITextFieldDelegate)CreateInternalEventDelegateType (); + Delegate = (IUITextFieldDelegate)del; + } + return del; +#else + var del = Delegate; + if (del == null || (!(del is _UITextFieldDelegate))){ + del = new _UITextFieldDelegate (); + Delegate = del; + } + return (_UITextFieldDelegate) del; +#endif + } + + #pragma warning disable 672 + [Register] +#if XAMCORE_2_0 + internal class _UITextFieldDelegate : NSObject, IUITextFieldDelegate { +#else + internal class _UITextFieldDelegate : UITextFieldDelegate { +#endif + public _UITextFieldDelegate () { IsDirectBinding = false; } + + internal EventHandler editingEnded; + [Preserve (Conditional = true)] + [Export ("textFieldDidEndEditing:")] + public void EditingEnded (UITextField textField) + { + EventHandler handler = editingEnded; + if (handler != null){ + handler (textField, EventArgs.Empty); + } else { + // if this is executed before iOS10 and only the new API is used we'll raise the new event (if set) + EventHandler handler2 = editingEnded1; + if (handler2 != null) { + var args = new UITextFieldEditingEndedEventArgs (UITextFieldDidEndEditingReason.Unknown); + handler2 (textField, args); + } + } + } + + internal EventHandler editingEnded1; + [Preserve (Conditional = true)] + [Export ("textFieldDidEndEditing:reason:")] + public void EditingEnded (UITextField textField, UITextFieldDidEndEditingReason reason) + { + EventHandler handler = editingEnded1; + if (handler != null) { + var args = new UITextFieldEditingEndedEventArgs (reason); + handler (textField, args); + } else { + // if this is executed on iOS10 (or late) and only the old API is used then we'll raise the old event (if set) + EventHandler handler2 = editingEnded; + if (handler2 != null) + handler2 (textField, EventArgs.Empty); + } + } + + internal EventHandler editingStarted; + [Preserve (Conditional = true)] + [Export ("textFieldDidBeginEditing:")] + public void EditingStarted (UITextField textField) + { + EventHandler handler = editingStarted; + if (handler != null){ + handler (textField, EventArgs.Empty); + } + } + + internal UITextFieldCondition shouldBeginEditing; + [Preserve (Conditional = true)] + [Export ("textFieldShouldBeginEditing:")] + public bool ShouldBeginEditing (UITextField textField) + { + UITextFieldCondition handler = shouldBeginEditing; + if (handler != null) + return handler (textField); + return true; + } + + internal UITextFieldChange shouldChangeCharacters; + [Preserve (Conditional = true)] + [Export ("textField:shouldChangeCharactersInRange:replacementString:")] + public bool ShouldChangeCharacters (UITextField textField, NSRange range, string replacementString) + { + UITextFieldChange handler = shouldChangeCharacters; + if (handler != null) + return handler (textField, range, replacementString); + return true; + } + + internal UITextFieldCondition shouldClear; + [Preserve (Conditional = true)] + [Export ("textFieldShouldClear:")] + public bool ShouldClear (UITextField textField) + { + UITextFieldCondition handler = shouldClear; + if (handler != null) + return handler (textField); + return true; + } + + internal UITextFieldCondition shouldEndEditing; + [Preserve (Conditional = true)] + [Export ("textFieldShouldEndEditing:")] + public bool ShouldEndEditing (UITextField textField) + { + UITextFieldCondition handler = shouldEndEditing; + if (handler != null) + return handler (textField); + return true; + } + + internal UITextFieldCondition shouldReturn; + [Preserve (Conditional = true)] + [Export ("textFieldShouldReturn:")] + public bool ShouldReturn (UITextField textField) + { + UITextFieldCondition handler = shouldReturn; + if (handler != null) + return handler (textField); + return true; + } + } + #pragma warning restore 672 + + public event EventHandler Ended { + add { EnsureUITextFieldDelegate ().editingEnded += value; } + remove { EnsureUITextFieldDelegate ().editingEnded -= value; } + } + + public event EventHandler EndedWithReason { + add { EnsureUITextFieldDelegate ().editingEnded1 += value; } + remove { EnsureUITextFieldDelegate ().editingEnded1 -= value; } + } + + public event EventHandler Started { + add { EnsureUITextFieldDelegate ().editingStarted += value; } + remove { EnsureUITextFieldDelegate ().editingStarted -= value; } + } + + public UITextFieldCondition ShouldBeginEditing { + get { return EnsureUITextFieldDelegate ().shouldBeginEditing; } + set { EnsureUITextFieldDelegate ().shouldBeginEditing = value; } + } + + public UITextFieldChange ShouldChangeCharacters { + get { return EnsureUITextFieldDelegate ().shouldChangeCharacters; } + set { EnsureUITextFieldDelegate ().shouldChangeCharacters = value; } + } + + public UITextFieldCondition ShouldClear { + get { return EnsureUITextFieldDelegate ().shouldClear; } + set { EnsureUITextFieldDelegate ().shouldClear = value; } + } + + public UITextFieldCondition ShouldEndEditing { + get { return EnsureUITextFieldDelegate ().shouldEndEditing; } + set { EnsureUITextFieldDelegate ().shouldEndEditing = value; } + } + + public UITextFieldCondition ShouldReturn { + get { return EnsureUITextFieldDelegate ().shouldReturn; } + set { EnsureUITextFieldDelegate ().shouldReturn = value; } + } } } diff --git a/src/uikit.cs b/src/uikit.cs index 11c40ddec6..8c152676de 100644 --- a/src/uikit.cs +++ b/src/uikit.cs @@ -11678,7 +11678,8 @@ namespace XamCore.UIKit { UITableViewRowAction Create (UITableViewRowActionStyle style, [NullAllowed] string title, Action handler); } - [BaseType (typeof (UIControl), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] {typeof(UITextFieldDelegate)})] + [BaseType (typeof (UIControl), Delegates=new string [] { "WeakDelegate" })] + // , Events=new Type [] {typeof(UITextFieldDelegate)})] custom logic needed, see https://bugzilla.xamarin.com/show_bug.cgi?id=53174 interface UITextField : UITextInput, UIContentSizeCategoryAdjusting { [Export ("initWithFrame:")] IntPtr Constructor (CGRect frame);