El uso de UIApparance con una subclass de UIBarButtonItem está provocando que el selector no reconocido se envíe a UINavigationButton

TL & DR;

Establecer una propiedad personalizada de una subclass de UIBarButtonItem utilizando el proxy UIAppearance provoca la exception del "selector no reconocido", ya que el configurador probablemente se reenvía mediante UIAppearance a UINavigationButton, no al button de barra.


Descripción general del SDK

Estoy usando iOS 7 Beta 5 SDK con Xcode 5 DP5. Pero, por favor, no me digas que está en el NDA, porque no hablo sobre nuevas características o nuevas classs en esta pregunta. Le informo de mi SDK, porque se puede descubrir que es solo un error en el software beta.

Lo que hice

UIBarButtonItem un UIBarButtonItem y creé una propiedad personalizada en el file de encabezado:

 @property (nonatomic, strong) NSString *mySubclassedProperty UI_APPEARANCE_SELECTOR; 

Mi setter y getter lucen así:

 - (void)setMySubclassedProperty:(NSString *)mySubclassedProperty { _mySubclassedProperty = mySubclassedProperty; NSLog(@"%p %s %@", self, __PRETTY_FUNCTION__, mySubclassedProperty); } 

Nada especial, ¿eh? Pero no funciona en absoluto con la UIAppearance de UIAppearance . Cuando trato de establecer la apariencia pnetworkingeterminada en el delegado de mi aplicación, no me da ningún error, ni advertencia, en absoluto.

 [[AKBarButtonItem appearance] setMySubclassedProperty:@"GLOBALLY ASSIGNED"]; 

El choque

