Appearance Proxies/Ui_Appearance_Selector in Swift

Can't find UI_APPEARANCE_SELECTOR anywhere

You can't find it because the accessor is inherited from UIView (UIButton inherits from; UIControl : UIView : UIResponder : NSObject).
So when you're doing [UIButton appearance] you get the appearance proxy for the passed class, just because UIView adopts the UIAppearance and UIAppearanceContainer protocols. I hope that this makes sense.

PS. If you look for example UIBarButtonItem's header file, you will see that the UI_APPEARANCE_SELECTOR is there.

respondsToSelector fails for appearance proxy

Don't check the appearance proxy. You can never rely on that, since it's a proxy. Instead, directly check the item that has the new method, in this case, the UIBarButtonItem:

BOOL hasNewMethod = [UIBarButtonItem instancesRespondToSelector:@selector(setBackgroundImage:forState:style:barMetrics:)];
if( hasNewMethod )
NSLog(@"Running iOS 6 with new method");
else
NSLog(@"Current OS doesn't support method...");

iOS: Change appearance of UIView only (no subclasses) - possible?

If you use the appearance proxy at a class level there's no choice. You're going to modify every UIView subclass.

I don't see many other options to subclassing UIViewController, changing the appearence of that subclass' view only and then making all the others UIViewController subclass of the first one.

UIAppearance proxy for custom objects

After some reserach I "give up" about using a standard Apple object. It doesn't exists, for now. I've created my own proxy, it's quite simple (works only with "appearance:" by now).

Let's explain it.
I want to set the appearance of "textColor" on a NSObject subclass, let's call it "FLObject".
Make FLObject conforms to UIAppearance protocol and override the appearance method.
In this method, you should return a proxy class (the one I created):

+ (id)appearance
{
return [FLAppearance appearanceForClass:[self class]];
}

How it works?
FLAppearance creates a single instance of itself for each class passed by the appearanceForClass: method.
If you call it two times for the same class, the same instance is returned.

Then, you can do something like this:

[[FLObject appearance] setTextColor:[UIColor redColor]]; 

FLAppearance overrides the forwardInvocation: method, so it accepts all methods sent.
Then, it puts all invocations in an array.
When FLObject is initialized, a simple call to

[(FLAppearance *)[FLAppearance appearanceForClass:[self class]] startForwarding:self];

will start to send invocations and set the appearance.
Sure, this needs some tuning and error checking, but I think it's a good start.

@interface FLAppearance ()

@property (strong, nonatomic) Class mainClass;
@property (strong, nonatomic) NSMutableArray *invocations;

@end

static NSMutableDictionary *dictionaryOfClasses = nil;

@implementation FLAppearance

// this method return the same object instance for each different class
+ (id) appearanceForClass:(Class)thisClass
{
// create the dictionary if not exists
// use a dispatch to avoid problems in case of concurrent calls
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!dictionaryOfClasses)
dictionaryOfClasses = [[NSMutableDictionary alloc]init];
});



if (![dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)])
{
id thisAppearance = [[self alloc]initWithClass:thisClass];
[dictionaryOfClasses setObject:thisAppearance forKey:NSStringFromClass(thisClass)];
return thisAppearance;
}
else
return [dictionaryOfClasses objectForKey:NSStringFromClass(thisClass)];
}

- (id)initWithClass:(Class)thisClass
{
self = [self initPrivate];
if (self) {
self.mainClass = thisClass;
self.invocations = [NSMutableArray array];
}
return self;
}

- (id)init
{
[NSException exceptionWithName:@"InvalidOperation" reason:@"Cannot invoke init. Use appearanceForClass: method" userInfo:nil];
return nil;
}

- (id)initPrivate
{
if (self = [super init]) {

}
return self;
}

-(void)forwardInvocation:(NSInvocation *)anInvocation;
{
// tell the invocation to retain arguments
[anInvocation retainArguments];

// add the invocation to the array
[self.invocations addObject:anInvocation];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [self.mainClass instanceMethodSignatureForSelector:aSelector];
}

-(void)startForwarding:(id)sender
{
for (NSInvocation *invocation in self.invocations) {
[invocation setTarget:sender];
[invocation invoke];
}
}

IOS 5 unrecognized selector send at appearance proxy for setBackgroundImage

