-
Notifications
You must be signed in to change notification settings - Fork 12
Extending Glider
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:
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.
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
).
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.
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";
}
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 withPGTableView
, there are some cases in which some attributes/properties are applied at creation-time and handled manually. Therefore, ifstyle
was added as a property here, then Pegasus would try applyingstyle
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.
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.
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, UIBarButtonItem
s 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.
Coming Soon