Parece funcionar como un encanto, excepto por el hecho de que se bloquea cuando bash establecer una instancia de AKBarButtonItem en self.navigationItem.rightBarButtonItem :

 self.navigationItem.rightBarButtonItem = [[AKBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:nil action:nil]; 

El backtrace se ve así:

 2013-08-13 14:30:08.551 UIBarButtonItem Subclass Demo[1512:a0b] -[UINavigationButton setMySubclassedProperty:]: unrecognized selector sent to instance 0x8c42710 2013-08-13 14:30:08.556 UIBarButtonItem Subclass Demo[1512:a0b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationButton setMySubclassedProperty:]: unrecognized selector sent to instance 0x8c42710' *** First throw call stack: ( 0 CoreFoundation 0x0173b6f4 __exceptionPreprocess + 180 1 libobjc.A.dylib 0x014bb8b6 objc_exception_throw + 44 2 CoreFoundation 0x017d8983 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275 3 CoreFoundation 0x0172ba1b ___forwarding___ + 1019 4 CoreFoundation 0x0172b5fe _CF_forwarding_prep_0 + 14 5 CoreFoundation 0x0172fe2d __invoking___ + 29 6 CoreFoundation 0x0172fd3a -[NSInvocation invoke] + 362 7 CoreFoundation 0x0172feba -[NSInvocation invokeWithTarget:] + 74 8 UIKit 0x00763255 workaround10030904InvokeWithTarget + 824 9 UIKit 0x0075dea8 +[_UIAppearance _applyInvocationsTo:window:matchingSelector:] + 4497 10 UIKit 0x0075e299 +[_UIAppearance _applyInvocationsTo:window:] + 55 11 UIKit 0x0029ddcb -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 183 12 libobjc.A.dylib 0x014cd81f -[NSObject performSelector:withObject:] + 70 13 QuartzCore 0x03ac172a -[CALayer layoutSublayers] + 148 14 QuartzCore 0x03ab5514 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380 15 QuartzCore 0x03ac3b55 -[CALayer(CALayerPrivate) layoutBelowIfNeeded] + 43 16 UIKit 0x00290886 -[UIView(Hierarchy) layoutBelowIfNeeded] + 595 17 UIKit 0x0029062d -[UIView(Hierarchy) layoutIfNeeded] + 74 18 UIKit 0x0050841e -[UIBarButtonItem(UIStatic) _leftRightImagePaddingForEdgeMarginInNavBarIsMini:] + 574 19 UIKit 0x002ea325 -[UINavigationBar _getTitleViewFrame:leftViewFrames:rightViewFrames:forItemAtIndex:returnedIdealWidthOfTextContent:availableLayoutWidthForTextContent:idealBackButtonWidth:] + 3522 20 UIKit 0x002ef2da -[UINavigationBar _getTitleViewFrame:leftViewFrames:rightViewFrames:forItemAtIndex:] + 591 21 UIKit 0x002ef59a -[UINavigationBar _getTitleViewFrame:leftViewFrames:rightViewFrames:] + 151 22 UIKit 0x002dc515 -[UINavigationBar _setLeftViews:rightViews:] + 1894 23 UIKit 0x002ca6c5 -[UINavigationItem updateNavigationBarButtonsAnimated:] + 188 24 UIKit 0x002cab63 -[UINavigationItem setObject:forLeftRightKeyPath:animated:] + 547 25 UIKit 0x002cb1ae -[UINavigationItem setRightBarButtonItem:animated:] + 171 26 UIKit 0x002cb0fe -[UINavigationItem setRightBarButtonItem:] + 48 27 UIBarButtonItem Subclass Demo 0x0000665e -[AKViewController viewDidLoad] + 222 28 UIKit 0x00345c18 -[UIViewController loadViewIfRequinetworking] + 696 29 UIKit 0x00345eb4 -[UIViewController view] + 35 30 UIKit 0x00370369 -[UINavigationController rotatingSnapshotViewForWindow:] + 52 31 UIKit 0x00698e10 -[UIClientRotationContext initWithClient:toOrientation:duration:andWindow:] + 420 32 UIKit 0x00276ff2 -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:] + 1495 33 UIKit 0x00276a16 -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:] + 82 34 UIKit 0x002768e8 -[UIWindow _setRotatableViewOrientation:updateStatusBar:duration:force:] + 117 35 UIKit 0x00276970 -[UIWindow _setRotatableViewOrientation:duration:force:] + 67 36 UIKit 0x00275a0a __57-[UIWindow _updateToInterfaceOrientation:duration:force:]_block_invoke + 120 37 UIKit 0x0027596c -[UIWindow _updateToInterfaceOrientation:duration:force:] + 400 38 UIKit 0x002766c3 -[UIWindow setAutorotates:forceUpdateInterfaceOrientation:] + 870 39 UIKit 0x00279c7e -[UIWindow setDelegate:] + 449 40 UIKit 0x0034a037 -[UIViewController _tryBecomeRootViewControllerInWindow:] + 180 41 UIKit 0x0026f91c -[UIWindow addRootViewControllerViewIfPossible] + 609 42 UIKit 0x00270146 -[UIWindow setRootViewController:] + 960 43 UIBarButtonItem Subclass Demo 0x00006a07 -[AKAppDelegate application:didFinishLaunchingWithOptions:] + 823 44 UIKit 0x0022d525 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restreState:] + 309 45 UIKit 0x0022dd65 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1536 46 UIKit 0x00232578 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 824 47 UIKit 0x0024657c -[UIApplication handleEvent:withNewEvent:] + 3447 48 UIKit 0x00246ae9 -[UIApplication sendEvent:] + 85 49 UIKit 0x002341f5 _UIApplicationHandleEvent + 736 50 GraphicsServices 0x0365a33b _PurpleEventCallback + 776 51 GraphicsServices 0x03659e46 PurpleEventCallback + 46 52 CoreFoundation 0x016b6e95 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53 53 CoreFoundation 0x016b6bcb __CFRunLoopDoSource1 + 523 54 CoreFoundation 0x016e18ac __CFRunLoopRun + 2156 55 CoreFoundation 0x016e0bf3 CFRunLoopRunSpecific + 467 56 CoreFoundation 0x016e0a0b CFRunLoopRunInMode + 123 57 UIKit 0x00231cad -[UIApplication _run] + 840 58 UIKit 0x00233f0b UIApplicationMain + 1225 59 UIBarButtonItem Subclass Demo 0x00006e9d main + 141 60 libdyld.dylib 0x01d77725 start + 0 ) libc++abi.dylib: terminating with uncaught exception of type NSException 

Después de un examen, me di count de que el post setMySubclassedProperty: se envía a UINavigationButton (que es una subclass privada de UIButton usado en las barras de navigation, barras de herramientas y barras de búsqueda). Puse el button en el cuadro 26 y la magia UIAppearance se ejecuta en el cuadro 10 .

En iOS 6, el problema existe, pero se envía un post diferente a UINavigationButton :

 2013-08-13 14:35:58.316 UIBarButtonItem Subclass Demo[1591:c07] -[UINavigationButton _UIAppearance_setMySubclassedProperty:]: unrecognized selector sent to instance 0x810d600 2013-08-13 14:35:58.359 UIBarButtonItem Subclass Demo[1591:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationButton _UIAppearance_setMySubclassedProperty:]: unrecognized selector sent to instance 0x810d600' *** First throw call stack: (0x12b2012 0x10d7e7e 0x133d4bd 0x12a1bbc 0x12a194e 0x12a61bd 0x12a60d6 0x12a624a 0x68b9e8 0x68769c 0x687a95 0x291884 0x3088ca 0x28cf96 0x2934a4 0x28b89d 0x2cc646 0x2bc076 0x2bc517 0x2bcb66 0x2bcab6 0x665e 0x3261c7 0x326232 0x3264da 0x33d8e5 0x33d9cb 0x33dc76 0x33dd71 0x33e89b 0x33e9b9 0x33ea45 0x44420b 0x2952dd 0x10eb6b0 0x289dfc0 0x289233c 0x289deaf 0x3342bd 0x27cb56 0x27b66f 0x27b589 0x27a7e4 0x27a61e 0x27b3d9 0x27e2d2 0x32899c 0x275574 0x275cc1 0x6a07 0x242157 0x242747 0x24394b 0x254cb5 0x255beb 0x247698 0x24b8df9 0x24b8ad0 0x1227bf5 0x1227962 0x1258bb6 0x1257f44 0x1257e1b 0x24317a 0x244ffc 0x6e9d 0x1ab0725) libc++abi.dylib: terminate called throwing an exception 

Deducción

Mi primer pensamiento fue: "UIAppearance reenvía el setter a AKBarButtonItem y luego se bloquea". ¿Pero lo hace? En un artículo de Peter Steinberger descubrí que Apple está usando una subclass especial de _UIAppearance para elementos de barra que se llama _UIBarItemAppearance . Quería confirmarlo y establecer un punto de corte simbólico en -[_UIBarItemAppearance forwardInvocation:] y lldb se detuvo con éxito justo antes de que ocurriera la exception.

Ahora me parece que Apple está haciendo alguna lógica sucia en -[_UIBarItemAppearance forwardInvocation:] para darse count de qué selector debe enviarse a qué instancia, como:

  • setBackgroundImage:forState: debe enviarse a UINavigationButton
  • setBackButtonBackgroundImage:forState:barMetrics: a UIBarButtonItem

Y su código se ve así:

 if (selector == @selector(setBackButtonBackgroundImage:forState:barMetrics:)) { // forward to UIBarButtonItem } else if (selector == @selector(setBackButtonBackgroundVerticalPositionAdjustment:forBarMetrics:)) { // forward to UIBarButtonItem } else if (...) { // more forwarding to UIBarButtonItem } else { // forward to UINavigationButton } 

Entonces, de acuerdo con mi deducción, la última instrucción else está causando setNiceBarButtonItemAttributes: selector se envía a UINavigationButton , no UIBarButtonItem . Si es cierto, estamos condenados.


Código de muestra

Puede download un proyecto de muestra aquí: http://cl.ly/241I2A3U0a0y .


Ayuda

¿Estoy haciendo algo mal o es un error? O quizás Apple lo hizo a propósito? Cualquier ayuda sería apreciada.

El problema es UIBarButtonItem no es una subclass de UIView , es una class "controller" que crea subclasss UIView privadas. Para los elementos del button de barra, hay un proxy de apariencia privada que se utiliza, en lugar del habitual (alguna información aquí ).

¿Puedo sugerirte que hagas una ruta diferente? ¿Realmente necesitas elementos del button de la barra de subclass para tu personalización? ¿No podría crear una subclass de UINavigationBar y usarla como desencadenante de cambios con appearanceWhenContainedIn: 😕

OK, terminé usando una customView . Parece que es la única forma …