@blog.justoneplanet.info

日々勉強

既存のプロジェクトをARC対応する

面倒ではあるがARC対応されたものしかライブラリとして存在しないものが少しづつ出てきたので時代の流れだししょうがない。

■Edit > Refactor > Convert to Objective-C ARC…

  1. Check
  2. Cannot Convert to Objective-C ARC…
  3. 左側でエラーが出たものを除外して、1に戻る

2が出なくなったら自動で変換される。

■Target > Build Phases > Compile Sources

除外したものに-fno-objc-arcが付いている。

  • ライブラリはARC版が出ていたら入れ替える
  • 自前のコードは頑張る

UIWebViewをカスタマイズする

WebViewっぽくならないようにバウンスをさせないようにし、HTTPリクエストにカスタムヘッダーを付加するようにした。たいしてカスタマイズしてない。

#import <UIKit/UIKit.h>
@interface CustomWebView : UIWebView
@end
#import "CustomWebView.h"
@implementation CustomWebView
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {// Initialization code
        // バウンスさせない
        for (id subview in self.subviews) {
            if ([[subview class] isSubclassOfClass: [UIScrollView class]]) {
                ((UIScrollView *)subview).bounces = NO;
            }
        }
    }
    return self;
}

- (void)loadRequest:(NSURLRequest *)request
{
    // カスタムヘッダーの付加
    NSMutableURLRequest *mutableRequest = (NSMutableURLRequest *)[request mutableCopy];
    [mutableRequest setValue:@"iOS.UIWebView" forHTTPHeaderField:@"App-Client"];
    [super loadRequest:mutableRequest];
}
@end

以下のようにオフライン時の画面を表示するメソッドを作ってあげてもいいと思う。

- (void)displayOfflineView
{
    if (self) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"offline" ofType:@"html"];
        [self loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
    }
}

webviewを表示した時に圏外の場合、圏内になった時にreloadをしても正しく表示ができないので以下のようなメソッドも加えておく。

- (void)dealloc
{
    if (initialRequest) {
        [initialRequest release];
    }
    [super dealloc];
}
- (void)reload
{
    if (self) {
        if ([self canGoBack]) {
            [super reload];
        }
        else if (initialRequest) {
            [self loadRequest:initialRequest];
        }
    }
}
- (void)loadRequest:(NSURLRequest *)request
{
    [super loadRequest:request];
    if (!initialRequest) {
        initialRequest = [request retain];
    }
}

■カスタムヘッダの付加

ただし、上述の方法ではUIWebView内のリンクをクリックした時にはカスタムヘッダが付加されないので以下のようにする。

