iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
Android Develpersサイトで、ADT (Android Developers Tools) が組み込まれたEclipseを配流するようになったようだ。EclipseはAndroid開発用だけで使用する場合は便利だと思う。
以前と異なるので戸惑ったが、OS Xの場合、adt-bundle-mac-x86_64というフォルダがダウンロードされる為、このフォルダを任意の場所に置けばインストールは完了だ。(著者は、間違えてEclipseのサイトにいって、Eclipseを単体でダウンロードしてしまったが不要だった。)
adt-bundle-mac-x86_64フォルダには、Eclipseが格納されているので、それを起動する。
そして、ADT Pluginを組み込む。ここは、以前と同じだと思う。
次は、新規プロジェクトを作成して、エミュレータで動作させてみよう。
Android Virtual Device Managerが更新されていたのは驚いた。
CPUが選べるようになっている。
今回で、『Core Animation for Max OS X and the iPhone』の『CHAPTER 5. LAYER-BACKED VIEWS』からのサンプルを終えようと思う。。
ビューの透明度はプロパティで変更できる。
self.pentagonImageView.alphaValue = 0.5;
self.starImageView.alphaValue = 0.5;
角度も同様だ。
CGFloat rotate = self.pentagonImageView.frameCenterRotation;
self.starImageView.frameCenterRotation = rotate + 15.0;
以前のサンプルから色々と変わっているので、再度、全ソースを載せる。
@interface BaseView ()
@property (strong, nonatomic) NSImageView *mover;
@property (assign, nonatomic) NSRect leftFramePosiotion;
@property (assign, nonatomic) NSRect rightFramePosiotion;
@property (assign, nonatomic) BOOL isRight;
@property (assign, nonatomic) CGMutablePathRef heartPath;
@property (strong, nonatomic) NSImageView *pentagonImageView;
@property (strong, nonatomic) NSImageView *starImageView;
- (void)initializeFramePositions;
- (void)addImageToSubview;
- (void)move;
- (CAKeyframeAnimation *)opacityAnimation;
- (void)applyShadow:(NSView *)view;
@end
@implementation BaseView
@synthesize mover = _mover;
@synthesize leftFramePosiotion = _leftFramePosiotion;
@synthesize rightFramePosiotion = _rightFramePosiotion;
@synthesize isRight = _isRight;
@synthesize heartPath = _heartPath;
@synthesize pentagonImageView = _pentagonImageView;
@synthesize starImageView = _starImageView;
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initializeFramePositions];
[self addImageToSubview];
[self addSubview:self.mover];
[self addSubview:self.pentagonImageView];
[self setWantsLayer:YES];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self initializeFramePositions];
[self addImageToSubview];
[self addSubview:self.mover];
[self addSubview:self.pentagonImageView];
[self setWantsLayer:YES];
}
return self;
}
+ (id)defaultAnimationForKey:(NSString *)key
{
DBGMSG(@"%s, key:%@", __func__, key);
id result = [super defaultAnimationForKey:key];
DBGMSG(@"%s, animation:%@", __func__, result);
return result;
}
- (id)animationForKey:(NSString *)key
{
DBGMSG(@"%s, key:%@", __func__, key);
id result = [super animationForKey:key];
DBGMSG(@"%s, animation:%@", __func__, result);
return result;
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (void)mouseDown:(NSEvent*)theEvent
{
[self move];
}
- (void)initializeFramePositions
{
CGFloat frameX = NSWidth(self.frame);
CGFloat frameY = NSHeight(self.frame);
self.leftFramePosiotion = NSMakeRect(0.0, 0.0, frameX / 4.0, frameY / 4.0);
self.rightFramePosiotion = NSMakeRect(7.0 * frameX / 8.0, 7.0 * frameY / 16.0, frameX / 8.0, frameY / 8.0);
CGFloat xInset = 3.0 * (NSWidth(self.frame) / 8.0);
CGFloat yInset = 3.0 * (NSHeight(self.frame) / 8.0);
NSRect moverFrame = NSInsetRect(self.frame, xInset, yInset);
self.mover = [[MyImageView alloc] initWithFrame:moverFrame];
self.isRight = NO;
self.heartPath = NULL;
self.animations = [NSDictionary dictionaryWithObjectsAndKeys:
[self transitionAnimation], @"subviews",
nil];
self.pentagonImageView = [[MyImageView alloc] initWithFrame:NSMakeRect(self.frame.origin.x + 20.0,
self.frame.origin.y + 20.0,
self.frame.size.width - 40.0,
self.frame.size.height - 40.0)];
self.starImageView = [[MyImageView alloc] initWithFrame:NSMakeRect(self.frame.origin.x + 20.0,
self.frame.origin.y + 20.0,
self.frame.size.width - 40.0,
self.frame.size.height - 40.0)];
self.pentagonImageView.alphaValue = 0.5;
self.starImageView.alphaValue = 0.5;
[self applyShadow:self.pentagonImageView];
[self applyShadow:self.starImageView];
}
- (void)addImageToSubview
{
[self.mover setImageScaling:NSScaleToFit];
[self.mover setImage:[NSImage imageNamed:@"snapshot.jpg"]];
[self.pentagonImageView setImageScaling:NSScaleToFit];
[self.pentagonImageView setImage:[NSImage imageNamed:@"pentagon.png"]];
[self.starImageView setImageScaling:NSScaleToFit];
[self.starImageView setImage:[NSImage imageNamed:@"star.png"]];
}
- (void)move
{
if (nil != [self.pentagonImageView superview]) {
CGFloat rotate = self.pentagonImageView.frameCenterRotation;
self.starImageView.frameCenterRotation = rotate + 15.0;
[[self animator] replaceSubview:self.pentagonImageView with:self.starImageView];
}
else if (nil != [self.starImageView superview]) {
CGFloat rotate = self.starImageView.frameCenterRotation;
self.pentagonImageView.frameCenterRotation = rotate + 15.0;
[[self animator] replaceSubview:self.starImageView with:self.pentagonImageView];
}
}
- (CAKeyframeAnimation *)opacityAnimation
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"alphaValue"];
animation.duration = 4.0;
animation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.75],
[NSNumber numberWithFloat:0.0], nil];
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.25],
[NSNumber numberWithFloat:0.50],
[NSNumber numberWithFloat:0.75], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil];
return animation;
}
- (CABasicAnimation *)opacityAnimationBasic
{
CABasicAnimation *animation = [CABasicAnimation animation];
animation.duration = 4.0;
animation.fromValue = [NSNumber numberWithFloat:1.0];
animation.toValue = [NSNumber numberWithFloat:0.0];
return animation;
}
- (CAKeyframeAnimation *)originAnimation
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"frameOrigin"];
animation.duration = 4.0;
animation.path = self.heartPath;
animation.calculationMode = kCAAnimationPaced;
return animation;
}
- (CGMutablePathRef)heartPath
{
if (! _heartPath) {
NSRect frame = self.mover.frame;
_heartPath = CGPathCreateMutable();
CGPathMoveToPoint(_heartPath, NULL, NSMinX(frame), NSMinY(frame));
CGPathAddLineToPoint(_heartPath, NULL,
NSMinX(frame) - NSWidth(frame),
NSMinY(frame) + NSHeight(frame) * 0.85);
CGPathAddLineToPoint(_heartPath, NULL,
NSMinX(frame),
NSMinY(frame) - NSHeight(frame) * 1.5);
CGPathAddLineToPoint(_heartPath, NULL,
NSMinX(frame) + NSWidth(frame),
NSMinY(frame) + NSHeight(frame) * 0.85);
CGPathAddLineToPoint(_heartPath, NULL,
NSMinX(frame),
NSMinY(frame));
CGPathCloseSubpath(_heartPath);
}
return _heartPath;
}
- (CAAnimationGroup *)groupAnimation
{
CAAnimationGroup *animation = [CAAnimationGroup animation];
animation.animations = [NSArray arrayWithObjects:
[self opacityAnimation],
[self originAnimation], nil];
animation.duration = 4.0;
return animation;
}
- (CATransition *)transitionAnimation
{
CATransition *animation = [CATransition animation];
animation.type = kCATransitionMoveIn;
animation.subtype = kCATransitionFromTop;
animation.timingFunction = [self getTimingFunction];
return animation;
}
- (CAMediaTimingFunction *)getTimingFunction
{
CGFloat c1x = 0.5;
CGFloat c1y = 1.0;
CGFloat c2x = 0.5;
CGFloat c2y = 0.0;
return [[CAMediaTimingFunction alloc] initWithControlPoints:c1x :c1y :c2x :c2y];
}
- (void)applyShadow:(NSView *)view
{
NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowOffset:NSMakeSize(10.0, -10.0)];
[shadow setShadowBlurRadius:10.0];
[shadow setShadowColor:[NSColor blackColor]];
[view setShadow:shadow];
}
@end
Xcodeでデバッグしていて不満があるのは、デバッグ実行すると、前のログがクリアされてしまう事だ。このログの保存を上手くやれる方法がないか調べて見つけたのが、ASL (Apple System Logger) API。ログの出力方法は以下のとおり。
#import <asl.h>
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool {
aslclient log_client;
log_client = asl_open("bitz-asl", "Bitz ASL", ASL_OPT_STDERR);
asl_log(log_client, NULL, ASL_LEVEL_EMERG, "%s", "Hello, World!");
asl_close(log_client);
}
return 0;
}
ASLの出力内容は、Xcodeのコンソールにも出力されるので、NSLogと似た使い方も出来る。
Feb 4 21:59:35 macintoshclassic.local bitz-asl[1481] : Hello, World!
また、Syslogを直接参照する方法もできる。
$ grep bitz-asl system.log
Feb 4 21:59:35 macintoshclassic bitz-asl[1481]: Hello, World!
『Core Animation for Max OS X and the iPhone』によると /Developer/Extras/Core Image/ CI Filter Browser widget (CI Filter Browser.wdgt) が存在するということだが、Xcodeがアプリケーション一体型になったのでDownloads for Apple Developerサイトを確認したところ、Graphics Tools for Xcodeに含まれているということでダウンロードしてみたのだが、含まれていなかった。無くなったのかな?なくても進められるので気にしない事にする。
Core Imageのフィルターの例として、ポインティライズ(CIPointillizeフィルタ)が紹介されていたので、それを前回までのサンプルに組み込んでみる。
文書によると、CIPointillizeには以下のパラメータが存在するようだ。
inputImage: 対象となるCIImageインスタンス
inputRadius: 矩形セルおよびドットのサイズ(1.0〜100.0)。
inputCenter: 矩形セルの底辺を指定する値。
inputRadiusの意味はなんとなく分かるのだが、inputCenterがよく分からない。ただ、サンプルでは、対象の中心を指定しているので、真似る事にする。
フィルタの登録は、以下のとおり。
- (void)pointillize
{
CIVector *center = [CIVector vectorWithX:NSMidX([self bounds])
Y:NSMidY([self bounds])];
CIFilter *pointillize = [CIFilter filterWithName:@"CIPointillize"
keysAndValues:kCIInputRadiusKey,
[NSNumber numberWithFloat:1.0],
kCIInputCenterKey,
center, nil];
pointillize.name = @"pointillize";
[self setContentFilters:[NSArray arrayWithObjects:pointillize, nil]];
}
マウス押下されるとフィルタが掛かるようにした。
- (void)move
{
if (nil != [self.pentagonImageView superview]) {
[[self animator] replaceSubview:self.pentagonImageView with:self.starImageView];
[self pointillize];
NSString *path = [NSString stringWithFormat:
@"contentFilters.pointillize.%@", kCIInputRadiusKey];
[self setValue:[NSNumber numberWithInt:10.0f] forKeyPath:path];
}
else if (nil != [self.starImageView superview]) {
[[self animator] replaceSubview:self.starImageView with:self.pentagonImageView];
[self pointillize];
NSString *path = [NSString stringWithFormat:
@"contentFilters.pointillize.%@", kCIInputRadiusKey];
[self setValue:[NSNumber numberWithInt:1.0f] forKeyPath:path];
}
}
フィルタは、ビューのcontentFiltersに登録され、フィルタの名前はpointillizeに、変化させたいのはkCIInputRadiusKeyなので、これを指すパスで値を変更するとフィルタが掛かるということのようだ。
泡のような感じになった。
使えるデータベースはSQLiteのみのサイトをINTER-Mediatorで構築してみる。
ツールとして、Coda2とDiet Codaを使ってみたのだが、iPad miniでPHPが編集できるなんて、素晴らしい世の中になったものだ。
SQLiteのデータベースファイルは、事前に生成した物をサイトにアップする事にしたので、スキーム・ファイルの設計が必要だ。
CREATE TABLE person (
id INTEGER PRIMARY KEY AUTOINCREMENT,
membership_number TEXT,
name TEXT
);
CREATE UNIQUE INDEX person_id ON person (id);
INSERT INTO person(id,membership_number,name) VALUES (1,'012345','Yukio Murakami');
INSERT INTO person(id,membership_number,name) VALUES (2,'000002','Someone');
INSERT INTO person(id,membership_number,name) VALUES (3,'000003','Anyone');
CREATE TABLE attendance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
person_id INTEGER,
class_name TEXT,
date DATE
);
CREATE UNIQUE INDEX attendance_id ON attendance (id);
CREATE INDEX attendance_person_id ON attendance (person_id);
INSERT INTO attendance (person_id,class_name,date) VALUES (1,'General','2013-1-6');
INSERT INTO attendance (person_id,class_name,date) VALUES (1,'General','2013-1-11');
INSERT INTO attendance (person_id,class_name,date) VALUES (1,'Special','2013-1-12');
スキーム・ファイルの名前は、rollbook_schema_sqlite.txtとした。
次は、データベースファイルを生成。
$ mkdir db
$ mkdir db/im
$ sudo sqlite3 -init rollbook_schema_sqlite.txt db/im/rollbook.sq3
:
sqlite> .quit
$ sudo chown _www db/im
$ sudo chown _www db/im/rollbook.sq3
定義ファイルは以下のとおり。
< 1,
'paging' => true,
'name' => 'person',
'key' => 'id',
'query' => array( /* array( 'field'=>'id', 'value'=>'5', 'operator'=>'eq' ),*/),
'sort' => array(array('field' => 'id', 'direction' => 'asc'),),
'repeat-control' => 'insert delete',
),
array(
'name' => 'attendance',
'key' => 'id',
'relation' => array(
array('foreign-key' => 'person_id', 'join-field' => 'id', 'operator' => '=')
),
'repeat-control' => 'insert delete',
),
),
array(
'formatter' => array(),
'aliases' => array(
'attendanceid' => 'attendance@person_id@value',
'attendancename' => 'attendance@name_person@innerHTML',
),
),
array(
'db-class' => 'PDO',
'dsn' => 'sqlite:/それぞれの環境のパスを記述/db/im/rollbook.sq3',
),
0
);
?>
実は、第二引数のaliasesに何を指定したらいいのか分かっていない。nullを指定すると上手く動かなかったので、サンプルを見よう見まねで真似た。
ページファイルは以下のとおり。
<!DOCTYPE html>
<!--
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="rollbook.css"/>
<title>出欠簿</title>
<script src="include.php"></script>
<script type="text/javascript">
window.onload = function() {
var nodeUnsupport = document.getElementById('nonsupportmessage');
if (INTERMediatorOnPage.INTERMediatorCheckBrowser(nodeUnsupport)) {
INTERMediator.construct(true);
}
}
</script>
</head>
<body>
<div id="nonsupportmessage" style="background-color:#333333">
<div style="text-align:center;color:yellow">
If you see this, you must use any unsupported web browser.
Or you should set to active for JavaScript.
In case of generating the page, Please wait a while.
</div>
<div style="text-align:center;color:yellow">
この表示が見えている場合、非対応ブラウザで参照されています。
あるいは、JavaScriptを使用しないように設定されています。
描画処理中の場合はしばらくお待ちください。
</div>
</div>
<div id="IM_NAVIGATOR">Navigation Controls by INTER-Mediator</div>
<table border="1">
<tbody>
<tr>
<th>id</th>
<td>
<div class="IM[person@id]"></div>
</td>
</tr>
<tr>
<th>membership#</th>
<td>
<input type="text" class="IM[person@membership_number]" value=""/>
</td>
</tr>
<tr>
<th>name</th>
<td>
<input type="text" class="IM[person@name]" value=""/>
</td>
</tr>
<tr>
<td colspan="2">
<table border="1">
<thead>
<tr>
<th>class_name</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" class="IM[attendance@class_name]"/></td>
<td><input type="text" class="IM[attendance@date]"/></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--<button onclick="INTERMediator.saveRecordFromNavi()">Save</button>-->
</body>
</html>
上手く動いているようだ。
今回、サイト側の作業はCoda2を使ったのだが、便利だね!