PaintCodeを買ってみたので、早速iOSアプリ用ボタンを作ってみました。
直感的に使用できるし、ボタン作成には丁度いいアプリでした。PDFで書き出すとAdobe Illustratorでレイヤーが保存された状態で利用できるので、効果をつけたりしたい場合はイラレに持っていくこともできます(それなら最初からイラレで作るって?)。やはりコードが出力されるのが魅力かな。
起動するとWindowが表示されます。

カンバスサイズを90×40に変更します。OS X/iOSのボタンをiOSに変更します。コードがiOS向けになります。原点表示も左上になります。

角丸長方形を描画。ストローク無しです。

#222222/#444444/#666666の色設定を追加。Hex Color Pickerで指定しました。

グラーションを追加。一番左が黒、左から2番目が#222222、3番目が#444444、一番右が#666666。Fill Styleに作成したグラデーションを指定。

Inner Shadowを作成して指定。

文字列を追加。

テキストにOuter Shadowを指定。

でき上がったコードがこれ。
//// General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
//// Color Declarations
UIColor* color222222 = [UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 1];
UIColor* color444444 = [UIColor colorWithRed: 0.27 green: 0.27 blue: 0.27 alpha: 1];
UIColor* color666666 = [UIColor colorWithRed: 0.4 green: 0.4 blue: 0.4 alpha: 1];
//// Gradient Declarations
NSArray* gradientColors = [NSArray arrayWithObjects:
(id)[UIColor blackColor].CGColor,
(id)[UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 1].CGColor,
(id)color222222.CGColor,
(id)color444444.CGColor,
(id)[UIColor colorWithRed: 0.33 green: 0.33 blue: 0.33 alpha: 1].CGColor,
(id)color666666.CGColor, nil];
CGFloat gradientLocations[] = {0, 0.25, 0.45, 0.51, 0.75, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)gradientColors, gradientLocations);
//// Shadow Declarations
CGColorRef innerShadow = color444444.CGColor;
CGSize innerShadowOffset = CGSizeMake(0, -0);
CGFloat innerShadowBlurRadius = 2;
CGColorRef textShadow = [UIColor blackColor].CGColor;
CGSize textShadowOffset = CGSizeMake(0, -0);
CGFloat textShadowBlurRadius = 3;
//// Abstracted Graphic Attributes
NSString* textContent = @"Touch!";
UIFont* textFont = [UIFont fontWithName: @"Helvetica-Bold" size: 18];
//// Rounded Rectangle Drawing
UIBezierPath* roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, 90, 40) cornerRadius: 4];
CGContextSaveGState(context);
[roundedRectanglePath addClip];
CGContextDrawLinearGradient(context, gradient, CGPointMake(45, 40), CGPointMake(45, -0), 0);
CGContextRestoreGState(context);
////// Rounded Rectangle Inner Shadow
CGRect roundedRectangleBorderRect = CGRectInset([roundedRectanglePath bounds], -innerShadowBlurRadius, -innerShadowBlurRadius);
roundedRectangleBorderRect = CGRectOffset(roundedRectangleBorderRect, -innerShadowOffset.width, -innerShadowOffset.height);
roundedRectangleBorderRect = CGRectInset(CGRectUnion(roundedRectangleBorderRect, [roundedRectanglePath bounds]), -1, -1);
UIBezierPath* roundedRectangleNegativePath = [UIBezierPath bezierPathWithRect: roundedRectangleBorderRect];
[roundedRectangleNegativePath appendPath: roundedRectanglePath];
roundedRectangleNegativePath.usesEvenOddFillRule = YES;
CGContextSaveGState(context);
{
CGFloat xOffset = innerShadowOffset.width + round(roundedRectangleBorderRect.size.width);
CGFloat yOffset = innerShadowOffset.height;
CGContextSetShadowWithColor(context,
CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
innerShadowBlurRadius,
innerShadow);
[roundedRectanglePath addClip];
CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(roundedRectangleBorderRect.size.width), 0);
[roundedRectangleNegativePath applyTransform: transform];
[[UIColor grayColor] setFill];
[roundedRectangleNegativePath fill];
}
CGContextRestoreGState(context);
//// Text Drawing
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, textShadowOffset, textShadowBlurRadius, textShadow);
CGRect textFrame = CGRectMake(5, 8, 80, 20);
[[UIColor whiteColor] setFill];
[textContent drawInRect: textFrame withFont: textFont lineBreakMode: UILineBreakModeWordWrap alignment: UITextAlignmentCenter];
CGContextRestoreGState(context);
//// Cleanup
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
node.js/npm/expressをHomebrewを使いつつインストール。
$ brew install node.js
$ curl http://npmjs.org/install.sh | sh
$ npm install express
$ ln -s /usr/local/node_modules/express/bin/express /usr/local/bin/express
FuelPHPでデフォルトタイムゾーンを設定するにはfuel/app/config/config.phpで
'default_timezone' => 'Asia/Tokyo'
のようにタイムゾーン名で設定する。
今日はFuelPHPを触ってみる。
2年以上前にKohanaをつかってWebサービス作ったり、Drupalのモジュール書いたりしてた。で、久しぶりにPHPでサーバアプリを作成することになった。で、どのフレームワークを使うか検討してみたんだけど、KohanaよりFuelPHPの方がよさそう。なので、FuelPHPを触って、ついでにPHPのリハビリ。
本当はRuby on Railsでやりたいんだけどね…
早速MAMPの更新。ここんところ使ってなかったので、バージョンがちょっと上がってた。
FuelPHPのインストール
$ curl get.fuelphp.com/oil | sh
oilという実行ファイルをインストール。oilを使ってプロジェクトを作成するらしい。oilは/usr/bin/oilにインストールされる。
プロジェクトを作成
と実行すると、プロジェクトに必要なファイルがダウンロードされ、プロジェクトが作成される。こんなディレクトリ構成。
.
├── docs
│ ├── assets
│ │ ├── css
│ │ ├── fonts
│ │ ├── img
│ │ └── js
│ ├── classes
│ │ ├── agent
│ │ ├── cache
│ │ ├── database
│ │ ├── file
│ │ ├── model_crud
│ │ ├── mongo
│ │ ├── session
│ │ ├── upload
│ │ └── validation
│ ├── general
│ │ └── controllers
│ ├── installation
│ ├── packages
│ │ ├── auth
│ │ ├── email
│ │ ├── oil
│ │ ├── orm
│ │ └── parser
│ └── templates
├── fuel
│ ├── app
│ │ ├── cache
│ │ ├── classes
│ │ ├── config
│ │ ├── lang
│ │ ├── logs
│ │ ├── migrations
│ │ ├── modules
│ │ ├── tasks
│ │ ├── tmp
│ │ ├── vendor
│ │ └── views
│ ├── core
│ │ ├── classes
│ │ ├── config
│ │ ├── lang
│ │ ├── tasks
│ │ ├── tests
│ │ ├── vendor
│ │ └── views
│ └── packages
│ ├── auth
│ ├── email
│ ├── oil
│ ├── orm
│ └── parser
└── public
└── assets
├── css
├── img
└── js
CodeIgniter系なのでKohanaとほぼ同じ(だっけ?)。
MAMPで表示するために
$ ln -s /Users/username/Documents/Workspace/MyToDo/public /Applications/MAMP/htdocs/mytodo
とシンボリックリンクを張る。MAMP(ポートを標準設定にしている)を起動して、http://localhost/mytodoにアクセスするとWelcomeページが表示された。

