[iOS]文字列長にフォントサイズを合わせる
IULabelやUITextFieldのadjustsFontSizeToFitWidthプロパティをYESに設定すればいい。
その際、最小のフォントサイズを適切な値に設定するのが重要だと思うが、OS側で余りにも小さなサイズはと考えているのか、これ以上小さくならないサイズがあるようだ。
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[iOS]15パズル(3)
1個の駒の動作が完成すれば、15個も大丈夫はずだ。
ただ、駒に印がないと区別がつかないので、数字のラベルを貼付けた。
また、既に駒があるマスには移動しないように、マスにフラグを持たせた。でも、ちょっと、フラグの扱いが汚いね。今後の課題としよう。
駒に番号のラベルを貼付けた。
@implementation GamePieceView
...
- (void)_init
{
static NSInteger count = 0;
self.delegate = nil;
UILabel *label = [[UILabel alloc]
initWithFrame:CGRectMake(self.bounds.origin.x + 2.0,
self.bounds.origin.y + 2.0,
self.bounds.size.width - 4.0,
self.bounds.size.height - 4.0)];
label.text = [[NSString alloc] initWithFormat:@"%d", count++];
label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor orangeColor];
[self addSubview:label];
}
...
@end
マスにフラグを持たせた。
@interface GameSquare : NSObject
...
@property (nonatomic, assign) BOOL isEmpty;
...
@end
駒を15個配置。
@implementation GameBoardView
...
- (void)setupWithDelegate:(id)delegate
{
....
self.squaresArray = [[NSMutableArray alloc] init];
for (int i=0; i < 16; i++) {
GameSquare *square = [[GameSquare alloc] initWithFrame:rect[i]];
square.index = i;
if (i != 15) {
square.isEmpty = NO; ←フラグを設定
}
else {
square.isEmpty = YES; ←フラグを設定
}
[self.squaresArray addObject:square];
}
self.pieceViewArray = [[NSMutableArray alloc] init];
for (int i=0; i < 15; i++) { ←駒を15個配置。
GamePieceView *pieceView = [[GamePieceView alloc] initWithFrame:rect[i]];
pieceView.delegate = delegate;
[self addSubview:pieceView];
[self.pieceViewArray addObject:pieceView];
}
}
...
@end
異動先のマスに駒があったら、元に戻る。
@interface GameController ()
@property(nonatomic, weak) GameSquare *square;
...
@end
- (void)gameBoardViewTouchDown:(GameBoardView *)gameBoardView location:(CGPoint)touchPt taps:(int)taps event:(UIEvent*)event
{
GameSquare *square = [self.gameBoardView squareAtPoint:touchPt];
if (square) {
self.square = square; ←元のマスを覚えておく
}
....
}
- (void)gameBoardViewTouchUp:(GameBoardView *)gameBoardView location:(CGPoint)touchPt taps:(int)taps event:(UIEvent*)event
{
if (self.pieceView) {
GameSquare *square = [self.gameBoardView squareAtPoint:touchPt];
if (square.isEmpty) { ←駒がなかったら移動
[self.pieceView moveWithSquare:square];
self.square.isEmpty = YES;
square.isEmpty = NO;
}
else { ←元のマスに戻る
[self.pieceView moveWithSquare:self.square];
}
}
self.pieceView = nil;
}
...
@end
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/FifteenPuzzle - GitHub
関連情報
実践!iOSで作るゲームアプリ
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[iOS]15パズル(2)
駒の移動にアニメーションを施す。
前回のサンプル・コードでは、駒のフレームに、移動先のマスのフレームを設定していた。これをアニメーションに変更する。
ゲームコントローラで、駒の移動をGamePieceViewのメソッドmoveWithSquare:に変更する。
@implementation GameController
...
- (void)gameBoardViewTouchUp:(GameBoardView *)gameBoardView location:(CGPoint)touchPt taps:(int)taps event:(UIEvent*)event
{
if (self.pieceView) {
GameSquare *square = [self.gameBoardView squareAtPoint:touchPt];
[self.pieceView moveWithSquare:square]; ←変更
}
self.pieceView = nil;
}
...
@end
moveWithSquare:メソッドの処理内容は以下の通り。
@implementation GamePieceView
...
- (void)moveWithSquare:(GameSquare *)square
{
CGRect frame = [square frame];
[self moveFrame:frame];
}
- (void)moveFrame:(CGRect)frame
{
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
CGPoint fromPt = self.layer.position;
CGPoint toPt = CGPointMake(frame.origin.x + (frame.size.width / 2.0),
frame.origin.y + (frame.size.height / 2.0));
theAnimation.fromValue = [NSValue valueWithCGPoint:fromPt];
theAnimation.toValue = [NSValue valueWithCGPoint:toPt];
theAnimation.delegate = self;
[self.layer addAnimation:theAnimation forKey:@"animatePosition"];
self.layer.frame = frame;
}
...
@end
ドラッグして指を離すと、アニメーションしてマスに駒がはまるようになったと思う。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/FifteenPuzzle - GitHub
関連情報
実践!iOSで作るゲームアプリ
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[iOS]15パズル
画像を使ったサービスの一つとして15パズルというスライディング・ブロック・パズルの一種の制作を考えている。
実装方法については、Cocoa勉強会(関西)で知り合いましたSTUDIO SHIN北村真二さんの記事『実践!iOSで作るゲームアプリ』を参考にした。北村さん、ありがとう!
まず、クラス構造だが、当初は、MVCのモデルにあたるDocumentクラスは、コントローラにあたるViewControllerでゲームの制御を行う事を考えていたが、ゲーム本来の制御とアプリの枠側とは切り離したいだとか、iPhone/iPadの両方に対応させる場合、機器の違いを吸収するViewControllerには持たせない方がいいのでは?と考え、ゲーム専用のコントローラクラスを用意しることにした。結局、北村さんの記事のとおりという事になってしまった。
今回の発表で作成したのは、画面に16個のマスを表示し、1個の駒をドラッグで移動するというところまでだ。
ビューコントローラでゲームの制御を行うのでなく、ゲーム用のコントローラを用意することにしたので、ビューコントローラでは描画が必要になったらゲームコントローラを生成するだけだ。
@implementation ViewController
...
- (void)viewDidLoad
{
[super viewDidLoad];
self.gameController = [[GameController alloc] initWithView:(GameBoardView *)self.view];
}
...
@end
ゲーム盤に関係するビュー等の生成は、GameBoardViewクラスと考えていたが、実際にプログラミングしてみると、ビューからは他のビューの生成が完了したか分からない事に気がついた。なので、GameControllerがGameBoardViewに初期化のタイミングを教える事にした。
@implementation GameController
...
- (id)initWithView:(GameBoardView *)view
{
self = [super init];
if (self) {
self.gameBoardView = view;
[self.gameBoardView setupWithDelegate:self];
}
return self;
}
...
@end
GameBoardViewに対するユーザ操作はデリゲートでGameControllerに伝えられる。なので、タッチダウンされたら何がタッチされたのか覚えておいて、ドラッグ中は駒を移動させ、タッチアップで駒をマスの位置に移動させた。
@implementation GameController
...
- (void)gameBoardViewTouchDown:(GameBoardView *)gameBoardView location:(CGPoint)touchPt taps:(int)taps event:(UIEvent*)event
{
GameSquare *square = [self.gameBoardView squareAtPoint:touchPt];
GamePieceView *pieceView = [self.gameBoardView pieceViewAtPoint:touchPt];
if (pieceView) {
self.pieceView = pieceView;
self.startLocation = touchPt;
}
}
- (void)gameBoardViewTouchMove:(GameBoardView *)gameBoardView location:(CGPoint)touchPt taps:(int)taps event:(UIEvent*)event
{
if (self.pieceView) {
CGRect frame = [self.pieceView frame];
frame.origin.x += touchPt.x - self.startLocation.x;
frame.origin.y += touchPt.y - self.startLocation.y;
self.startLocation = touchPt;
[self.pieceView setFrame:frame];
}
}
- (void)gameBoardViewTouchUp:(GameBoardView *)gameBoardView location:(CGPoint)touchPt taps:(int)taps event:(UIEvent*)event
{
if (self.pieceView) {
GameSquare *square = [self.gameBoardView squareAtPoint:touchPt];
CGRect frame = [square frame];
[self.pieceView setFrame:frame];
}
self.pieceView = nil;
}
...
@end
15個の駒が全て揃った際は、タッチアップの際に、移動可能かどうかの判定をして、可能なら移動する。その際にアニメーションで、ということになる予定だ。
想定通り、マスが描画さいれているか確認するため、数字を印字しているが、反転してしまっている。デバッグ目的の簡易な実装なので、気にしないで欲しい。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/FifteenPuzzle - GitHub
関連情報
実践!iOSで作るゲームアプリ
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[Web]Ruby on Rails
以前、『[iOS][Web]iPhoneアプリケーションとサーバ間の通信』の回でRuby on Railsを取り上げたが、今回は、Railsそのものを取り上げる。
以前の回では、~/Doccuments/rails/配下に、workbookというアプリケーション環境を生成したが、今回は、その並びにweblogというアプリケーション環境を生成する。
$ cd ~/Doccuments/rails/
$ rails new weblog
$ cd weblog
$ ls
Gemfile
Gemfile.lock
README.rdoc
Rakefile
$ rake db:create
TOPページを静的コンテンツから、動的なものに変更する。
$ rails generate controller home index
$ vi app/views/home/index.html.erb
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<h1>Hello, Rails!</h1> ←追加
$ mv public/index.html public/old.index.html
$ vi config/routes.rb
Weblog::Application.routes.draw do
get "home/index"
root :to => 'home#index' ←追加
end
サーバ起動。
$ rails server
「http://localhost:3000/」にアクセスすると、「index.html.erb」に追加した文言が表示されているはずだ。
関連情報
Ruby on Rails Guides: Getting Started with Rails
実践 Ruby on Rails Webプログラミング入門?無駄なく迅速な開発環境
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[OSX][iOS]クラス拡張
そもそも、クラス宣言にPublic/Privateがあるのは何に対してか?という気がしてきたのだが、Objective-Cで非公開。C言語でいうところのモジュール(ファイル)に閉じた変数/関数を宣言するにはどうするのか?が今回のお話。
ヘッダーファイル。
@interface MyClass : NSObject
@property (nonatomic, strong) クラス名 *公開プロパティ;
@property (nonatomic, assign, readonly) float value;
- (void)公開メソッド;
@end
.mファイル。
@interface MyClass ()
@property (nonatomic, strong) クラス名 *非公開プロパティ;
@property (nonatomic, assign, readwrite) float value;
- (void)非公開メソッド;
@end
@implementation Lisp
@synthesiz 公開プロパティ = _公開プロパティ;
@synthesiz 非公開プロパティ = _非公開プロパティ;
@synthesiz value = _value;
- (void)公開メソッド
{
}
- (void)非公開メソッド
{
}
@end
非公開にしたいインスタンス変数、プロパティ、メソッドを.mファイル内の名前が空のカテゴリで宣言すれば、非公開のインスタンス変数、プロパティ、メソッドになる。その際、ヘッダーファイルではreadonlyで宣言し、内部ではreadwriteにするという事も出来る。
著者は、これを匿名(無名)カテゴリと思っていたのだが、正確にはクラス拡張と呼ぶらしい。
関連情報
The Objective-C Programming Language: Categories and Extensions
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[iOS]グラフ描画ライブラリ(5)
グラフ描画ライブラリSimpleChart。まだ手を加えたい箇所があるが終わらないので、今の状態でGitHubのREADMEを作成し、正式公開とする。
SimpleChartは、SimpleChartView.hとSimpleChartView.cの2つのファイルで構成されていて、公開しているプロジェクトがサンプル・プログラムになっているので、使い方はサンプル・コードで確認して欲しい。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/SimpleChart - GitHub
関連情報
http://code.google.com/p/s7graphview/
S7GraphViewのサイト。残念ながら、閉鎖されたようだ。
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[Web]Chrome Tech Talk Night #3
Chrome Tech Talk Night #3に参加してきた。
今回のChrome Tech Talk Night #3は、先日開催されたGoogle I/O 2012のChrome Extension関連を抜き出して、日本で再演する、という意図で開催されたイベントだ。
そもそも、Extensionsとは?だが、HTML/CSS/JavaScriptを使って、クライアント側のWebブラウザを拡張するモジュールの事で、著名なものとして、Safari ExtensionsとChrome Extensionsがある。
APIを統一して欲しい!という気持ちがあるが、近年のスマートフォンのネイティブ・アプリケーションのことを考えると、APIの統一は制作者側の問題で、ユーザにとってはどうでもよく、より良いユーザ体験を実現する為には、そのプラットフォームの能力を最大限発揮できるAPIが望ましいので、仕方がないのかな?
さて、今回のセッションの内容を簡単にまとめると、以下のとおり。
以前のExtensionsのAPIだと、普通のExtensions開発者へのセキュリティやリソース管理に対する負担が多きかったと考え、次のManifest Ver.2では改善する。ただし、徐々にだが来年、今のManifest Ver.1の対応は止める。つまり、動かなくなるという事。
これは、将来、スマートフォン版ChromeのExtensions対応も考慮に入れた判断。
次に、Chrome Appsが変わるというお話。開発中だが、ネイティブ・アプリと遜色ないChromeアプリケーションが開発できるように努力しているということ。
二つともだが、今回の変更等で都合が悪い開発者があったらフィードバックが欲しい。今後の開発の参考にしたいという事だ。
関連情報
[Web]Chrome Tech Talk Night #3 を開催します
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[OSX][iOS]デバッグ出力(トレース編)
先日のmosa entrance #21で思いついた事だ。
以前、C++を使っていた頃。そう、AT&Tからcfrontという名前でC言語のプリプロセッサとしてリリースされていた事の話。C++はnewしなくても変数宣言して使えて、この自動変数の寿命にタイミング、構築子/破滅子でデバッグ出力させる事によって、関数トレースをさせていた。
Objective-CのARCは、alloc/initされたクラスのインスタンスが、自動変数のような振る舞いをするので、もしかしたら、同じ事が出来るのではと思って、試してみた。
@interface DebugMessage : NSObject
- (id)initWithString:(NSString *)aString;
@end
@interface DebugMessage ()
@property (strong, nonatomic) NSString *dbgMsg;
@end
@implementation DebugMessage
@synthesize dbgMsg = _dbgMsg;
- (id)init
{
return [self initWithString:@""];
}
- (id)initWithString:(NSString *)aString
{
self = [super init];
if (self) {
self.dbgMsg = [[NSString alloc] initWithString:aString];
if (self.dbgMsg) {
NSLog(@"[BEGIN]%@", self.dbgMsg);
}
}
return self;
}
- (void)dealloc
{
if (self.dbgMsg) {
NSLog(@"[END]%@", self.dbgMsg);
}
self.dbgMsg = nil;
/* [super dealloc]; */
}
@end
#ifdef DEBUG
#define DBGMSG(...) NSLog(__VA_ARGS__)
#define TRC(aString) DebugMessage *dbgmsg = [[DebugMessage alloc] initWithString:aString]
#else /* DEBUG */
#define DBGMSG(...)
#define TRC(aString)
#endif /* DEBUG */
このTRC()マクロを以下のように宣言すれば!
- (void)viewDidLoad
{
TRC(@"ViewController # - viewDidLoad");
[super viewDidLoad];
}
-viewDidLoadメソッドの呼び出し前後で、トレースのメッセージが出力されている!
2012-07-12 19:29:46.204 DebugMessage[22897:f803] [BEGIN]ViewController # - viewDidLoad
2012-07-12 19:29:46.204 DebugMessage[22897:f803] [END]ViewController # - viewDidLoad
ただ、実際にサンプル・コードをビルドして実行すると分かるのだが、ARCが使えるビルド環境だと、マクロで宣言した変数dbgmsgが使われていないとか、だったら、メソッド呼び出しで変数は宣言しないようにしたら、今度は戻り値を受け取っていないとか、ビルド時に警告メッセージが出力されてしまう。
これは、大失敗のようだ。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/DebugMessage - GitHub
関連情報
Transitioning to ARC Release Notes
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
[iOS]データ管理(その4)
CoreDataのエンティティは、そのままの状態だと管理オブジェクトクラスで操作する事になる。
/* 値の取得 */
[管理オブジェクト valueForKey:@"キー名"]
/* 値の設定 */
[管理オブジェクト setValue:値 forKey:@"キー名"];
規模が大きくなり、これだと煩雑だったり、単純な属性の参照/設定以上の事を行いたい場合、カスタム管理オブジェクトクラスの作成を考えると思う。
サンプル・コードのエンティティ属性を読み書きしている箇所を以下のように変更する。
[newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];
↓
newManagedObject.timeStamp = [NSDate date];
cell.textLabel.text = [[object valueForKey:@"timeStamp"] description];
↓
cell.textLabel.text = [object.timeStamp description];
このカスタム管理オブジェクトクラスは、管理オブジェクトモデルのエンティティから生成という手段のみで作成される。つまり、新規作成のみで作成済みの場合は上書き生成となる。
もし、カスタム管理オブジェクトクラスに自分独自のカスタマイズを施している場合、上書き生成は困るのでどうすればいいのか?岸川さんの日記『CoreData の NSManagedObject のサブクラスを変更する場合はカテゴリを使うと便利』が参考になった。
例えば、カスタム管理オブジェクトクラスのファイル名が、Event.[hm] の場合、独自のカスタマイズ・コードをこのファイルに記述するのではなくて、例えば、Event_description.[hm] という名前のカテゴリを追加で作成する。
#import "Event.h"
@interface Event(description)
- (NSString *)description; /* override */
@end
@implementation Event(description)
- (NSString *)description
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy/MM/dd HH:mm:ss"];
NSString *str = [formatter stringFromDate:self.timeStamp];
return str;
}
@end
サンプルでは、EventクラスのtimeStampプロパティのdescriptionをセルに設定していたが、カテゴリでオーバーライドしたEventクラスのdescriptionを設定するように変更する。
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Event *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [object description];
}
実行。拡張した形式で表示されている。
ソースコード
GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/Datum - GitHub
関連情報
Core Data Programming Guide
CoreData の NSManagedObject のサブクラスを変更する場合はカテゴリを使うと便利
岸川さんの日記。参考になります!
【Cocoa練習帳】
http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)