Skip to content

Instantly share code, notes, and snippets.

@keicoder
Created March 10, 2014 00:43
Show Gist options
  • Save keicoder/9457498 to your computer and use it in GitHub Desktop.
Save keicoder/9457498 to your computer and use it in GitHub Desktop.
objective-c : Custom UITextView with JavaScript Core
//Custom UITextView with JavaScript Core
//1. Setting up the console
//ConsoleTextView.h
@interface ConsoleTextView : UITextView
- (void)setText:(NSString *)text concatenate:(BOOL)concatenate;
- (void)clear;
@end
//ConsoleTextView.m
@interface ConsoleTextView() <UITextViewDelegate>
@end
@implementation ConsoleTextView
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
//Initialization code
self.delegate = self;
[self configureTextView];
}
return self;
}
- (void)configureTextView {
self.textContainerInset = UIEdgeInsetsMake(0, 20, 0, 20); //top, left, bottom, right
self.textColor = [UIColor whiteColor];
self.font = [UIFont fontWithName:@"Courier" size:20];
}
- (void)setText:(NSString *)text concatenate:(BOOL)concatenate {
if (concatenate) {
text = [NSString stringWithFormat:@"\n%@", text];
self.text = [self.text stringByAppendingString:text];
}
else {
self.text = text;
}
self.scrollEnabled = NO;
[self.delegate textViewDidChange:self];
}
- (void)clear {
self.text = @"";
}
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView {
self.scrollEnabled = YES;
float margin = 0;
if (self.contentSize.height + margin > self.frame.size.height) {
CGPoint offset = CGPointMake(0, self.contentSize.height - self.frame.size.height + margin);
[self setContentOffset:offset animated:YES]; //sets the offset from the content view’s origin that corresponds to the receiver’s origin.
}
}
@end
//XorkViewController.h
@interface XorkViewController : UIViewController
@end
//XorkViewController.m
#import "XorkViewController.h"
#import "ConsoleTextView.h"
@interface XorkViewController () <UITextFieldDelegate>
@property (strong, nonatomic) IBOutlet ConsoleTextView *outputTextView;
@property (strong, nonatomic) IBOutlet UITextField *inputTextField;
@property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;
@end
@implementation XorkViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.inputTextField.delegate = self;
[self.inputTextField becomeFirstResponder];
UIFont *navBarFont = [UIFont fontWithName:@"Courier" size:23];
NSDictionary *attributes = @{NSFontAttributeName : navBarFont};
[self.navigationBar setTitleTextAttributes:attributes];
}
#pragma mark - UITextFielDelegate methods
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
//1
NSString* inputString = textField.text;
[inputString lowercaseString];
//2
if ([inputString isEqualToString:@"clear"]) {
[self.outputTextView clear];
//3
} else {
[self.outputTextView setText:inputString concatenate:YES]; //by default, the text view overwrites everything, Passing YES into setText:concatenate: preserves the text that was showing before
[self.inputTextField setText:@""];
}
return YES;
}
@end
//2. Printing to the console from JavaScript
//create a new JavaScript file in Xcode
//File > New > File.... > iOS > Other > Empty template > Name the file hello.js > Create
//in this case don't add hello.js to the Xork target
//project file > Build Phases > Copy Bundle Resources > add a file hello.js
//write JavaScript function
//hello.js
//JavaScript print() function will be defined with Objective-C code!
function startGame()
{
print("Hello World");
}
//XorkViewController.m
@import JavaScriptCore;
@interface XorkViewController () <UITextFieldDelegate>
//...
@property (strong, nonatomic) JSContext *context; //JSContext object as an execution environment to run JavaScript
@end
- (void)viewDidLoad {
[super viewDidLoad];
//below code runs the startGame method in hello.js, which will then in-turn call the print method you defined in an Objective-C block
//1. Grab the contents of hello.js and store it as a string.
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"hello"
ofType:@"js"];
NSString *scriptString = [NSString stringWithContentsOfFile:scriptPath
encoding:NSUTF8StringEncoding
error:nil];
//2. Initialize a JSContext instance to evaluate and run the string containing the script
self.context = [[JSContext alloc] init];
[self.context evaluateScript:scriptString];
//3. secret sauce. define the JavaScript function print() inside the JSContext
__weak XorkViewController *weakSelf = self; //weak reference to self to avoid a retain cycle
self.context[@"print"] = ^(NSString* text) {
text = [NSString stringWithFormat:@"%@\n", text];
[weakSelf.outputTextView setText:text concatenate:YES];
};
//4. Get a reference to the startGame function defined in hello.js
JSValue *function = self.context[@"startGame"];
[function callWithArguments:@[]]; //call it with an empty argument array since it doesn’t take any arguments
}
//3. Building Game Logic
//replace viewDidLoad method with the following
- (void)viewDidLoad {
[super viewDidLoad];
//below code runs the startGame method in hello.js, which will then in-turn call the print method you defined in an Objective-C block
//1. Grab the contents of .js, .json files and store it as a string, array.
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"xork"
ofType:@"js"];
NSString *scriptString = [NSString stringWithContentsOfFile:scriptPath
encoding:NSUTF8StringEncoding
error:nil];
NSString *dataPath = [[NSBundle mainBundle] pathForResource:@"data"
ofType:@"json"];
NSString *dataString = [NSString stringWithContentsOfFile:dataPath
encoding:NSUTF8StringEncoding
error:nil];
NSData *jsonData = [dataString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonData
options:0
error:&error];
if (error) {
NSLog(@"%@", @"NSJSONSerialization error");
return;
}
//2. Initialize a JSContext instance to evaluate and run the string containing the script
self.context = [[JSContext alloc] init];
[self.context evaluateScript:scriptString];
//3. secret sauce. define the JavaScript function print() inside the JSContext
__weak XorkViewController *weakSelf = self; //weak reference to self to avoid a retain cycle
self.context[@"print"] = ^(NSString* text) {
text = [NSString stringWithFormat:@"%@\n", text];
[weakSelf.outputTextView setText:text concatenate:YES];
};
//4. Get a reference to the startGame() function defined in xork.js
//startGame() takes an array of rooms.
//Convert the NSArray representation to a JavaScript object using valueWithObject:inContext:
//finally, call startGame() with callWithArguments: to start the game.
JSValue *function = self.context[@"startGame"];
JSValue *dataValue = [JSValue valueWithObject:jsonArray
inContext:self.context];
[function callWithArguments:@[dataValue]]; //call it with an argument array
}
#pragma mark - UITextFielDelegate methods
//replace [self.outputTextView setText:inputString concatenate:YES]; to the following code
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
//...
//3
} else {
[self processUserInput:inputString]; //app forwards the text input to the JavaScript function processUserInput()
//...
}
return YES;
}
//app forwards the text input to the JavaScript function processUserInput()
- (void)processUserInput:(NSString *)input
{
JSValue *function = self.context[@"processUserInput"];
JSValue *value = [JSValue valueWithObject:input inContext:self.context];
[function callWithArguments:@[value]];
}
//4. Extending the game logic
//xork.js
//function processCommand(action, object)
//add the following else-if statement immediately before the closing else statement
function processCommand(action, object) {
//...
else if (action == "take") {
take(object);
}
//...
}
//A global array map contains all the rooms passed into startGame(). A global index currentRoom tracks your position in the map array as you navigate the game. The function getCurrentRoom() simply returns the Room object you’re in according to the currentRoom index.
//The Room object contains a hasItem() function that indicates whether or not a particular item is in the room. If it is, add the Item object to the global inventory array and remove it from the Room.
//Alert the user if the item they want to take isn't in the room or doesn't exist.
function take(itemName) {
//1
var room = getCurrentRoom();
//2
if (room.hasItem(itemName)) {
item = room.itemForName(itemName);
inventory.addItem(item);
room.removeItem(itemName);
print("You picked it up. Woot!");
}
//3
else {
print("You can't pick that up.");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment