DRY

Web関連の技術の事、食事/お酒の事、旅行の事など

仮想ザッカーバーグの試験(笑)

Life is beautifulにこんな記事があったので(笑)、ちょっと挑戦してみる事に

HTTPLoader.h


//
// HTTPLoader.h
// HTTPLoader
//
#import

typedef void (^HTTPLoaderComplete)(NSData* data, NSError *error);
@interface HTTPLoader : NSObject

+ (void)loadAsync:(NSURLRequest*)request complete:(HTTPLoaderComplete)loaderComplete;
@end

HTTPLoader.m


//
// HTTPLoader.m
// HTTPLoader
//
#import "HTTPLoader.h"

@implementation HTTPLoader
+ (void)loadAsync:(NSURLRequest*)request complete:(HTTPLoaderComplete)loaderComplete
{
dispatch_queue_t g_queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async (g_queue, ^{
NSLog (@"Execute g_queue");

NSURLResponse* response = nil;
NSError* error = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];

loaderComplete (data, error);
});
}
@end

HTTPLoaderTest.h


//
// HTTPLoaderTests.h
// HTTPLoaderTests
//
#import
#import "HTTPLoader.h"

@interface HTTPLoaderTests : SenTestCase

- (void)testloadAsync;
@end

HTTPLoaderTest.m


//
// HTTPLoaderTests.m
// HTTPLoaderTests
//

#import "HTTPLoaderTests.h"

@implementation HTTPLoaderTests
- (void)setUp
{
[super setUp];

// Set-up code here.
}

- (void)tearDown
{
// Tear-down code here.

[super tearDown];
}

- (void)excute:(NSString*)url
{
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[HTTPLoader loadAsync:request
complete:^(NSData* data, NSError *error) {
if (data)
{
NSLog (@"Success: process the data [URL = %@]", url);
}
else
{
NSLog (@"Error: process the error [URL = %@]", url);
}
}];
}

- (void)testloadAsync
{
[self excute:@"http://www.google.co.jp"];
[self excute:@"http://d.hatena.ne.jp/ke-16"];
[self excute:@"http://takoikasaru"];
}
@end

全然45分で終わらなかったのと、関数の引数がblockだった場合の処理の仕方がうる覚え(というか作法がわかってない)だった。。。ので出直しと言う事で。
これがクラスの中身としては一番シンプルな形だと思うのですが。合ってる???

Objecive-Cで独自のPIN&Annotationを使用する方法

また調べていて、あまりサンプルソースが見当たらなかったので自分メモ用に。


やりたかった事は単純にMKMapViewを利用してPINを立てる事と、Annotationの中に住所を入れて表示したかっただけです。

iOS5.1に対応しているので、位置情報はApple推奨のCLGeocoderを使用してます。

■LocationViewController
MKMapViewDelegate、CLLocationManagerDelegateを持っているViewControllerクラス

■MapAnnotation
Map表示時に、地図上に配置するAnnotationクラス

■CalloutAnnotation
PINをタップした際に呼ばれる、Annotationクラス

■CalloutAnnotationView
PINをタップした際に表示される、MKAnnotationViewクラス

LocationViewController.h


#import
#import
#import

#import "TalkViewController.h"
#import "MapAnnotation.h"
#import "CalloutAnnotation.h"
#import "CalloutAnnotationView.h"


@interface LocationViewController : UIViewController
{
@private
BOOL first;
int openMode;
}

@property (strong, nonatomic)id parent;
@property (strong, nonatomic)IBOutlet MKMapView *mapView;
@property (strong, nonatomic)CLLocationManager *locationManager;
@property (strong, nonatomic)CLLocation *presentLocation;
@property (strong, nonatomic)NSMutableDictionary* annotationDictionary;
@property (strong, nonatomic)NSString *addressTxt;


- (id)initWithNibNameAndLocation:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil lat:(double)_lat lng:(double)_lng;

- (IBAction)close:(id)sender;
- (IBAction)focasLocation:(id)sender;
@end

LocationViewController.m

#import "LocationViewController.h"


@interface LocationViewController ()
@end


@implementation LocationViewController
@synthesize parent = _parent;
@synthesize mapView = _mapView;
@synthesize locationManager = _locationManager;
@synthesize presentLocation = _presentLocation;
@synthesize annotationDictionary = _annotationDictionary;
@synthesize addressTxt = _addressTxt;


#define KEY_CLEAR -1
enum LOCATION_VIEW_MODE
{
LOCATION_TELL_MODE, // When you tell your position.
LOCATION_VIEW_MODE, // When you show their posiiton.
};


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
first = YES;
openMode = LOCATION_TELL_MODE;
}
return self;
}


- (id)initWithNibNameAndLocation:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil lat:(double)_lat lng:(double)_lng
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
first = YES;
openMode = LOCATION_VIEW_MODE;

MKCoordinateSpan CoordinateSpan = MKCoordinateSpanMake (0.005,0.005);
self.presentLocation= [[CLLocation alloc] initWithLatitude:_lat longitude:_lng];
CLLocationCoordinate2D mapCenter = self.presentLocation.coordinate;
MKCoordinateRegion CoordinateRegion = MKCoordinateRegionMake(mapCenter,CoordinateSpan);
[self.mapView setRegion:CoordinateRegion animated:YES];

}
return self;
}


- (void)viewDidLoad
{
LOG_FUNC
[super viewDidLoad];

// Do any additional setup after loading the view from its nib.
self.title = @"マップ";

self.mapView.delegate = self;
self.mapView.mapType = MKMapTypeStandard;
if (openMode == LOCATION_TELL_MODE)
self.mapView.showsUserLocation =YES;

if (self.locationManager == nil)
self.locationManager = [[CLLocationManager alloc] init];

self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // Accuracy is set up
self.locationManager.distanceFilter = kCLDistanceFilterNone; // Standard distance

// Its present location information reception start.
[self.locationManager startUpdatingLocation];

self.annotationDictionary = [NSMutableDictionary dictionary];
}

- (void)setPinToCoordinate:(CLLocation*)location
{
// setup default span
MKCoordinateSpan span;
if (self.mapView.region.span.latitudeDelta > 100)
span = MKCoordinateSpanMake(0.005, 0.005);
else
span = self.mapView.region.span;

// set the map view to location
CLLocationCoordinate2D centerCoordinate = location.coordinate;
MKCoordinateRegion coordinateRegion =
MKCoordinateRegionMake(centerCoordinate, span);
[self.mapView setRegion:coordinateRegion animated:YES];

// lat, lngから地名を逆引き。仕方ないのでココで実行
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:self.presentLocation completionHandler:^(NSArray *placemarks, NSError *error)
{
if(placemarks && placemarks.count > 0)
{
CLPlacemark *topResult = [placemarks objectAtIndex:0];
self.addressTxt = [NSString stringWithFormat:@"%@ %@ %@ %@ %@",
[topResult name],
[topResult subAdministrativeArea],
[topResult administrativeArea],
[topResult postalCode],
[topResult country]];

LOG(@"%@", self.addressTxt);

// add annotation
MapAnnotation *annotation = [[MapAnnotation alloc] init];
annotation.coordinate = self.presentLocation.coordinate;
annotation.title = self.addressTxt;

[self.mapView addAnnotation:annotation];
[self setAnnotation:annotation forCoordinate:location.coordinate];
}
}];
}

- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
LOG_FUNC

// Get location
if (self.presentLocation == nil)
self.presentLocation = newLocation;

if (first)
{
first = NO;
[self setPinToCoordinate:self.locationManager.location];
}

LOG(@"lat = %f lng = %f", self.presentLocation.coordinate.latitude, self.presentLocation.coordinate.longitude);
MKCoordinateSpan coordinateSpan = MKCoordinateSpanMake (0.005, 0.005);
MKCoordinateRegion CoordinateRegion = MKCoordinateRegionMake(self.presentLocation.coordinate, coordinateSpan);
[self.mapView setRegion:CoordinateRegion animated:YES];

[self.locationManager stopUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
{
LOG_FUNC

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"エラー" message:@"位置情報が取得できませんでした。" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
[alertView show];
}

#pragma mark - MapView delegate.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation
{
MKAnnotationView *annotationView;
NSString *identifier;

if ([annotation isKindOfClass:[MapAnnotation class]])
{
// Map annotation.
identifier = @"Pin";
annotationView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];

if (annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
}
annotationView.image = [UIImage imageNamed:@"location.png"];
}
else if ([annotation isKindOfClass:[CalloutAnnotation class]])
{
// Callout annotation.
identifier = @"Callout";
annotationView = (CalloutAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];

if (annotationView == nil)
{
annotationView = [[CalloutAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
}

//
CalloutAnnotation *calloutAnnotation = (CalloutAnnotation *)annotation;
((CalloutAnnotationView *)annotationView).title = calloutAnnotation.title;
[(CalloutAnnotationView *)annotationView initRect];
[(CalloutAnnotationView *)annotationView presentPointingAtView:self.mapView inView:(CalloutAnnotationView *)annotationView];

// Position adjustment of annotation
((CalloutAnnotationView *)annotationView).centerOffset = CGPointMake(10, -80);

UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(calloutTapped:)];
[annotationView addGestureRecognizer:tapGesture];
}

annotationView.annotation = annotation;
return annotationView;
}


/*MKMapViewのピンをタッチせずにコールアウトを出す
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
[mapView selectAnnotation:[mapView.annotations lastObject] animated:YES];
}
*/

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
LOG_FUNC
if ([view.annotation isKindOfClass:[MapAnnotation class]])
{
// Selected the pin annotation.
CalloutAnnotation *calloutAnnotation = [[CalloutAnnotation alloc] init];

MapAnnotation *mapAnnotation = ((MapAnnotation *)view.annotation);
calloutAnnotation.title = mapAnnotation.title;
calloutAnnotation.coordinate = mapAnnotation.coordinate;
mapAnnotation.calloutAnnotation = calloutAnnotation;
[mapView addAnnotation:calloutAnnotation];
}
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
LOG_FUNC
if ([view.annotation isKindOfClass:[MapAnnotation class]])
{
// Deselected the pin annotation.
MapAnnotation *mapAnnotation = ((MapAnnotation *)view.annotation);

[mapView removeAnnotation:mapAnnotation.calloutAnnotation];
mapAnnotation.calloutAnnotation = nil;
}
}

// Processing when the tap of the annotation is carried out
- (void) calloutTapped:(id) sender
{
LOG_FUNC
if (openMode == LOCATION_TELL_MODE)
{
MKCoordinateSpan coordinateSpan = MKCoordinateSpanMake(0.005,0.005);
MKCoordinateRegion CoordinateRegion = MKCoordinateRegionMake(self.presentLocation.coordinate ,coordinateSpan);
[self.mapView setRegion:CoordinateRegion animated:YES];

// lat, lngから地名を逆引き。仕方ないのでココで実行
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:self.presentLocation completionHandler:^(NSArray *placemarks, NSError *error)
{
if(placemarks && placemarks.count > 0)
{
CLPlacemark *topResult = [placemarks objectAtIndex:0];
NSString* txt = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n%@",
[topResult name],
[topResult subAdministrativeArea],
[topResult administrativeArea],
[topResult postalCode],
[topResult country]];

[self.parent willCloseLocationPicker:txt lat:self.presentLocation.coordinate.latitude lng:self.presentLocation.coordinate.longitude];
}
}];
}
else
{
[self selectMapPicker];
}
}

- (IBAction)close:(id)sender
{
LOG_FUNC
[self dismissModalViewControllerAnimated:YES];
}

- (IBAction)focasLocation:(id)sender
{
LOG_FUNC
[self selectMapPicker];
}

- (void)selectMapPicker
{
UIActionSheet *as = [[UIActionSheet alloc] init];
as.tag = 0;
as.delegate = self;
[as addButtonWithTitle:@"Open in Maps"];
[as addButtonWithTitle:@"Directions From Current Location"];
[as addButtonWithTitle:@"Send to Another Chat"];
[as addButtonWithTitle:@"Cancel"];
as.cancelButtonIndex = 3;
//as.destructiveButtonIndex = 0;
[as showInView:self.view];
}

- (void)actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (actionSheet.tag == 0)
{
switch (buttonIndex)
{
case 0: // "Open in Maps"
{
NSString* urlstr = [NSString stringWithFormat:@"http://maps.google.com/maps?q=%@@%1.6f,%1.6f",
[self.addressTxt stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], self.presentLocation.coordinate.latitude, self.presentLocation.coordinate.longitude];

NSURL* url = [NSURL URLWithString: urlstr];
if ([[UIApplication sharedApplication] canOpenURL:url])
{
[[UIApplication sharedApplication] openURL:url];
}
break;
}
case 1: // "Directions From Current Location"
{
NSString* urlstr = [NSString stringWithFormat:@"http://maps.google.com/maps?saddr=%1.6f,%1.6f&daddr=%1.6f,%1.6f&dirflg=w",
self.presentLocation.coordinate.latitude, self.presentLocation.coordinate.longitude,
self.locationManager.location.coordinate.latitude, self.locationManager.location.coordinate.longitude];

NSURL* url = [NSURL URLWithString: urlstr];
if ([[UIApplication sharedApplication] canOpenURL:url])
{
[[UIApplication sharedApplication] openURL:url];
}

break;
}
case 2: // "Send to Another Chat"
{
UIAlertView *alert =
[[UIAlertView alloc] initWithTitle:@"Sorry" message:@"工事中です"
delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];

break;
}
}
}
}

- (void)viewDidUnload
{
LOG_FUNC
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark -
#pragma mark
- (void)setAnnotation:(MapAnnotation*)annotation forCoordinate:(CLLocationCoordinate2D)coordinate
{
NSValue* coordinateValue = [NSValue value:&coordinate
withObjCType:@encode(CLLocationCoordinate2D)];
[self.annotationDictionary setObject:annotation
forKey:coordinateValue];
}

- (MapAnnotation*)annotationForCoordinate:(CLLocationCoordinate2D)coordinate
{
NSValue* coordinateValue = [NSValue value:&coordinate
withObjCType:@encode(CLLocationCoordinate2D)];
MapAnnotation* annotation = [self.annotationDictionary objectForKey:coordinateValue];
[self.annotationDictionary removeObjectForKey:coordinateValue];

return annotation;
}
@end

LocationViewController.xib


MapAnnotation.h


#import
#import
#import "CalloutAnnotation.h"

@interface MapAnnotation : NSObject

@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (strong , nonatomic) NSString *title;
@property (nonatomic, strong) CalloutAnnotation *calloutAnnotation;


@end

MapAnnotation.m


#import "MapAnnotation.h"

@implementation MapAnnotation
@synthesize coordinate = _coordinate;
@synthesize title = _title;
@synthesize calloutAnnotation = _calloutAnnotation;

@end

CalloutAnnotation.h


#import
#import


@interface CalloutAnnotation : NSObject

@property (nonatomic, strong) NSString *title;
@property (nonatomic, readwrite) CLLocationCoordinate2D coordinate;

@end

CalloutAnnotation.m


#import "CalloutAnnotation.h"

@implementation CalloutAnnotation

@synthesize title = _title;
@synthesize coordinate = _coordinate;

@end

最後が一番重要な吹き出しを作成するCalloutAnnotationViewなんですが、これの描画周りはコチラをかなり利用させて貰いました。
ただし、吹き出しは常に上に出ていて欲しいので、その辺りを改変しました。
※MITライセンスとの事ですが、何か問題ありましたらご連絡下さい。

CalloutAnnotationView.h


#import
#import


@interface CalloutAnnotationView : MKAnnotationView
{
@private
NSString *title;

@private
UIColor *backgroundColor;
UIColor *textColor;
UIFont *textFont;
UIColor *borderColor;
CGFloat borderWidth;

@private
CGSize bubbleSize;
CGFloat cornerRadius;
BOOL highlight;
CGFloat sidePadding;
CGFloat topMargin;
CGFloat pointerSize;
CGPoint targetPoint;
}

@property (nonatomic, strong)NSString *title;
@property (nonatomic, strong)UIColor *backgroundColor;
@property (nonatomic, strong)UIColor *textColor;
@property (nonatomic, strong)UIFont *textFont;
@property (nonatomic, assign)UITextAlignment textAlignment;
@property (nonatomic, strong)UIColor *borderColor;
@property (nonatomic, assign)CGFloat borderWidth;


- (void)initRect;
- (void)presentPointingAtView:(UIView *)targetView inView:(UIView *)containerView;

@end

CalloutAnnotationView.m


//
// CMPopTipView.m
//
// Created by Chris Miles on 18/07/10.
// Copyright (c) Chris Miles 2010-2012.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//


#import "CalloutAnnotationView.h"


@implementation CalloutAnnotationView
@synthesize title = _title;

@synthesize backgroundColor;
@synthesize textColor;
@synthesize textFont;
@synthesize textAlignment;
@synthesize borderColor;
@synthesize borderWidth;


#define TEXT_SIZE 10.0
#define TEXT_WIDTH 320.0


- (float)textHeight:(NSString*)text
{
CGSize boundingSize = CGSizeMake (TEXT_WIDTH, CGFLOAT_MAX);

//文字の横幅から高さを算出
CGSize labelsize = [text sizeWithFont:[UIFont systemFontOfSize:TEXT_SIZE]
constrainedToSize:boundingSize
lineBreakMode:UILineBreakModeWordWrap];

NSLog(@"%f", labelsize.width);
NSLog(@"%f", labelsize.height);
return labelsize.height;
}

// Initialize Rect
- (void)initRect
{
self.opaque = NO;
[self setFrame:CGRectMake (0, 0, TEXT_WIDTH, 0)];

cornerRadius = 10.0;
topMargin = 2.0;
pointerSize = 12.0;
sidePadding = 2.0;
borderWidth = 1.0;

self.textFont = [UIFont boldSystemFontOfSize:TEXT_SIZE];
self.textColor = [UIColor whiteColor];
self.textAlignment = UITextAlignmentCenter;
self.backgroundColor = [UIColor grayColor];
self.borderColor = [UIColor blackColor];
}

// Initialize point
- (void)presentPointingAtView:(UIView *)targetView inView:(UIView *)containerView
{
LOG_FUNC

// Size of rounded rect
CGFloat rectWidth = (int)(containerView.frame.size.width * 2 / 3);

CGSize textSize = CGSizeZero;
if (self.title != nil)
{
textSize= [self.title sizeWithFont:textFont constrainedToSize:CGSizeMake(rectWidth, 99999.0) lineBreakMode:UILineBreakModeWordWrap];
}
bubbleSize = CGSizeMake (textSize.width + cornerRadius * 2, textSize.height + cornerRadius * 2);

CGPoint targetOriginInContainer = [targetView convertPoint:CGPointMake(0.0, 0.0) toView:containerView];
// Y coordinate of pointer target (within containerView)
CGFloat pointerY = targetOriginInContainer.y;

CGFloat W = containerView.frame.size.width;
CGPoint p = [targetView.superview convertPoint:targetView.center toView:containerView];
CGFloat x_p = p.x;
CGFloat x_b = x_p - roundf(bubbleSize.width / 2);
if (x_b < sidePadding)
{
x_b = sidePadding;
}
if (x_b + bubbleSize.width + sidePadding > W)
{
x_b = W - bubbleSize.width - sidePadding;
}
if (x_p - pointerSize < x_b + cornerRadius)
{
x_p = x_b + cornerRadius + pointerSize;
}
if (x_p + pointerSize > x_b + bubbleSize.width - cornerRadius)
{
x_p = x_b + bubbleSize.width - cornerRadius - pointerSize;
}

CGFloat fullHeight = bubbleSize.height + pointerSize + 10.0;
CGFloat y_b = pointerY - fullHeight;
targetPoint = CGPointMake(x_p - x_b, fullHeight - 2.0);

CGRect finalFrame = CGRectMake(x_b - sidePadding, y_b,
bubbleSize.width + sidePadding * 2, fullHeight);

self.alpha = 0.0;
CGRect startFrame = finalFrame;
startFrame.origin.y += 10;
self.frame = startFrame;

[self setNeedsDisplay];

[UIView beginAnimations:nil context:nil];
self.alpha = 1.0;
self.frame = finalFrame;
[UIView commitAnimations];
}


- (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
LOG_FUNC
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];

if (self)
{
}

return self;
}

- (CGRect)bubbleFrame
{
CGRect bubbleFrame = CGRectMake(2.0, targetPoint.y - pointerSize - bubbleSize.height, bubbleSize.width, bubbleSize.height);
return bubbleFrame;
}

- (CGRect)contentFrame
{
CGRect bubbleFrame = [self bubbleFrame];
CGRect contentFrame = CGRectMake(bubbleFrame.origin.x + cornerRadius,
bubbleFrame.origin.y + cornerRadius,
bubbleFrame.size.width - cornerRadius * 2,
bubbleFrame.size.height - cornerRadius * 2);
return contentFrame;
}

- (void)drawRect:(CGRect)rect
{
LOG_FUNC

CGRect bubbleRect = [self bubbleFrame];
CGContextRef c = UIGraphicsGetCurrentContext();

CGContextSetRGBStrokeColor(c, 0.0, 0.0, 0.0, 1.0); // black
CGContextSetLineWidth(c, borderWidth);

CGMutablePathRef bubblePath = CGPathCreateMutable();

CGPathMoveToPoint(bubblePath, NULL, targetPoint.x, targetPoint.y);
CGPathAddLineToPoint(bubblePath, NULL, targetPoint.x - pointerSize, targetPoint.y - pointerSize);

CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x, bubbleRect.origin.y + bubbleRect.size.height,
bubbleRect.origin.x, bubbleRect.origin.y + bubbleRect.size.height - cornerRadius,
cornerRadius);
CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x, bubbleRect.origin.y,
bubbleRect.origin.x + cornerRadius, bubbleRect.origin.y,
cornerRadius);
CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x + bubbleRect.size.width, bubbleRect.origin.y,
bubbleRect.origin.x + bubbleRect.size.width, bubbleRect.origin.y + cornerRadius,
cornerRadius);
CGPathAddArcToPoint(bubblePath, NULL,
bubbleRect.origin.x + bubbleRect.size.width, bubbleRect.origin.y+bubbleRect.size.height,
bubbleRect.origin.x + bubbleRect.size.width - cornerRadius, bubbleRect.origin.y + bubbleRect.size.height,
cornerRadius);
CGPathAddLineToPoint(bubblePath, NULL, targetPoint.x + pointerSize, targetPoint.y - pointerSize);

CGPathCloseSubpath(bubblePath);

// Draw shadow
CGContextAddPath(c, bubblePath);
CGContextSaveGState(c);
CGContextSetShadow(c, CGSizeMake(0, 3), 5);
CGContextSetRGBFillColor(c, 0.0, 0.0, 0.0, 0.9);
CGContextFillPath(c);
CGContextRestoreGState(c);

// Draw clipped background gradient
CGContextAddPath(c, bubblePath);
CGContextClip(c);
CGFloat bubbleMiddle = (bubbleRect.origin.y + (bubbleRect.size.height / 2)) / self.bounds.size.height;


CGGradientRef myGradient;
CGColorSpaceRef myColorSpace;
size_t locationCount = 5;
CGFloat locationList[] = {0.0, bubbleMiddle - 0.03, bubbleMiddle, bubbleMiddle + 0.03, 1.0};

CGFloat colourHL = 0.0;
if (highlight)
colourHL = 0.25;

CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
int numComponents = CGColorGetNumberOfComponents([backgroundColor CGColor]);
const CGFloat *components = CGColorGetComponents([backgroundColor CGColor]);
if (numComponents == 2)
{
red = components[0];
green = components[0];
blue = components[0];
alpha = components[1];
}
else
{
red = components[0];
green = components[1];
blue = components[2];
alpha = components[3];
}
CGFloat colorList[] = {
//red, green, blue, alpha
red*1.16+colourHL, green*1.16+colourHL, blue*1.16+colourHL, alpha,
red*1.16+colourHL, green*1.16+colourHL, blue*1.16+colourHL, alpha,
red*1.08+colourHL, green*1.08+colourHL, blue*1.08+colourHL, alpha,
red +colourHL, green +colourHL, blue +colourHL, alpha,
red +colourHL, green +colourHL, blue +colourHL, alpha
};
myColorSpace = CGColorSpaceCreateDeviceRGB();
myGradient = CGGradientCreateWithColorComponents(myColorSpace, colorList, locationList, locationCount);
CGPoint startPoint, endPoint;
startPoint.x = 0;
startPoint.y = 0;
endPoint.x = 0;
endPoint.y = CGRectGetMaxY(self.bounds);

CGContextDrawLinearGradient(c, myGradient, startPoint, endPoint,0);
CGGradientRelease(myGradient);
CGColorSpaceRelease(myColorSpace);


//Draw Border
int numBorderComponents = CGColorGetNumberOfComponents([borderColor CGColor]);
const CGFloat *borderComponents = CGColorGetComponents(borderColor.CGColor);
CGFloat r, g, b, a;
if (numBorderComponents == 2)
{
r = borderComponents[0];
g = borderComponents[0];
b = borderComponents[0];
a = borderComponents[1];
}
else
{
r = borderComponents[0];
g = borderComponents[1];
b = borderComponents[2];
a = borderComponents[3];
}

CGContextSetRGBStrokeColor(c, r, g, b, a);
CGContextAddPath(c, bubblePath);
CGContextDrawPath(c, kCGPathStroke);

CGPathRelease(bubblePath);

// Draw text
if (self.title)
{
[textColor set];
CGRect textFrame = [self contentFrame];
[self.title drawInRect:textFrame
withFont:textFont
lineBreakMode:UILineBreakModeWordWrap
alignment:UITextAlignmentCenter];
}
}
@end

pyhtonで正規表現

python2.6で正規表現したくて作ったスクリプトがあるので、メモしておきます。

邪道スクリプトですが、とある理由で「ある特定のユーザが実行しているSELECT文で60秒以上掛かっているものをKILLする」ありがちなスクリプトです。

pythonのバージョンによって、「re.」の書き方が違うようです。

p = re.compile (r'select(.*)', re.IGNORECASE)
で、大文字小文字を区別しないselect文を抽出したい対象にして

if (p.match (format (row[7])) is not None):
で、is not None つまりselect文だったら

という形で出来ました。
やっぱ正規表現perlが便利ですね。


#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb
import re

if __name__ == '__main__':
connect = MySQLdb.connect (db="__DATABASENAME__", host="__HOSTNAME__", port=3306, user="__USERNAME__", passwd="__PASSWD__")
cur = connect.cursor ()
cur.execute ('show processlist')
rows = cur.fetchall ()
p = re.compile (r'select(.*)', re.IGNORECASE)
for row in rows:
print row[0], row[7], row[5]
if (row[1] == '特定のユーザ1' or row[1] == '特定のユーザ2'):
if (p.match (format (row[7])) is not None):
if (int (row[5]) > 60):
print 'kill %s' % row[0]
cur.execute ('kill %s' % row[0])

cur.close()
connect.close()

UnityでiPhoneゲームを2時間で作る

まずは大前提として、こちらの動画iPhoneゲームを20分間で作る【メダルプッシャー編】を自分でやってみた版です。

私がやったら、2時間掛かったのでタイトルをそうしました(笑)
で事情によりJSではなくC#でやりたかったので、スクリプトの部分だけ自分で解釈してC#にしてあります。

非常に分りやすくて素晴らしい動画でした。Unityのサンプルが自分で作れたのは良かったです。
作者さんに感謝しつつ自分で自分に解説しておきます。

■コイン(Cylinder)の生成

GameObject→Create Other→Cylinder
Component→Physics→Rigidbody を追加


Rididbody
ジッドボディとは

ジッドボディ(Rigid Body)とは NURBS やポリゴンのサーフェスを固い形状にしたものです。
ジッドボディ同士を衝突させて、 貫通しないで跳ね返らせるようにすることができます。

Unity ではオブジェクトに rigidbody を追加することで物理的な意味で個体として扱うことができるようになるそうです。

■ライトの生成

GameObject→Create Other→Directional Light

■床の生成

GameObject→Create Other→Cube

Inspectorビュー
Position (0, 0, 0)
Scale (5, 1, 5)

■カメラやコインの位置を修正

■コインオブジェクトの修正

当たり安定がCapsule Colliderを使っているので丸になってしまっている。
これをコインの形と同じにするために、Colliderを変更
Component→Physics→Mesh Collider に変更

Mesh Colliderの処理は重いので、Convexにチェックを入れる。
凸型の形状の場合は処理速度が改善されるらしい。

Projectビュー
右クリック→Create→Physics Material の生成(名前をCoinに変更)


「Dynamic Friction」
は、動作中の摩擦係数で、0になると氷のように滑ります。逆に1にすると強い力や重力がかからないとすぐに止まってしまいます。

「Static Friction」
は、静止中の摩擦係数で、同じく0にすると氷のように滑り、1にすると動かすのが難しくなります。

参考

Frictionをそれぞれ0.2に、跳ね返り係数 Bouncinessを0.6に設定。
Physics MaterialをD&DでCoinオブジェクトに関連付けてあげる。

コインの色付け
Projectビュー
右クリック→Create→Material
同様にMaterialをD&DでCoinオブジェクトに関連付けてあげる。

■コイン生成の挙動の変更(Prefab)

コインをボタンが押される度に生成したく、そういった大量生産を行うオブジェクトはPrefabに格納するものらしい。
Projectビュー
右クリック→Create→Prefab

コインオブジェクトをPrefabにD&Dし、名称をCoinPrefabに。
Worldにおいてあるコインオブジェクトの削除

■CoinPrefabをWorldに生成する為のオブジェクトを生成

GameObject→Create Empty→Spawnerに名称を変更

Projectビュー
右クリック→Create→C#Script→Spawnerに名称を変更
SpawnerオブジェクトにD&DしてScript制御を行う

SpawnerScriptの修正


using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour
{
public Transform prefab;

// Update is called once per frame
void Update ()
{
if (Input.GetButtonDown ("Fire1"))
{
Vector3 offset = new Vector3 (Mathf.Sin (Time.time * 7), 0, 0);
Instantiate (prefab, transform.position + offset, transform.rotation);
}
}
}

※ビデオ途中の位置の修正は込みになってます

そうすると、Inspectorビューに変数Prefabが表示されるので、ProjectビューのCoinPrefabをそこにD&Dする。
実行すると、クリックするたびにコインが生成される。

■Pusherの生成

オブジェクトCubeの名前をWallに変更して、右クリック→Duplicateで生成(Pushreに名称変更)
Pusherっぽい位置に修正して、Pusherはコインとぶつかる(干渉する)のでRigidBodyに変更。
Component→Physics→Rigidbody で変更。

スクリプトで制御をするので、Is Kinematicにチェックを入れる。

マニュアルによると、

Is Kinematic にチェックの入っているRigidbodyオブジェクトは物理学エンジンによって運転されず、そのTransformによってのみ操作することができます。
だそうな。つまり、ScriptでTransformを制御することによって意図する動きができるよ。とかそんな感じ?

Projectビュー
右クリック→Create→C#Script→Pusherに名称を変更
PusherオブジェクトにD&DしてScript制御を行う

PusherScriptの修正


using UnityEngine;
using System.Collections;

public class Pusher : MonoBehaviour
{
private Vector3 origin;

// Update is called once per frame
void Update ()
{
Vector3 offset = new Vector3 (0, 0, Mathf.Sin (Time.time));
rigidbody.MovePosition (origin + offset);
}

void Awake ()
{
// 起点を保存しておく
origin = rigidbody.position;
}
}

これにより実行すると、Pusherが前後に動く。

■壁の生成

Wallオブジェクトを右クリック→Duplicateで壁を生成し、適当に位置に配置

■Remover(コインの削除機能)の生成

GameObject→Create Empty→Removerに名称を変更
当たり判定を付けるため、Component→Physics→Box Collider に変更

Inspectorビュー
Scale (50, 1, 50)
と適当な位置に配置

Is Triggerにチェックを入れる。ぶつかった時(当たり判定の際)に何か処理を入れたい時はチェックを入れる。

Projectビュー
右クリック→Create→C#Script→Removerに名称を変更
RemoverオブジェクトにD&DしてScript制御を行う

RemoverScriptの修正


using UnityEngine;
using System.Collections;

public class Remover : MonoBehaviour
{
// 当たり判定のイベント
void OnTriggerEnter (Collider collider)
{
// オブジェクトの消去
Destroy (collider.gameObject);
}
}

これで実行すると、Removerにぶつかった際にコインが消えるのがわかる。

■Remover2の生成

Removerオブジェクトを右クリック→DuplicateでRemover2の生成
位置・サイズを適当に修正。

※実際は下記のScoreの生成を先にやらないとダメ

Projectビュー
右クリック→Create→C#Script→Remover2に名称を変更
Remover2オブジェクトにD&DしてScript制御を行う

Remover2Scriptの修正


using UnityEngine;
using System.Collections;

public class Remover2 : MonoBehaviour
{
void OnTriggerEnter (Collider collider)
{
Destroy (collider.gameObject);
Score.score += 3;
}
}

■Scoreの生成及びコインの増減制御

GameObject→Create Empty→Scoreに名称を変更
右クリック→Create→C#Script→Scoreに名称を変更
ScoreオブジェクトにD&DしてScript制御を行う
ScoreScriptの修正


using UnityEngine;
using System.Collections;

public class Score : MonoBehaviour
{
public static int score;

void Awake ()
{
score = 30;
}

void Update ()
{
guiText.text = score.ToString ();
}
}

コイン投入時にスコアを減らす為、SpawnerScriptの修正

using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour
{
public Transform prefab;

// Update is called once per frame
void Update ()
{
if (Input.GetButtonDown ("Fire1"))
{
Vector3 offset = new Vector3 (Mathf.Sin (Time.time * 7), 0, 0);
Instantiate (prefab, transform.position + offset, transform.rotation);

Score.score--;
}
}
}

■Scoreテキストの表示

Scoreオブジェクト選択状態で、Component→Rendering→GUIText

Inspectorビュー
Position (0, 1, 0)
Text 999
Font Size 30

これで実行すると、スコアの増減が見てとれる。
あとは実行中デバッグっぽい動作できるの凄いよね!

iPhone用にコンパイル

File→Build & Run クリックで暫く待つと勝手にxcodeが起動して開始します。
iPhoneで実行すると、スコアのテキストが小さくて見難いのでFont Sizeを60ぐらいにしても良いかもです。

まあこれで終わりでもいいのですが、せっかくなので背景画象の設定と、コインがRemover2に落ちた時に「チャリン」とか音を鳴らす処理を入れてみました。

■背景画象の設定

適当に背景画象をProjectタブにD&Dします。
GameObject→Create Other→Plane を追加(Bgに名称を変更)

画象をBgオブジェクトにD&Dして位置を調整してあげれば終わりでした。

■サウンドの設定

適当に再生したい音声(mp3でいけました)をProjectタブにD&Dします。
ファイル名はsound.mp3としました。

今回はRemover2に落ちた時(コインを+3する時)に音を鳴らしたいので
Remover2を以下のように修正します。
こちらのサイトを参考にさせて頂きました。


using UnityEngine;
using System.Collections;

public class Remover2 : MonoBehaviour
{
 public AudioClip getCoin;
 void OnTriggerEnter (Collider collider)
 {
Sound (1);
Destroy (collider.gameObject);
Score.score += 3;
}

void Sound (int time)
{
GameObject obj = new GameObject ("sound");
AudioSource sound = obj.gameObject.AddComponent ();
sound.clip = getCoin;
sound.Play ();
Destroy (obj, time);
}
}


一応自分なりに改修を加えてWeb版で実行したキャプチャを。色のセンスは問わないで下さいw

※背景画象を設定したり試したのですが、グレーになってる部分に埋めた素材が無料じゃないかもなので、一旦外しておきます。


それと最後にフォルダなどを整理して、動画の方のマネをした私のUnityの配置です。

すごく分りやすくて本当に良い動画でした。たまたま見つける事ができてラッキーです。
概ねUnityの操作方法とか理解できた気がします。

Macでパーミッション表示時に横にでるアットマーク(@)を取り除く

【コラム】OS X ハッキング!253 Leopard解体新書(4) 〜拡張された拡張属性〜
によると、EA (Extended Attributes)と呼ばれるものらしい。

これが何なのかは他の多くの人が記事を書いているのでそちらを参照頂ければ。

とりあえずNTFSフォーマット上の.pdfファイルを開こうとした時に、「MacOSXが使用しているため開けません」みたいなメッセージが出たので、パーミション関係かと思い調べていたらこれに当たりました。

でこれを削除するにはxattrコマンドを使えば削除できるようなので、試してみると


$ /usr/bin/xattr ファイル名
com.apple.FinderInfo
com.apple.metadata:kMDItemWhereFroms
com.apple.quarantine
などと、返ってくるのでこれを一つずつ -d オプションを付けて実行してあげれば大丈夫。

$ /usr/bin/xattr -d com.apple.FinderInfo ファイル名
$ /usr/bin/xattr -d com.apple.metadata:kMDItemWhereFroms ファイル名
$ /usr/bin/xattr -d com.apple.quarantine ファイル名

で、これを一つ一つやるのはメンドクサイので、雑なperlスクリプトを。

xattr.pl


#!/usr/bin/perl -w

use strict;
use Data::Dumper;
use File::Find;

my @dir = ('./');
warn Dumper (@dir);
find (\&_find, @dir);

sub _find
{
# The file name of a full path
my $f = $File::Find::name;
my @xattr = `/usr/bin/xattr "$f"`;
if (@xattr)
{
warn Dumper ($f);
foreach my $x (@xattr)
{
$x =~ s/\n|\n\r//;
#warn Dumper ($x);
`/usr/bin/xattr -d $x "$f"`;
}
}
}

exit 1;

Command /usr/bin/codesign failed with exit code 1

UnityからiOSプロジェクトを書き出した後、ビルドしようとすると「Command /usr/bin/codesign failed with exit code 1」エラーが出たのでその対処方法をメモ

キーチェーンの"iPhone Developer"がふんぬんと言っているので、キーチェーンアクセスに余計なものが無いか確認。
(私の場合は”不明”のキーチェーンなどがあったりしてしまいました。。。)

一旦キーチェーンを削除して、再度ビルドしましたがまだダメだったので、Organizerを開いてもう一度Add to Portalしたら無事起動できました。

iPhone用のFacebookSDKでfbDidLoginに飛んで来ない

iPhone用のFacebookSDKでfbDidLoginに飛んで来ない現象で頭を悩ませました。。。
stackoverflowでしか引っ掛からないのですが、みんな言ってる事がマチマチで。

とりあえず、fbDidLoginにはコールバックが来たのでその方法をメモしておきます。
SDKは2011年12月28日現在最新のものを使っています。


SDKのバージョン?が古いタイプだと


facebook = [[Facebook alloc] initWithAppId:APP_ID];
facebook.sessionDelegate = self;

[facebook authorize:permissions andDelegate:self];

とかで、delegateの指定位置が上記のような書き方になっているものもあるのですが、私が使用しているSDKではauthorizeを上記の用に書くとエラーが出るタイプです。

私は下記でコンパイルが通りました。


facebook = [[Facebook alloc] initWithAppId:APP_ID andDelegate:self];

[facebook authorize:permissions];

ですが、これで幾らやってもfbDidLoginには飛んできませんでした。
当然、fbDidNotLoginにもfbDidLogout飛んでくる訳がなく。。。

UIApplicationDelegateも継承してなきゃダメなのかなとかで


@interface FaceBookDelegate : NSObject
{
Facebook *facebook;
NSArray *permissions;
}
に変更して

// For 4.2+ support
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [facebook handleOpenURL:url];
}
をオーバーライドしたりとかしてみますが、変わらず。。。


で、いろいろ調べていてこのエントリにぶつかって
限りなく私に近いな〜と思い、以下の結論に達しました。

結論的には、SDKFacebook.mの296行目あたりのauthorizeの関数でsafariを使うのを止めました。


- (void) authorize:(NSArray *)permissions {
self.permissions = permissions;

//[self authorizeWithFBAppAuth:YES safariAuth:YES];
[self authorizeWithFBAppAuth:NO safariAuth:NO];
}

う〜ん。。。正しい動きなんだろうか???