Skip to content
This repository has been archived by the owner on Jul 14, 2020. It is now read-only.

Extending Glider

jonathanellis edited this page Mar 2, 2012 · 4 revisions

NOTICE: This document discusses functionality not yet possible within Pegasus. It's documented here so that you can begin to familiarise yourself with the SDK prior to release.


You will soon be able to extend Pegasus to support your own custom views and objects. For example, you might have a custom toolbar or image control that you've created that you want to be supported in Glider.

You should have a thorough understanding of Objective-C development and experience with Pegasus before proceeding, as this is considered an advanced tutorial.

As discussed in Getting-Started, Pegasus uses adapters to wrap UIKit classes so that they can be used within the Pegasus framework:

pegasus overall process

So getting your own view(s) to work with Pegasus is a two-step process:

  • Writing a view adapter for your view.
  • Making Pegasus aware of your new view adapter.

Writing a view adapter

Generally, your view adapter should subclass PGView. If your view is a direct subclass of an existing UIKit view (like UIToolbar or UIImageView), then your view adapter might as well subclass that class instead.

PGView implements the PGAdapter protocol, and it's this protocol that you should refer to in terms of what methods you need to provide in your adapter class.

We'll now go through them. We'll use an example here to remain consistent. We're going to suppose that your custom view is a UIView subclass called YYClock and so your view adapter is going to be called PGClock (subclass of PGView).

+ (id)internalViewWithAttributes:(NSDictionary *)attributes

This method is used to **instantiate ** your custom view, in this case YYClock. The method is also passed an NSDictionary of mappings of attribute names to values provided with your class.

The purpose of this method is only to do the absolute minimum amount of work necessary to instantiate your view. Most of these will provide a method body that is simply:

+ (id)internalViewWithAttributes:(NSDictionary *)attributes {
    return [ [YYClock alloc] init];
 }

This is generally fine. As you can see, you didn't need to use attributes here at all. The reason why the attributes are even given at all is that there are certain classes that might need to have access to some (or all) of their attributes before they can be created.

For example, UITableView needs to know its style at creation-time, and it can't be changed later. Therefore, the method implementation for PGTableView looks like this:

+ (id)internalViewWithAttributes:(NSDictionary *)attributes {
    NSString *tableViewStyleStr = [ [attributes objectForKey:@"style"] lowercaseString];
    UITableViewStyle tableViewStyle = [PGTranslators tableViewStyleWithString:tableViewStyleStr];      
    return [ [UITableView alloc] initWithFrame:CGRectZero style:tableViewStyle];
}

What's going on here is that the attributes are being queried and the style for the view is being applied at creation-time. Note that the frame has been given the value CGRectZero. It would be problematic to try and apply a different frame value here, or to extract it from the attributes: you should do absolutely the minimum set-up here that you can.

I know it's tempting to look at the attributes dictionary and think that it would be great to set up your whole view here. That would be a silly thing to do, because Pegasus can do all that for you, as we'll see shortly.

+ (NSString *)name

Simply return an NSString of the element name you want to associate with this class. It doesn't necessarily need to correlate to the class name, but it is usually the same, just in lowercase.

+ (NSString *)name {
    return @"clock";
}

+ (NSDictionary *)properties

Here's where a lot of the work gets done. From this method you need to return an NSDictionary mapping property names to their respective types.

Here's a simple example for the PGClock adapter:

+ (NSDictionary *)properties {
    
    NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                      @"NSDate", @"time", 
                                      @"UIColor", @"digitColor",
                                      nil];
    
    [properties addEntriesFromDictionary:[PGView properties]];
    
    return properties;
}

This specifies two properties: time of type NSDate and digitColor of type UIColor. Then, because this YYClock is a subclass of UIView it should also inherit all of UIView's properties, such as frame, etc. so you just need to add all the properties from the superclass. (The same goes if you subclassed PGImageView adapter for instance, you'd need to just add everything from [PGImageView properties], which in turn adds all the properties from [PGView properties]).

You should add a pair of strings for each non-read-only property your view has. (You should omit any read-only properties as these can't be set for obvious reasons!)

There are a couple of additional important rules you need to know about, as there are two special property types you might need to handle:

  • # is used to specify a virtual property. These are properties which don't map directly to properties on your view. More on this in a moment.
  • * is used to specify properties that should be ignored. An example: As we saw earlier with PGTableView, there are some cases in which some attributes/properties are applied at creation-time and handled manually. Therefore, if style was added as a property here, then Pegasus would try applying style again when processing the attributes, which is undesirable, as it was already set earlier and can't be changed. Alternatively, if we omitted it from here entirely, Pegasus would complain that an undefined attribute has been used. Therefore, the * type is tell Pegasus that this attribute exists, but is being handled manually and should be safely ignored.

- (void)setValue:(NSString *)string forVirtualProperty:(NSString *)propertyName

This method is used to manually apply virtual properties to your view. You should already have a rough idea of what virtual properties are: they are a Pegasus feature that allows you to specify attribute values for properties that don't actually exist.

For instance, UIButton doesn't have a property called title, but Pegasus uses virtual properties to synthesize a property called title which is then handled separately.

The relevant part of the code is as follows:-

- (void)setValue:(NSString *)string forVirtualProperty:(NSString *)property {
    [super setValue:string forVirtualProperty:property];
    
    UIButton *button = view;
    
    if ([property isEqualToString:@"title"]) {
        [button setTitle:string forState:UIControlStateNormal];
    }
}

Firstly, don't forget the called to super, then feel free to cast the view to the actual type to make things easier, then perform the necessary tasks in here.

The important thing is to set the property type to # in the properties dictionary in the previous method, so that the property is handled by this method and Pegasus doesn't try to apply it directly.

This method is optional, so if you don't need to specify any virtual properties, just skip it.

- (void)addSubview:(PGView *)subview

Don't be fooled by this one... addSubview: can do a lot more than just add a subview!

This method is important if you want to handle sub-elements in a special way. The default behaviour is that it's just created as a Pegasus view, then added as a subview using UIView's addSubview: method.

But sometimes, you might want to handle all or some of the sub-elements in a special way. For example, UIBarButtonItems need to be added to a UIToolbar's items property, not just added as a subview.

To handle this properly, you need to write some custom code to handle certain subviews. This is what's used for PGToolbar:

- (void)addSubview:(PGView *)subview {
    if ([subview isKindOfClass:[PGBarButtonItem class]]) {
        
        UIToolbar *toolbar = (UIToolbar *)view;

        toolbar.items = [toolbar.items arrayByAddingObject:subview.view];
        
    } else {
        [super addSubview:subview];
    }
}

As you can see, a check is made that if the "subview" is of type PGBarButtonItem then it should be added to the items array rather than than adding it as a subview. All other subviews are handled by super which just does the default behaviour of actually adding it as a subview.

This method is optional, so you only need to provide it if you intend to have special sub-elements that need to be handled in a special way. Otherwise, the default behaviour will be employed which adds sub-elements as subviews. You can also supply an empty method body which will simply throw away any subviews.

Adding your view adapter

Coming Soon