設定
まずDBの設定を行う。
MAMPのphpmyadminでデータベース「mytodo」を作成する。
fuel/app/config/development/db.phpを編集する。
dbname=mytodoとする。
return array(
'default' => array(
'connection' => array(
'dsn' => 'mysql:host=localhost;dbname=mytodo',
'username' => 'root',
'password' => 'root',
),
),
);
次にORMを利用するように設定する。fuel/app/config/config.phpを編集する。packagesでormがコメントアウトされているので有効にする。認証も行うので、authも入れる。
/**************************************************************************/
/* Always Load */
/**************************************************************************/
'always_load' => array(
'packages' => array(
'auth',
'orm',
),
また、同ファイルを以下のように変更する。
'whitelisted_classes' => array(
'Fuel\\Core\\Response',
'Fuel\\Core\\View',
'Fuel\\Core\\ViewModel',
'Fuel\Core\Validation',
'Closure',
)
続いて、認証におけるグループを設定する。fuel/packages/auth/config/simpleauth.phpを編集する。
'groups' => array(
-1 => array('name' => 'Banned', 'roles' => array('banned')),
0 => array('name' => 'Guests', 'roles' => array()),
1 => array('name' => 'Users', 'roles' => array('user')),
50 => array('name' => 'Moderators', 'roles' => array('user', 'moderator')),
100 => array('name' => 'Administrators', 'roles' => array('user', 'moderator', 'admin')),
),
同ファイルを編集してゲストログインができないように設定する。
ユーザの作成
ユーザモデルを作成する。
$ oil generate model users username:varchar[50] password:string group:int email:string last_login:int login_hash:string profile_fields:text
$ oil refine migrate
oilのコンソールモードでadminユーザを作成する。
$ oil console
Fuel 1.1 - PHP 5.3.6 (cli) (Sep 15 2011 11:18:57) [Darwin]
>>> Auth::create_user('admin', 'password', 'r.izumita@example.com', 100);
Warning - Cannot modify header information - headers already sent by (output started at /Users/rizumita/Documents/Workspace/MyToDo/fuel/core/classes/cli.php:127) in COREPATH/classes/cookie.php on line 95
1
>>> [ctrl-c]
コードの生成
oil genetare adminコマンドで認証付きのコードが生成される。ToDoモデルとToDoテーブル、コントローラ、ビューが作成される。
$ oil generate admin todos title:string summary:text due:datetime user_id:int
$ oil refine migrate
ブラウザでadmin/todosにアクセスすると認証を求められる。

上で設定したadmin/passwordでログインする。と、Dashboardが表示される。

ナビゲーションバーにTodosがあるのでクリックするとToDoの一覧ページが表示される。

Add new TodoボタンからToDo追加画面に遷移する。
とりあえず管理画面までできた。
ユーザの選択とか日付の入力のカスタマイズはおいておく。
Storyboardを利用している時に、起動時にNavigation Controllerの子ビューコントローラの構成を変更してみます。
まずStoryboardを以下のように作成します。

Initial view controllerをUINavigationController、FirstViewControllerをUINavigationControllerに接続、SecondViewControllerを別個作成しています。
SecondViewControllerには以下のようにIdentifierにSecondViewControllerを指定しています。

次にApplication Delegateの
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
メソッドを以下のように記述します。
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
UIViewController *secondViewController = [navigationController.storyboard instantiateViewControllerWithIdentifier:@"SecondViewController"];
[navigationController pushViewController:secondViewController animated:NO];
return YES;
StoryboardのInitial view controllerであるUINavigationControllerがwindowのrootViewControllerにセットされているので、それを取り出します。コントローラからStoryboardを取り出し、instantiateViewControllerWithIdentifierメソッドで指定したコントローラをインスタンス化します。上記コードではSecondViewControllerをインスタンス化しています。そのSecondViewControllerをUINavigationControllerにpushして、表示しています。
アプリを実行すると

このように、SecondViewControllerのビューが表示されます。
このようにinstantiateViewControllerWithIdentifierでインスタンス化することで、Segueで指定していないビューコントローラを利用することができます。
Storyboardではカスタマイズしたセグウェイを利用することができます。
UINavigationControllerでフリップで画面を切り替えるセグウェイを作ってみます。
まず、UIStoryboardSegueを継承したFlipSegueクラスを作成します。
次に、FlipSegueクラスに以下のコードを追加します。
- (void)perform {
UIViewController *sourceViewController = (UIViewController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *)self.destinationViewController;
[UIView transitionWithView:sourceViewController.navigationController.view
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[sourceViewController.navigationController pushViewController:destinationViewController animated:NO];
}
completion:nil];
}
ソースビューからデスティネーションビューにフリップアニメーションを行いつつ、Navigation Controllerでコントローラの切り替えをアニメーション無しで行っています。
そして、StoryboardでSegueをCustomに変更してFlipSegueを指定します。