- (void) _internalInit {
    super.delegate = self;
    foreignDelegate = nil;
}
- (id) init {
    if((self = [super init])) {
        [self _internalInit];
    }
    return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder {
    if((self = [super initWithCoder:aDecoder])) {
        [self _internalInit];
    }
    return self;
}
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {// Initialization code
        [self _internalInit];
    }
    return self;
}
- (void)dealloc
{
    [super dealloc];
}
// offline画面を表示する
- (void)displayOfflineView
{
    if (self) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"offline" ofType:@"html"];
        [self loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
    }
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/
- (void) setDelegate:(id<UIWebViewDelegate>)aDelegate {
    if(foreignDelegate == aDelegate) {
        return;
    }
	
    [foreignDelegate release];
    foreignDelegate = [aDelegate retain];
    /* Make sure this class is still the real delegate */
    super.delegate = self;
}
- (id<UIWebViewDelegate>) delegate {
    return foreignDelegate;
}
- (void) setCustomHeaders:(NSDictionary *)cHeaders {
    NSMutableDictionary *newHeaders = [NSMutableDictionary dictionary];
    for(NSString *key in [cHeaders allKeys]) {
        NSString *lowercaseKey = [key lowercaseString];
        [newHeaders setObject:[cHeaders objectForKey:key] forKey:lowercaseKey];
    }
    [customHeaders release];
    customHeaders = [newHeaders retain];
}
#pragma mark -
#pragma mark UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)aRequest navigationType:(UIWebViewNavigationType)navigationType {    
    
    BOOL missingHeaders = NO;
    
    /* Take all headers keys, lowercase them */
    NSArray *currentHeaders = [[aRequest allHTTPHeaderFields] allKeys];
    NSMutableArray *lowercasedHeaders = [NSMutableArray array];
    for(NSString *key in currentHeaders) {
        [lowercasedHeaders addObject:[key lowercaseString]];
    }
    
    /* See if there's at least one custom header missing */
    for(NSString *key in customHeaders) {
        if(![lowercasedHeaders containsObject:key]) {
            missingHeaders = YES;
            break;
        }
    }
    
    /* If one is missing, drop current request, and make a new one
     * with custom header */
    if(missingHeaders) {
        NSMutableURLRequest *newRequest = [aRequest mutableCopy];
        for(NSString *key in [customHeaders allKeys]) {
            [newRequest setValue:[customHeaders valueForKey:key] forHTTPHeaderField:key];
        }
        [self loadRequest:newRequest];
        [newRequest release];
        return YES;
    }
    
    //NSLog(@"Loading request with HTTP headers %@", [aRequest allHTTPHeaderFields]);
    
    if([foreignDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [foreignDelegate webView:self shouldStartLoadWithRequest:aRequest navigationType:navigationType];
    }
    
    return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
    if([foreignDelegate respondsToSelector:@selector(webViewDidStartLoad:)])
        [foreignDelegate webViewDidStartLoad:self];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if([foreignDelegate respondsToSelector:@selector(webViewDidFinishLoad:)])
        [foreignDelegate webViewDidFinishLoad:self];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    if([foreignDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)])
        [foreignDelegate webView:self didFailLoadWithError:error];
}

NSURLのURLの各部分に対するアクセス方法を調べてみる

分かりやすいものから分かりにくいものまであるのでとりあえずメモしておく。

// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
NSURL *url = [request URL];
[url scheme];// http
[url host];// hogehoge.com
[url absoluteString];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url absoluteURL];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url baseURL];
[url fragment];// hoge
[url host];// hogehoge.com
[url lastPathComponent];// test.html
[url parameterString];
[url password];// pass
[url path];// /dir/use/test.html
[url pathComponents];// ("/",dir,d,"test.html")
[url pathExtension];// html
[url port];// 1234
[url query];// q=sample
[url relativePath];// /dir/use/test.html
[url relativeString];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url resourceSpecifier];// //user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url standardizedURL];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url user];// user

面倒になってきた。

HMAC-MD5を計算する

■PHP

hash_hmac('md5', $str, 'key');

■Python

import hmac
from hashlib import sha1
from hashlib import md5
hmac.new("key", "value", md5).hexdigest()
hmac.new("key", "value", sha1).hexdigest()

■Android

public class HmacMD5 {
private static final String ALGORISM = “HmacMD5”;
private static final String S = “key”;
public static String get(String str) {
SecretKeySpec secretKeySpec = new SecretKeySpec(S.getBytes(), ALGORISM);
try {
Mac mac = Mac.getInstance(ALGORISM);
mac.init(secretKeySpec);
byte[] result = mac.doFinal(str.getBytes());
return byteToString(result);
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
catch (InvalidKeyException e) {
e.printStackTrace();
}
return “”;
}

private static String byteToString(byte [] b) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < b.length; i++) { int d = b[i]; d += (d < 0)? 256 : 0; if (d < 16) { buffer.append("0"); } buffer.append(Integer.toString(d, 16)); } return buffer.toString(); } } [/sourcecode]

■iOS

日本語入力に対応するためにstackoverflowから持ってきたコードに少々手を入れた。

+ (NSString *)HMACMD5WithKey:(NSString *)data
{
const char *cKey = [@”key” cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding];
const unsigned int blockSize = 64;
char ipad[blockSize];
char opad[blockSize];
char keypad[blockSize];

unsigned int keyLen = strlen(cKey);
CC_MD5_CTX ctxt;
if (keyLen > blockSize) {
CC_MD5_Init(&ctxt);
CC_MD5_Update(&ctxt, cKey, keyLen);
CC_MD5_Final((unsigned char *)keypad, &ctxt);
keyLen = CC_MD5_DIGEST_LENGTH;
}
else {
memcpy(keypad, cKey, keyLen);
}

memset(ipad, 0x36, blockSize);
memset(opad, 0x5c, blockSize);

int i;
for (i = 0; i < keyLen; i++) { ipad[i] ^= keypad[i]; opad[i] ^= keypad[i]; } CC_MD5_Init(&ctxt); CC_MD5_Update(&ctxt, ipad, blockSize); CC_MD5_Update(&ctxt, cData, strlen(cData)); unsigned char md5[CC_MD5_DIGEST_LENGTH]; CC_MD5_Final(md5, &ctxt); CC_MD5_Init(&ctxt); CC_MD5_Update(&ctxt, opad, blockSize); CC_MD5_Update(&ctxt, md5, CC_MD5_DIGEST_LENGTH); CC_MD5_Final(md5, &ctxt); const unsigned int hex_len = CC_MD5_DIGEST_LENGTH*2+2; char hex[hex_len]; for(i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { snprintf(&hex[i*2], hex_len-i*2, "%02x", md5[i]); } NSData *HMAC = [[NSData alloc] initWithBytes:hex length:strlen(hex)]; NSString *hash = [[[NSString alloc] initWithData:HMAC encoding:NSUTF8StringEncoding] autorelease]; [HMAC release]; return hash; } [/sourcecode]

ZXingをXcodeプロジェクトに組み込む

■組込

zxing-x.x/iphone/ZXingWidget/を自身のプロジェクトのプロジェクトナビゲーターにドロップする。

Targets > Build Phases

Target DependenciesでZXingWidgetを追加した後、Link Binary With Librariesで以下を追加する。

  • libZXingWidget.a
  • libiconv.dylib
  • CoreMedia.framework
  • CoreVideo.framework
  • AVFoundation.framework
  • AudioToolbox.framework
  • AddressBook.framework
  • AddressBookUI.framework
  • CoreGraphics.framework

Project > Build Settings

Header Search Pathsに以下の項目を追加する。

  • /User/[your name]/zxing-x.x/iphone/ZXingWidget/Classes
  • /User/[your name]/zxing-x.x/cpp/core/src

前者はrecursiveにチェックを入れて、後者はチェックを入れない。

参考

■実装

ScanViewController.h

#import <UIKit/UIKit.h>
#import "ZXingWidgetController.h"

@interface ScanViewController : UIViewController<ZXingDelegate>{
    UITextView *resultView;
    NSString *resultStr;
}
@property (nonatomic, retain) UITextView *resultView;
@property (nonatomic, copy) NSString *resultStr;
- (void)scanPressed:(id)sender;
@end

ScanViewController.mm

ファイルの拡張子を.mmとしないとコンパイルできない。

#import "ScanViewController.h"
#import "QRCodeReader.h"

@implementation ScanViewController
@synthesize resultView;
@synthesize resultStr;

- (void)scanPressed:(id)sender
{
    ZXingWidgetController *widController = [[ZXingWidgetController alloc] initWithDelegate:self showCancel:YES OneDMode:NO];
    QRCodeReader* qrcodeReader = [[QRCodeReader alloc] init];
    NSSet *readers = [[NSSet alloc ] initWithObjects:qrcodeReader,nil];
    [qrcodeReader release];
    widController.readers = readers;
    [readers release];

    [self presentModalViewController:widController animated:YES];
    [widController release];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
    }
    return self;
}
- (void)dealloc {
    [resultView release];
}

#pragma mark - View lifecycle
- (void)loadView
{
    [super loadView];
    
    UIButton *btnStamp = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btnStamp setFrame:CGRectMake(40, 220, 240, 50)];
    [btnStamp setTitle:@"QRコードを読み取る" forState:UIControlStateNormal];
    [btnStamp setTag:1];
    [btnStamp addTarget:self action:@selector(scanPressed:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btnStamp];
    
    resultView = [[UITextView alloc] initWithFrame:CGRectMake(40 , 280, 240, 50)];
    [self.view addSubview:resultView];
}
- (void)viewDidUnload
{
    [super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark -
#pragma mark ZXingDelegateMethods
- (void)zxingController:(ZXingWidgetController*)controller didScanResult:(NSString *)result {
    self.resultStr = result;
    if (self.isViewLoaded) {
        [resultView setText:resultStr];
        [resultView setNeedsDisplay];
    }
    [self dismissModalViewControllerAnimated:NO];
}
- (void)zxingControllerDidCancel:(ZXingWidgetController*)controller {
    [self dismissModalViewControllerAnimated:YES];
}
@end

こんな感じで。

AccessoryViewをカスタマイズする

setAccessoryViewしただけだとタップした時にaccessoryButtonTappedForRowWithIndexPathが発火しなくなるので以下のようにする。

// create cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // call cell
    UITableViewCellFixed *cell = (UITableViewCellFixed *)[tableView dequeueReusableCellWithIdentifier:@"table_cell"];
    if (cell == nil) {// create cell
        cell = [[[UITableViewCellFixed alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"table_cell"] autorelease];
    }
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setFrame:CGRectMake(0, 0, 26, 26)];
    [button setBackgroundImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor clearColor]];
    [button addTarget:self action:@selector(accessoryButtonTapped:withEvent:) forControlEvents:UIControlEventTouchUpInside];
    [cell setAccessoryView:button];
    return cell;
}
- (void)accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event
{
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[[[event touchesForView: button] anyObject] locationInView:self.tableView]];
    if (indexPath == nil) {
        return;
    }
    [self.tableView.delegate tableView: self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}

UITextViewの高さを可変にする

編集可能なUITextViewを使用するときに入力内容にあわせてUITextViewの高さを変えたい時がある。

■コード

以下のようにUITextViewを定義する。

textview = [[UITextView alloc] init];
[textview setFrame:CGRectMake(0, 0, 100, 50)];
[textview setEditable:YES];
[textview setDelegate:self];

以下のようにcontentSizeを使用する。

-(void)textViewDidChange:(UITextView *)textView {
    CGRect f = textview.frame;
    f.size.height = textview.contentSize.height;
    textview.frame = f;
}

Objective-CでSingleton

■実装

#import <Foundation/Foundation.h>

@interface TapManager : NSObject {
}
+ (id)instance;
+ (id)allocWithZone:(NSZone *)zone;
- (id)copyWithZone:(NSZone *)zone;
- (id)retain;
- (unsigned)retainCount;
- (void)release;
- (id)autorelease;
- (void)registerWithFace:(NSString *)face withTag:(NSString *)tag;
@end
#import "TapManager.h"
#import "HistoryDB.h"
#import "Util.h"

@implementation TapManager
static TapManager *_instance = nil;

+ (TapManager *)instance {
    @synchronized(self) {
        if (_instance == nil) {
            [[self alloc] init];//代入はしない
        }
    }
    return _instance;
}

// zoneからオブジェクトを生成する
+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];// 最初のみ代入する
            return _instance;// 最初のみ値を返す
        }
    }
    return nil;
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;
}

- (void)release {
}

- (id)autorelease {
    return self;
}

- (void)registerWithFace:(NSString *)face withTag:(NSString *)tag {
    UIPasteboard *pb = [UIPasteboard generalPasteboard];
    [pb setValue:face forPasteboardType:@"public.utf8-plain-text"];
    [Util showAlert:@"Hello" text:@"World"];
    // ...その他の処理
}
@end

NSNullとnilを判定する

■失敗例1

UITableViewのcellを設定する部分で以下のようなコードを書きクラッシュした。

NSString *tag = (NSString *)[row objectForKey:@"tag"];
[cell.tag setText:tag];
return cell;

■失敗例2

nilの場合があるなと考えて以下のようにする。

NSString *tag = (NSString *)[row objectForKey:@"tag"];
if (tag == nil) {
    [cell.tag setText:@""];
}
else {
    [cell.tag setText:tag];
}
return cell;

残念ながらクラッシュは依然として起こる。

■最終版

tagはNSNullであり以下の判定に修正。

NSString *tag = (NSString *)[row objectForKey:@"tag"];
if (tag == nil || [tag isEqual:[NSNull null]]) {
    [cell.tag setText:@""];
}
else {
    [cell.tag setText:tag];
}
return cell;

参考

NSNullは配列の要素のようなオブジェクトしか許されない所でnilを表すのに用いられる