The method has a different name, you're missing a part of it. Since iOS 5 is still under NDA, I won't cite or link the method name here, but you can look it up yourself:

  • Go to the iOS Dev Center.
  • Select iOS SDK GM Seed.
  • Select iOS Developer Library.
  • In the search field, start typing UINavigationBar.
  • Click on the UINavigationBar Class Reference link.
  • Read the documentation.

appearance proxy not working as intended for UIButton font

The font name is wrong, it should be HelveticaNeue, without the space between.

In the future if you want to see other iOS font names you should check this website piece of code

EDIT

After a closer look I realized that you are trying to set the appearance of the button's title which is a UILabel, sadly UILabel doesn't have the font property in the UIAppearance proxy and that's why the font doesn't work.

How to create a UIAppearance proxy for an inherited view in MonoTouch?

Although there may be a simpler answer, I ended up inheriting from UIAppearance directly and copy-pasting relevant piece of UIButton and UIButton+UIButtonAppearance binding implementation from MonoDevelop C# disassembler and fixing internal fields here and there.

Inside BackButton:

static readonly IntPtr class_ptr = Class.GetHandle(typeof(BackButton));

public new static BackButtonAppearance Appearance
{
get {
return new BackButtonAppearance (Messaging.IntPtr_objc_msgSend (class_ptr, UIAppearance.SelectorAppearance));
}
}

public new static BackButtonAppearance AppearanceWhenContainedIn (params Type[] containers)
{
return new BackButtonAppearance (UIAppearance.GetAppearance (class_ptr, containers));
}

Nested BackButtonAppearance:

public class BackButtonAppearance : UIAppearance {
public BackButtonAppearance(IntPtr handle) : base(handle) { }

// These are copied from UIButton disassembly:

static readonly IntPtr selSetBackgroundImageForState_ = Selector.GetHandle ("setBackgroundImage:forState:");
static readonly IntPtr selSetTitleShadowColorForState_ = Selector.GetHandle ("setTitleShadowColor:forState:");
static readonly IntPtr selSetTitleColorForState_ = Selector.GetHandle ("setTitleColor:forState:");

// These are copied from UIButton+UIButtonAppearance disassembly,
// with internal UIButton.selSet* fields replaced by fields declared above.

[Export ("setBackgroundImage:forState:"), CompilerGenerated]
public virtual void SetBackgroundImage (UIImage image, UIControlState forState)
{
UIApplication.EnsureUIThread ();
if (this.IsDirectBinding) {
Messaging.void_objc_msgSend_IntPtr_UInt32 (base.Handle, selSetBackgroundImageForState_, (image != null) ? image.Handle : IntPtr.Zero, (uint)forState);
} else {
Messaging.void_objc_msgSendSuper_IntPtr_UInt32 (base.SuperHandle, selSetBackgroundImageForState_, (image != null) ? image.Handle : IntPtr.Zero, (uint)forState);
}
}


[Export ("setTitleColor:forState:"), CompilerGenerated]
public virtual void SetTitleColor (UIColor color, UIControlState forState)
{
UIApplication.EnsureUIThread ();
if (this.IsDirectBinding) {
Messaging.void_objc_msgSend_IntPtr_UInt32 (base.Handle, selSetTitleColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
} else {
Messaging.void_objc_msgSendSuper_IntPtr_UInt32 (base.SuperHandle, selSetTitleColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
}
}

[Export ("setTitleShadowColor:forState:"), CompilerGenerated]
public virtual void SetTitleShadowColor (UIColor color, UIControlState forState)
{
UIApplication.EnsureUIThread ();
if (this.IsDirectBinding) {
Messaging.void_objc_msgSend_IntPtr_UInt32 (base.Handle, selSetTitleShadowColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
} else {
Messaging.void_objc_msgSendSuper_IntPtr_UInt32 (base.SuperHandle, selSetTitleShadowColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
}
}
}

This allows me to bring my custom view to support UIAppearance-style API.

Custom UITableViewCell using UIAppearance proxy

Try creating custom appearance selector in UITableViewCell category. Here's an example implementation:

.h:

- (void)setAccessoryViewImage:(UIImage *)image UI_APPEARANCE_SELECTOR;

.m:

- (void)setAccessoryViewImage:(UIImage *)image {
self.accessoryView = [[UIImageView alloc] initWithImage:image];
}

And than you can configure all your cells using proxy object:

[[UITableViewCell appearance] setAccessoryViewImage:[UIImage imageNamed:@"table-arrow.png"]];


Related Topics



Leave a reply



Submit