Skip to content

Instantly share code, notes, and snippets.

@Shilo
Created May 24, 2011 05:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shilo/988167 to your computer and use it in GitHub Desktop.
Save Shilo/988167 to your computer and use it in GitHub Desktop.
Brian Ensor's extension for Sparrow's SPTextField class.
//
// SPTextField.h
// Sparrow
//
// Created by Daniel Sperl on 29.06.09.
// Copyright 2011 Gamua. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the Simplified BSD License.
//
#import <Foundation/Foundation.h>
#import "SPDisplayObjectContainer.h"
#import "SPMacros.h"
#import <UIKit/UIKit.h>
@class SPTexture;
@class SPQuad;
#define SP_DEFAULT_FONT_NAME @"Helvetica"
#define SP_DEFAULT_FONT_SIZE 14.0f
#define SP_DEFAULT_FONT_COLOR SP_BLACK
#define SP_NATIVE_FONT_SIZE -1.0f
#define SP_EVENT_TYPE_INPUT_BEGAN @"inputBegan"
#define SP_EVENT_TYPE_INPUT_ENDED @"inputFinished"
#define SP_EVENT_TYPE_INPUT_CANCELLED @"inputCancelled"
/// Horizontal Alignment
typedef enum
{
SPHAlignLeft = 0,
SPHAlignCenter,
SPHAlignRight
} SPHAlign;
/// Vertical Alignment
typedef enum
{
SPVAlignTop = 0,
SPVAlignCenter,
SPVAlignBottom
} SPVAlign;
/** ------------------------------------------------------------------------------------------------
An SPTextField displays text, either using standard iOS fonts or a custom bitmap font.
You can set all properties you are used to, like the font name and size, a color, the horizontal
and vertical alignment, etc. The border property is helpful during development, because it lets
you see the bounds of the textfield.
There are two types of fonts that can be displayed:
* Standard iOS fonts. This renders the text with standard iOS fonts like Verdana or Arial. Use this
method if you want to keep it simple, and if the text changes not too often. Simply pass the
font name to the corresponding property.
* Bitmap fonts. If you need speed or fancy font effects, use a bitmap font instead. That is a
font that has its glyphs rendered to a texture atlas. To use it, first register the font with
the method registerBitmapFontFromFile:, and then pass the font name to the corresponding
property of the text field.
For the latter, we recommend the Angel Code Bitmap Font Generator. Export the font data as an XML
file and the texture as a png with white characters on a transparent background (32 bit).
-> http://www.angelcode.com/products/bmfont/
Here is a sample with a standard font:
SPTextField *textField = [SPTextField textFieldWithWidth:300 height:100 text:@"Hello world!"];
textField.hAlign = SPHAlignCenter;
textField.vAlign = SPVAlignCenter;
textField.fontSize = 18;
textField.fontName = @"Georgia-Bold";
And now we use a bitmap font:
// Register the font; the returned font name is the one that is defined in the font XML.
NSString *fontName = [SPTextField registerBitmapFontFromFile:@"bitmap_font.fnt"];
SPTextField *textField = [SPTextField textFieldWithWidth:300 height:100 text:@"Hello world!"];
textField.fontName = fontName;
------------------------------------------------------------------------------------------------- */
@interface SPTextField : SPDisplayObjectContainer <UITextViewDelegate>
{
@private
float mFontSize;
uint mColor;
NSString *mText;
NSString *mFontName;
SPHAlign mHAlign;
SPVAlign mVAlign;
BOOL mBorder;
BOOL mRequiresRedraw;
BOOL mIsRenderedText;
BOOL mKerning;
SPQuad *mHitArea;
SPQuad *mTextArea;
SPDisplayObject *mContents;
UITextView *mDummyField;
BOOL mInputEnabled;
UIKeyboardType mKeyboardType;
UIReturnKeyType mReturnKeyType;
BOOL mFocus;
NSString *mCursor;
BOOL mCursorShowing;
NSTimer *mCursorTimer;
}
/// ------------------
/// @name Initializers
/// ------------------
/// Initialize a text field with all important font properties. _Designated Initializer_.
- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text fontName:(NSString*)name
fontSize:(float)size color:(uint)color;
/// Initialize a text field with default settings (Helvetica, 14pt, black).
- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text;
/// Initialize a 128x128 textField (Helvetica, 14pt, black).
- (id)initWithText:(NSString *)text;
/// Factory method.
+ (SPTextField *)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text
fontName:(NSString*)name fontSize:(float)size color:(uint)color;
/// Factory method.
+ (SPTextField *)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text;
/// Factory method.
+ (SPTextField *)textFieldWithText:(NSString *)text;
/// -------------
/// @name Methods
/// -------------
/// Makes a bitmap font available at any text field, manually providing the texture.
///
/// @return The name of the font as defined in the font XML.
+ (NSString *)registerBitmapFontFromFile:(NSString*)path texture:(SPTexture *)texture;
/// Makes a bitmap font available at any text field, using the texture defined in the file.
///
/// @return The name of the font as defined in the font XML.
+ (NSString *)registerBitmapFontFromFile:(NSString*)path;
/// Releases the bitmap font.
+ (void)unregisterBitmapFont:(NSString *)name;
/// ----------------
/// @name Properties
/// ----------------
/// The displayed text.
@property (nonatomic, copy) NSString *text;
/// The name of the font.
@property (nonatomic, copy) NSString *fontName;
/// The size of the font. For bitmap fonts, use `SP_NATIVE_FONT_SIZE` for the original size.
@property (nonatomic, assign) float fontSize;
/// The horizontal alignment of the text.
@property (nonatomic, assign) SPHAlign hAlign;
/// The vertical alignment of the text.
@property (nonatomic, assign) SPVAlign vAlign;
/// Allows displaying a border around the edges of the text field. Useful for visual debugging.
@property (nonatomic, assign) BOOL border;
/// The color of the text.
@property (nonatomic, assign) uint color;
/// The bounds of the actual characters inside the text field.
@property (nonatomic, readonly) SPRectangle *textBounds;
/// Allows using kerning information with a bitmap font (where available). Default is YES.
@property (nonatomic, assign) BOOL kerning;
@property (nonatomic, assign) BOOL inputEnabled;
@property (nonatomic) UIKeyboardType keyboardType;
@property (nonatomic) UIReturnKeyType returnKeyType;
@property (nonatomic, assign) BOOL focus;
@end
//
// SPTextField.m
// Sparrow
//
// Created by Daniel Sperl on 29.06.09.
// Copyright 2011 Gamua. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the Simplified BSD License.
//
#import "SPTextField.h"
#import "SPImage.h"
#import "SPTexture.h"
#import "SPSubTexture.h"
#import "SPGLTexture.h"
#import "SPEnterFrameEvent.h"
#import "SPQuad.h"
#import "SPBitmapFont.h"
#import "SPStage.h"
#import "SPTouchEvent.h"
#import <UIKit/UIKit.h>
static NSMutableDictionary *bitmapFonts = nil;
// --- private interface ---------------------------------------------------------------------------
@interface SPTextField()
- (void)redrawContents;
- (SPDisplayObject *)createRenderedContents;
- (SPDisplayObject *)createComposedContents;
@end
// --- class implementation ------------------------------------------------------------------------
@implementation SPTextField
@synthesize text = mText;
@synthesize fontName = mFontName;
@synthesize fontSize = mFontSize;
@synthesize hAlign = mHAlign;
@synthesize vAlign = mVAlign;
@synthesize border = mBorder;
@synthesize color = mColor;
@synthesize inputEnabled = mInputEnabled;
@synthesize keyboardType = mKeyboardType;
@synthesize returnKeyType = mReturnKeyType;
@synthesize focus = mFocus;
@synthesize kerning = mKerning;
- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text fontName:(NSString*)name
fontSize:(float)size color:(uint)color
{
if ((self = [super init]))
{
mText = [text copy];
mFontSize = size;
mColor = color;
mHAlign = SPHAlignCenter;
mVAlign = SPVAlignCenter;
mBorder = NO;
mKerning = YES;
self.fontName = name;
mHitArea = [[SPQuad alloc] initWithWidth:width height:height];
mHitArea.alpha = 0.0f;
[self addChild:mHitArea];
[mHitArea release];
mTextArea = [[SPQuad alloc] initWithWidth:width height:height];
mTextArea.visible = NO;
[self addChild:mTextArea];
[mTextArea release];
mRequiresRedraw = YES;
mInputEnabled = NO;
mInputEnabled = NO;
mDummyField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
mDummyField.delegate = self;
mDummyField.autocapitalizationType = UITextAutocapitalizationTypeNone;
mDummyField.autocorrectionType = UITextAutocorrectionTypeNo;
mDummyField.text = self.text;
mCursor = @"|";
[self addEventListener:@selector(addListener:) atObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
}
return self;
}
- (id)initWithWidth:(float)width height:(float)height text:(NSString*)text
{
return [self initWithWidth:width height:height text:text fontName:SP_DEFAULT_FONT_NAME
fontSize:SP_DEFAULT_FONT_SIZE color:SP_DEFAULT_FONT_COLOR];
}
- (id)initWithWidth:(float)width height:(float)height
{
return [self initWithWidth:width height:height text:@""];
}
- (id)initWithText:(NSString *)text
{
return [self initWithWidth:128 height:128 text:text];
}
- (id)init
{
return [self initWithText:@""];
}
- (void)addListener:(SPEvent *)event {
[self removeEventListener:@selector(addListener:) atObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
[self.stage.nativeView addSubview:mDummyField];
[self.stage addEventListener:@selector(onTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];
[self addEventListener:@selector(removeFromStageListener:) atObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
}
- (void)removeFromStageListener:(SPEvent *)event {
[self removeEventListener:@selector(removeFromStageListener:) atObject:self forType:SP_EVENT_TYPE_REMOVED_FROM_STAGE];
[self.stage removeEventListener:@selector(onTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];
[mDummyField removeFromSuperview];
[self addEventListener:@selector(addListener:) atObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
}
- (void)onTouch:(SPTouchEvent *)event {
if (mInputEnabled) {
SPTouch *touch = [[event touchesWithTarget:self andPhase:SPTouchPhaseEnded] anyObject];
if (touch) {
self.focus = YES;
return;
}
SPTouch *stageTouch = [[event touchesWithTarget:self.stage andPhase:SPTouchPhaseEnded] anyObject];
if (stageTouch) {
self.focus = NO;
}
}
}
- (void)animateCursor {
int index = [self.text length]-1;
if (index<0) index = 0;
if (mCursorShowing == YES) {
if ([[self.text substringFromIndex:index] isEqualToString:mCursor]) {
self.text = [[self.text substringToIndex:index] stringByAppendingString:@"."];
}
mCursorShowing = NO;
}
else {
if ([[self.text substringFromIndex:index] isEqualToString:@"."]) {
self.text = [self.text substringToIndex:index];
}
self.text = [self.text stringByAppendingString:mCursor];
mCursorShowing = YES;
}
}
- (void)setFocus:(BOOL)focus {
if (focus != mFocus) {
mFocus = focus;
if (mFocus) {
[mDummyField becomeFirstResponder];
[self dispatchEvent:[SPEvent eventWithType:SP_EVENT_TYPE_INPUT_BEGAN]];
self.text = [self.text stringByAppendingString:mCursor];
mCursorShowing = YES;
mCursorTimer = [[NSTimer scheduledTimerWithTimeInterval:.5 target:self selector:@selector(animateCursor) userInfo:nil repeats:YES] retain];
}
else {
[mCursorTimer invalidate];
[mCursorTimer release];
int index = [self.text length]-1;
if (index<0) index = 0;
if ([[self.text substringFromIndex:index] isEqualToString:mCursor]) {
self.text = [self.text substringToIndex:index];
mCursorShowing = NO;
}
[mDummyField resignFirstResponder];
[self dispatchEvent:[SPEvent eventWithType:SP_EVENT_TYPE_INPUT_CANCELLED]];
}
}
}
- (void)textViewDidChange:(UITextView *)textView {
self.text = textView.text;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:@"\n"]) {
if (mDummyField.returnKeyType != UIReturnKeyDefault) {
[self setFocus:NO];
}
}
return YES;
}
- (void)setReturnKeyType:(UIReturnKeyType)type {
mDummyField.returnKeyType = type;
}
- (UIReturnKeyType)returnKeyType {
return mDummyField.returnKeyType;
}
- (void)setKeyboardType:(UIKeyboardType)type {
mDummyField.keyboardType = type;
}
- (UIKeyboardType)keyboardType {
return mDummyField.keyboardType;
}
- (void)render:(SPRenderSupport *)support
{
if (mRequiresRedraw) [self redrawContents];
[super render:support];
}
- (void)redrawContents
{
[mContents removeFromParent];
mContents = mIsRenderedText ? [self createRenderedContents] : [self createComposedContents];
mContents.touchable = NO;
mRequiresRedraw = NO;
[self addChild:mContents];
}
- (SPDisplayObject *)createRenderedContents
{
float width = mHitArea.width;
float height = mHitArea.height;
float fontSize = mFontSize == SP_NATIVE_FONT_SIZE ? SP_DEFAULT_FONT_SIZE : mFontSize;
UILineBreakMode lbm = UILineBreakModeTailTruncation;
CGSize textSize = [mText sizeWithFont:[UIFont fontWithName:mFontName size:fontSize]
constrainedToSize:CGSizeMake(width, height) lineBreakMode:lbm];
float xOffset = 0;
if (mHAlign == SPHAlignCenter) xOffset = (width - textSize.width) / 2.0f;
else if (mHAlign == SPHAlignRight) xOffset = width - textSize.width;
float yOffset = 0;
if (mVAlign == SPVAlignCenter) yOffset = (height - textSize.height) / 2.0f;
else if (mVAlign == SPVAlignBottom) yOffset = height - textSize.height;
mTextArea.x = xOffset;
mTextArea.y = yOffset;
mTextArea.width = textSize.width;
mTextArea.height = textSize.height;
SPTexture *texture = [[SPTexture alloc] initWithWidth:width height:height
scale:[SPStage contentScaleFactor]
colorSpace:SPColorSpaceAlpha
draw:^(CGContextRef context)
{
if (mBorder)
{
CGContextSetGrayStrokeColor(context, 1.0f, 1.0f);
CGContextSetLineWidth(context, 1.0f);
CGContextStrokeRect(context, CGRectMake(0.5f, 0.5f, width-1, height-1));
}
CGContextSetGrayFillColor(context, 1.0f, 1.0f);
[mText drawInRect:CGRectMake(0, yOffset, width, height)
withFont:[UIFont fontWithName:mFontName size:fontSize]
lineBreakMode:lbm alignment:mHAlign];
}];
SPImage *image = [SPImage imageWithTexture:texture];
image.color = mColor;
[texture release];
return image;
}
- (SPDisplayObject *)createComposedContents
{
SPBitmapFont *bitmapFont = [bitmapFonts objectForKey:mFontName];
if (!bitmapFont)
[NSException raise:SP_EXC_INVALID_OPERATION
format:@"bitmap font %@ not registered!", mFontName];
SPDisplayObject *contents = [bitmapFont createDisplayObjectWithWidth:mHitArea.width
height:mHitArea.height text:mText fontSize:mFontSize color:mColor
hAlign:mHAlign vAlign:mVAlign border:mBorder kerning:mKerning];
SPRectangle *textBounds = [(SPDisplayObjectContainer *)contents childAtIndex:0].bounds;
mTextArea.x = textBounds.x; mTextArea.y = textBounds.y;
mTextArea.width = textBounds.width; mTextArea.height = textBounds.height;
return contents;
}
- (SPRectangle *)textBounds
{
if (mRequiresRedraw) [self redrawContents];
return [mTextArea boundsInSpace:self.parent];
}
- (SPRectangle*)boundsInSpace:(SPDisplayObject*)targetCoordinateSpace
{
return [mHitArea boundsInSpace:targetCoordinateSpace];
}
- (void)setWidth:(float)width
{
// other than in SPDisplayObject, changing the size of the object should not change the scaling;
// changing the size should just make the texture bigger/smaller,
// keeping the size of the text/font unchanged. (this applies to setHeight:, as well.)
mHitArea.width = width;
mRequiresRedraw = YES;
}
- (void)setHeight:(float)height
{
mHitArea.height = height;
mRequiresRedraw = YES;
}
- (void)setText:(NSString *)text
{
if (![text isEqualToString:mText])
{
[mText release];
mText = [text copy];
mRequiresRedraw = YES;
}
}
- (void)setFontName:(NSString *)fontName
{
if (![fontName isEqualToString:mFontName])
{
[mFontName release];
mFontName = [fontName copy];
mRequiresRedraw = YES;
mIsRenderedText = ![bitmapFonts objectForKey:mFontName];
}
}
- (void)setFontSize:(float)fontSize
{
if (fontSize != mFontSize)
{
mFontSize = fontSize;
mRequiresRedraw = YES;
}
}
- (void)setBorder:(BOOL)border
{
if (border != mBorder)
{
mBorder = border;
mRequiresRedraw = YES;
}
}
- (void)setHAlign:(SPHAlign)hAlign
{
if (hAlign != mHAlign)
{
mHAlign = hAlign;
mRequiresRedraw = YES;
}
}
- (void)setVAlign:(SPVAlign)vAlign
{
if (vAlign != mVAlign)
{
mVAlign = vAlign;
mRequiresRedraw = YES;
}
}
- (void)setColor:(uint)color
{
if (color != mColor)
{
mColor = color;
if (mIsRenderedText)
[(SPImage *)mContents setColor:color];
else
mRequiresRedraw = YES;
}
}
- (void)setKerning:(BOOL)kerning
{
if (kerning != mKerning)
{
mKerning = kerning;
mRequiresRedraw = YES;
}
}
+ (SPTextField*)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text
fontName:(NSString*)name fontSize:(float)size color:(uint)color
{
return [[[SPTextField alloc] initWithWidth:width height:height text:text fontName:name
fontSize:size color:color] autorelease];
}
+ (SPTextField*)textFieldWithWidth:(float)width height:(float)height text:(NSString*)text
{
return [[[SPTextField alloc] initWithWidth:width height:height text:text] autorelease];
}
+ (SPTextField*)textFieldWithText:(NSString*)text
{
return [[[SPTextField alloc] initWithText:text] autorelease];
}
+ (NSString *)registerBitmapFontFromFile:(NSString*)path texture:(SPTexture *)texture
{
if (!bitmapFonts) bitmapFonts = [[NSMutableDictionary alloc] init];
SPBitmapFont *bitmapFont = [[SPBitmapFont alloc] initWithContentsOfFile:path texture:texture];
NSString *fontName = bitmapFont.name;
[bitmapFonts setObject:bitmapFont forKey:fontName];
[bitmapFont release];
return fontName;
}
+ (NSString *)registerBitmapFontFromFile:(NSString *)path
{
return [SPTextField registerBitmapFontFromFile:path texture:nil];
}
+ (void)unregisterBitmapFont:(NSString *)name
{
[bitmapFonts removeObjectForKey:name];
if (bitmapFonts.count == 0)
{
[bitmapFonts release];
bitmapFonts = nil;
}
}
- (void)dealloc
{
if ([self hasEventListenerForType:SP_EVENT_TYPE_ADDED_TO_STAGE]) {
[self removeEventListener:@selector(addListener:) atObject:self forType:SP_EVENT_TYPE_ADDED_TO_STAGE];
}
if (mDummyField) {
mDummyField.delegate = nil;
[mDummyField resignFirstResponder];
[mDummyField removeFromSuperview];
[mDummyField release];
}
[mText release];
[mFontName release];
[super dealloc];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment