Bug 38823 - ActionForKey is not called for custom property
Summary: ActionForKey is not called for custom property
Alias: None
Product: iOS
Classification: Xamarin
Component: General (show other bugs)
Version: XI 9.4 (iOS 9.2)
Hardware: PC Mac OS
: --- enhancement
Target Milestone: Future Cycle (TBD)
Assignee: Bugzilla
Depends on:
Reported: 2016-02-16 23:10 UTC by Cody Beyer (MSFT)
Modified: 2017-11-06 22:34 UTC (History)
4 users (show)

See Also:
Is this bug a regression?: ---
Last known good build:

Sample (15.38 KB, application/zip)
2016-02-16 23:10 UTC, Cody Beyer (MSFT)

Description Cody Beyer (MSFT) 2016-02-16 23:10:12 UTC
Created attachment 15070 [details]

# Description

The following sample app demonstrates an issue where ActionForKey will not fire for a custom property of a custom layer

# Sample


# Steps to Reproduce

1. Download sample
2. Set breakpoint at line 49 of TestTextFieldBorderLayer.cs
3. Run in simulator

# Expected Results

Breakpoint should hit

# Actual Results

Breakpoint does not hit

# Additional Notes

If you set the breakpoint at line 47, and repeat step 3, you will see that the event is fired for other properties

# Versions

=== Xamarin Studio ===

Version 6.0 (build 3668)
Installation UUID: f7bdaa72-7488-49e8-a5cc-72440654f7a3
	Mono 4.3.0 (mono-4.3.1-branch/37d269c) (64-bit)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 403002114

=== Xamarin.Profiler ===

Not Installed

=== Apple Developer Tools ===

Xcode 7.2.1 (9548.1)
Build 7C1002

=== Xamarin.iOS ===

Version: (Business Edition)
Hash: 962a050
Branch: master
Build date: 2016-01-29 16:59:11-0500

=== Xamarin.Android ===

Version: (Business Edition)
Android SDK: /Users/codybeyer/Library/Developer/Xamarin/android-sdk-macosx
	Supported Android versions:
		4.0.3 (API level 15)
		4.4   (API level 19)
		6.0   (API level 23)

SDK Tools Version: 24.4.1
SDK Platform Tools Version: 23.0.1
SDK Build Tools Version: 23.0.1

Java SDK: /usr
java version "1.7.0_71"
Java(TM) SE Runtime Environment (build 1.7.0_71-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.71-b01, mixed mode)

=== Xamarin Android Player ===

Version: 0.6.5
Location: /Applications/Xamarin Android Player.app

=== Xamarin.Mac ===

Version: (Business Edition)

=== Build Information ===

Release ID: 600003668
Git revision: 23e9d5d22133ca5a7c56b26644ab59a4626bccdb
Build date: 2016-01-20 15:12:22-05
Xamarin addins: bca6749910827de4af7eab7a925ff4ccf2bda06a
Build lane: monodevelop-lion-roslyn-dark-skin-64bit

=== Operating System ===

Mac OS X 10.11.3
Darwin clb-macbook.local 15.3.0 Darwin Kernel Version 15.3.0
	Thu Dec 10 18:40:58 PST 2015
	root:xnu-3248.30.4~1/RELEASE_X86_64 x86_64
Comment 1 Rolf Bjarne Kvinge [MSFT] 2016-02-17 16:48:29 UTC
This seems to be because we don't support anything equivalent to the Objective-C @dynamic keyword (a property without getter/setter).

Relevant reading:

Comment 2 Rolf Bjarne Kvinge [MSFT] 2016-04-27 21:31:39 UTC
Idea how to implement:

    class MyClass : NSObject {
        public NSObject MyProperty {
            get { return GetObjectiveCProperty<NSObject> ("MyProperty"); }
            set { SetObjectiveCProperty ("MyProperty", value); }

The [ObjectiveCProperty] would tell the registrars that this needs to be an ObjC property where the data is stored in native code.

The getters/setters would be the code that users would have to write manually.
Comment 3 RobertN 2017-10-31 16:48:15 UTC
I'm currently working on a project that has extensive use of custom CALayer properties for visual and non-visual CAActions. Other then writing all of these in ObjC, is there any work-around available to make this work (currently using Xamarin.iOS Version:

Thanks, Robert
Comment 4 Rolf Bjarne Kvinge [MSFT] 2017-11-01 07:24:39 UTC
@Robert, this is untested, but something like the following might work: https://gist.github.com/rolfbjarne/62149c28d755ef1f7c22cc76b6196b43. It might also be required to call WillChangeValue/DidChangeValue in the setter.
Comment 5 RobertN 2017-11-04 22:49:46 UTC
Rolf, Thank you for the reply. 

I did not get the project, the client went with an ObjC/Swift solution to expedite things… :-(

But I did look at your gist, thanks, I have actually used set/get on the Ivar in Xamarin.iOS for different reasons but using a Xamarin.iOS Export/registered property will not work as CoreAnimation (and CoreData) is looking for @dynamic/NSManaged-based props that have no setter &| getter to determine what props are custom and that it should handle.

Is raining so I played around w/ ObjC and @property and the produced ObjC property type encodings and CoreAnimiation will respond to (T@“NSNumber”,&,D) and ignore vars that have backing instance vars like (T@“NSNumber",&,N,V_foobar) or anything that has a setter or getter (G<name>/S<name>). It is fairly easy to create these properties in Xamarin.iOS at runtime:


That is rough, but you get the idea ;-)

Using that, you have to register the @dynamic properties at runtime, such as:

        //!!! Add @dynamic properties to your CALayer subclasses before instancing any of them or their owning UIViews
		clsType: typeof(DigitalMixerMotorizedFaderLayer),
		propertyName: DigitalMixerMotorizedFaderLayer.DynamicLevelKey,
		encodeType: typeof(NSNumber)

As a cheap debug test, override the Layer's SetValueForUndefinedKey, and as long as is not being called with your dynamic keyPath, the property was added corrected and it is available to be "animated" (assuming that the type can be CoreAnimation handled (basically any numeric type but not things like NSDate, ...).

Since that works fine with CoreAnimiation/CALayer, I used your implementation idea and I went ahead and added it to the ObjCRuntime.DynamicRegistrar.cs and came up this with using a custom attribute (ExportDynamicAttribute), this is an example of the code the user writes as they are responsible for ValueForKey & SetValueForKey and also adding WillChangeValue & SetValueForKey if they need KVO support on these @dynamic props, etc...

	public const string DynamicLevelKey = "motorizedVolumeLevel";
	public NSNumber MotorizedVolumeLevel
		get => (NSNumber)ValueForKey(new NSString(DynamicLevelKey));
			WillChangeValue(DynamicLevelKey); //!!! If you need KVO on the @dynamic property
			SetValueForKey(value, new NSString(DynamicLevelKey));
			DidChangeValue(DynamicLevelKey); //!!! If you need KVO on the @dynamic property

I have not dived into the Xamarin.iOS static registration, so as a kludge I just add a resource bundle file that contains the classes to scan via ObjCRuntime.DynamicRegistrar to avoid scanning all classes so all the @dynamic properties are added before the app starts instancing any classes that contain them.

Works great for me, I was able to throw together an prototype that simulates a motorized mixing board by cascading @dynamic property values across the custom slider ( a UIVIew and their custom "layerClass:" CALayers that contain those @dynamic properties) to handle the channel's output audio volumes and the graphical slider positions and also link adjacent sliders to track the assigned volume curve as you override an individual channel's value (move one slider and they all ripple the changes to maintain the same assigned curve...).

Would love to see @dynamics supported out of the box in a future release ;-)

Thanks, Robert
Comment 6 Rolf Bjarne Kvinge [MSFT] 2017-11-06 14:33:24 UTC
Wow, you pretty much implemented this yourself, and already figured out solutions for the problems we would have run into too.

Great work!

Hopefully we'll be able to implement and support this soon.
Comment 7 RobertN 2017-11-06 22:34:52 UTC
Rolf, Thanks... 

Learning how ObjC property type encoding works was cool but it was a really good CoreAnimation learning experience for me. I have found a lot of "misinformation" concerning usage of CALayer custom properties on non-Apple blog posts and StackOverflow concerning how these should (and can) be used. Highly recommend referring to the official docs in Apple's Core Animation Programming Guide first as I "learned" a lot of wrong assumptions by not reading that doc first (especially the appendixes) ;-)

* https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004514

Note You need to log in before you can comment on or make changes to this bug.