Everword バージョン1.1を公開しました。
主な変更は共有ノートブックのサポートです。アプリ内課金時のみ共有ノートブックを利用できます。
XcodeでConvert to Objective-C ARCでARC化しようとした時に、Saveボタンを押しても変換されなかった。
スナップショットを作成できないのが問題らしく、調べたら.gitが邪魔らしい。.gitを外部に移動させるといいという情報にしたがって、.gitを移動して再度実行してみた。
が、失敗。Saveできない。
さらに検索すると、Developer Toolsをアンインストールして再度インストールするといいという情報が。試してみたが、やはりうまくいかない。
どうしようもなくなったが、ふと思い立って、試しにプロジェクトフォルダを他の場所に移動(この時はデスクトップ)に移動して、.gitを外部に出して、再度Saveしてみると成功。
何が悪かったか分からないが、とにかく変換できた。
Xcodeで.hファイルをimportすると、File not foundになって、でもビルドは出来る状態になることがある。
その場合は、当該ターゲットを表示して、Build PhasesのCompile Sourcesからその.hファイルの対になる.mファイルを削除して、再度追加すると直る。
今請け負っている仕事でViewを画像変換して保存するという仕様があるのだが、CALayerを3d transformしていて、いざrenderInContextで画像に変換しようとしたら、transformが反映されませんでした。ドキュメント見たら「layers that use 3D transforms are not rendered」なんて書かれてて、あと二日くらいでOpenGLで書き直しか?と非常にあせった。
で、3Dな変換は行っていなかったので、かわりにaffineTransformを使ってみたら無事変換できた。