忍者ブログ

Home

Born Neet

[PR]

  • 2025-02-23 (Sun)
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

  • Comments (Close):
  • TrackBack (Close):

IB不使用&オープンソースなiPadアプリ(テキストエディタ)を作る 第7回:AdMob

第7回。

今回は無料アプリの宿命(?)である
お金の問題を解決してくれるかもしれない広告についてです。

まぁお金が欲しければ有料化すればいい話なのですが、
そんな勇気もない僕のような開発者には強い味方ですね。

それでは始めます。

なお、AppleのiAdはまだ日本では表示されないので、
GoogleのAdMobを使います。

参考

参考文献は第1回参照

素材

引き続き、Soft * Accessory様の素材をお借りしています。

1. AdMob登録

まずはAdMobに登録しないと始まりません。

AdMobのサイトにアクセスし、
今すぐスタートから新規登録を行ないます。

ちなみに必要な情報は以下のとおり。
僕はオプションの電話・会社以外ちゃんと入力しました。

  • 電話(オプション)
  • 会社(オプション)
  • 住所
  • 市区町村
  • 郵便番号
  • 言語

必要な情報を入力し「submit」すると、「AdMobアカウントの登録」メールが来ます。
その中のリンクをクリックすると「お客様のアカウントが有効になりました」画面が表示され、
登録は完了です。

2. AdMob SDKへの取得

次にAdMobのSDKをダウンロードします。

ログインするか、ログイン後画面右上のメールアドレスをクリックして、
アカウント画面に行きます。

「サイト及びアプリケーション」内の「サイト/アプリケーションの追加」を
クリックすると、サイト情報の入力画面になります。
「iPadアプリケーション」を選択し、必要な情報を入力します。
※ アプリURLは入力しなくても大丈夫です。

次に進むと「サイト コードを取得」画面が出てくるので、
「AdMob iPad/iPhone SDKをダウンロード」からSDK(admob_iphone_sdk_20100818.tar.gz)を、
「SDK の説明書をPDF 文書でダウンロードしてください。」からPDF(AdMob_iPad_SDK_Instructions.pdf)を
ダウンロードします。

3. プロジェクトへの導入

ダウンロードしたSDKをプロジェクトに組み込みます。
PDFファイルに沿ってやればOKですが、英語なので一応流れを書いておきます。

Step1

SDK内の以下のファイルをAdMobフォルダごと、
プロジェクト直下にコピーします。

  • AdMob/
    • AdMobDelegateProtocol.h
    • AdMobView.h
    • libAdMob.a

Step2

「Frameworksを右クリック→追加→既存のフレームワーク」から、
以下を追加します。

  • AudioToolbox
  • CoreGraphics
  • MediaPlayer
  • MessageUI
  • QuartzCore

Step3

SDK内のTouchJSONフォルダをまるごとプロジェクト直下にコピーします。

Step4

publisher IDはSDK内に記載されているので問題なしです。

Step5(iPad)

実装です。次節で説明します。

4. 広告表示

やっとコーディングです。
お金がからむと前作業が長いですね^^

ここもPDF内のStep5に従うだけですが、量もそんなに多く無いので、
今回コードに加えた変更を全部載せておきます。

といっても、
「admob_iphone_sdk/sample_projects/AdMobSampleAdsiPad/Classes」内の
「EarthquakeListViewController.h, m」にPublisher ID含め全部書いてあるので、
コピーするだけなんですが。

AdMobDelegate

AdMobからのdelegateを受けるクラスを実装します。
今回はRootViewControllerにその機能を持たせています。

// RootViewController.h

#import "AdMobDelegateProtocol.h"
#import "AdMobView.h"

@interface RootViewController : UITableViewController <AdMobDelegate> {
// RootViewController.m

#pragma mark -
#pragma mark AdMobDelegate methods

- (NSString *)publisherIdForAd:(AdMobView *)adView {
    return @"publisher ID"; // this should be prefilled; if not, get it from www.admob.com
}

- (UIViewController *)currentViewControllerForAd:(AdMobView *)adView {
    // Return the top level view controller if possible. In this case, it is
    // the split view controller
    return self.splitViewController;
//    return self.navigationController.parentViewController;
}

- (void)willPresentFullScreenModalFromAd:(AdMobView *)adView {
    // IMPORTANT!!! IMPORTANT!!!
    // If we are about to get a full screen modal and we have a popover controller, dimiss it.
    // Otherwise, you may see the popover on top of the landing page.
    if (detailViewController.popoverController && detailViewController.popoverController.popoverVisible) {
        [detailViewController.popoverController dismissPopoverAnimated:YES];
    }
}

- (NSArray *)testDevices {
	return [NSArray arrayWithObjects: ADMOB_SIMULATOR_ID, nil];
}

プライベート指定解除

delegate内から使うDetailViewControllerのプロパティがプライペート化されちゃってるので、それを解除します。

// DetailViewController.h

@property (nonatomic, retain) UIPopoverController *popoverController;
// DetailViewController.m

@interface DetailViewController ()
//プライベートメソッドの解除(AdMobから使うので)
//@property (nonatomic, retain) UIPopoverController *popoverController;
- (void)configureView;
@end

AdMobViewの追加

最後に広告Viewを表示します。
今回はTableViewのFooterに表示するので、
RootViewControllerのinitWithPath内で行います。

        AdMobView *adMobView = [AdMobView requestAdOfSize:ADMOB_SIZE_320x270 withDelegate:self];
//        AdMobView *adMobView = [AdMobView requestAdOfSize:ADMOB_SIZE_320x48 withDelegate:self];
        self.tableView.tableFooterView = adMobView;

[完成]

広告とはいえ、画像が入ると見た目が良くなっていいですね。
AdMob導入

[まとめ]

AdMobは現状審査もないらしく、
非常に簡単な印象を受けました。

あとはどれくらいのお小遣いになるかですが、
もうちょっとでEdhitaをリリースしてみる予定なので、
そこで試してみようと思います。

それでは。

[コード]

今回のコードは「Edhita7.zip」に入っています。
GitHubのdownloadページからダウンロードして下さい。

Downloads for tnantoka's Edhita - GitHub

おまけと謝罪

今までgitのリポジトリにほとんどのファイルがコミットできていませんでした。
(git add .等をしていなかったので、新規ファイルが管理されていなかったのです。)

たまたま毎回ダウンロード用のファイルを用意していなので、少しは救われましたが、
試行錯誤の痕跡がなくなってしまってかなりショックです。

リポジトリをチェックアウトされた方々にもご迷惑をお掛けしました。

凹んでてもしょうがないので気を取り直して頑張ります。

PR

IB不使用&オープンソースなiPadアプリ(テキストエディタ)を作る 第6回:AccessoryView、カーソル移動

第6回です。
奇跡的にまだ飽きてません。
この勢いで電子書籍を出せるまで頑張りたいと思います。

今回はキーボードの上部にAccessoryViewを表示させて、
そこから記号やタブを入力できるようにします。

また、ついでにツールバーのボタンからカーソルを左右に移動できるようにします。

参考

今回も参考書籍(第1回参照)とAppleのドキュメントのみです。

素材

引き続き、Soft * Accessory様の素材をお借りしています。

1. AccessoryViewの作成

まず、AccessoryViewを作成します。

と言っても、UIViewを継承したEdhitaAccessoryViewを作成し、
その上に記号入力用のUIButtonを配置するだけ。

特別なことは必要ありません。

// EdhitaAccessoryView.m

- (id)initWithTextView:(UITextView *)textView {
	if (self = [super init]) {
		
		textView_ = textView;

		NSInteger padding = 8;
		NSInteger margin = 8;
		NSInteger size = 46;
		
		self.frame = CGRectMake(0, 0, 768, size + padding * 2);
		self.backgroundColor = [UIColor lightGrayColor];
		
		// あんまりボタンを表示すると小さくせざるをえないので、使用頻度が高そうで入力しづらいやつを
		NSArray *titles = [NSArray arrayWithObjects: @"{", @"}" ,@"<", @">", @"[", @"]", @"+", @"*", @"=", @"_", @"#", @"|", @"\\", nil];		
		
		UIButton *button;
		
		// 記号
		NSInteger i = 0;
		for (NSString *title in titles) {

			button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
			button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
			button.frame = CGRectMake((size + margin) * i + padding, padding, size, size);
			[button setTitle:title forState:UIControlStateNormal];
			[button addTarget:self action:@selector(insertDidPush:) forControlEvents:UIControlEventTouchUpInside];
			button.titleLabel.font = [UIFont systemFontOfSize: 20];
			[self addSubview:button];
			i++;
		}

		// タブ
		button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
		button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
		button.frame = CGRectMake(self.frame.size.width - size - padding, padding, size, size);
		[button setTitle:@"\\t" forState:UIControlStateNormal];
		[button addTarget:self action:@selector(tabDidPush) forControlEvents:UIControlEventTouchUpInside];
		[self addSubview:button];

	}
	return self;
}

2. AccessoryViewの表示

先程作ったViewをTextViewのAccessoryViewとして設定します。

方法は、DetailViewControllerのinit内で、
textView_のinputAccessoryViewにインスタンスを代入するだけ。

これでAccessoryViewが表示されるようになります。
簡単ですね!

		if ([settings objectForKey:@"accessoryView"] == NULL || [settings boolForKey:@"accessoryView"]) {
			EdhitaAccessoryView *accessoryView = [[EdhitaAccessoryView alloc] initWithTextView:textView_];
			textView_.inputAccessoryView = accessoryView;
		}

人によってはAccessoryViewが邪魔だという人もいると思うので、
Settingsに項目を追加してON/OFFできるようにしています。
上のif文はONに設定されているか(デフォルトON)を判別しています。

		<dict>
			<key>Type</key>
			<string>PSGroupSpecifier</string>
			<key>Title</key>
			<string>Input</string>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSToggleSwitchSpecifier</string>
			<key>Title</key>
			<string>Accessory View</string>
			<key>Key</key>
			<string>accessoryView</string>
			<key>DefaultValue</key>
			<true/>
		</dict>

3. 記号入力処理

AccessoryViewのボタンから記号を入力できるようにします。

やることは簡単で、ボタンがタップされたら、TextViewのselectedRangeを取得して、
ボタンのtitleLabel.text(タブの場合は\t)で置換します。
また、undoできるようにundoManagerに反対の処理(backspace)を仕込んでいます。
※ 見ての通り、view内に処理をがっつり書いちゃってます。
 MVC的にアウトだと思うんですが、
 この為に新たにControllerを作るのもどうかと思うし、
 これ以上DetailControllerを肥大化させてくないし…ということで一旦こういう実装にしています。
 今後勉強していく中で綺麗なアーキテクチャがわかったら直します。

// EdhitaAccessoryView.m

- (void)insertDidPush:(UIButton *)button {
	[self insertString:button.titleLabel.text];
}

- (void)tabDidPush {
	[self insertString:@"\t"];
}

- (void)insertString:(NSString *)string {

	NSMutableString *text = [textView_.text mutableCopy];

	NSRange range = textView_.selectedRange;
	[text replaceCharactersInRange:range withString:string];

	// カーソルが最後に行っちゃうので選択しなおす
	textView_.text = text;
	textView_.selectedRange = NSMakeRange(range.location + 1, range.length);

	// undo登録
	[[textView_ undoManager] registerUndoWithTarget:self selector:@selector(backSpace:) object:string];
}

- (void)backSpace:(NSString *)string {
	
	NSMutableString *text = [textView_.text mutableCopy];

	// 手前にカーソルを戻して空文字で1文字分置換
	NSRange range = textView_.selectedRange;
	textView_.selectedRange = NSMakeRange(range.location - 1, 1);
	[text replaceCharactersInRange:textView_.selectedRange withString:@""];
	textView_.text = text;
	
	textView_.selectedRange = NSMakeRange(range.location - 1, 0);

	// redo登録
	[[textView_ undoManager] registerUndoWithTarget:self selector:@selector(insertString:) object:string];
}

4. カーソル移動

最後にカーソルキーによるキャレットの移動です。

本当はAccessoryViewのボタンでやりたかったんですが、
どうしてもPortraitの時に幅が足りなくなるので、
ツールバーでやってます。

処理としては、selectedRangeのlocationを±1して設定しなおしているだけです。
上下移動は文字数とか考慮しなきゃいけないので保留です。

- (void)leftDidPush {
	NSRange range = textView_.selectedRange;
	textView_.selectedRange = NSMakeRange(range.location - 1, range.length);
	[[textView_ undoManager] registerUndoWithTarget:self selector:@selector(rightDidPush) object:nil];
}

- (void)rightDidPush {
	NSRange range = textView_.selectedRange;
	textView_.selectedRange = NSMakeRange(range.location + 1, range.length);
	[[textView_ undoManager] registerUndoWithTarget:self selector:@selector(leftDidPush) object:nil];
}

[完成]

できました!
AccessoryViewの実装

[まとめ]

いかがだったでしょうか。

だいぶTextEditorらしくなってきましたが、
肝心のSyntax Highlightingが後回しのまま…。

まだ検討もついてませんが、
そろそろやるしかないですねぇ。

[コード]

今回のコードは「Edhita6.zip」に入っています。
GitHubのdownloadページからダウンロードして下さい。

Downloads for tnantoka's Edhita - GitHub

IB不使用&オープンソースなiPadアプリ(テキストエディタ)を作る 第5回:Undo・Redoボタン

続けざまに第5回。
簡単そうな課題が残ってたを思い出したので。

参考

今回は、参考サイトはありません。
参考書籍は第1回をご参照下さい。

素材

引き続き、Soft * Accessory様の素材をお借りしています。

1. Undo/Redoボタンの追加

まずはボタンの追加です。
といっても既に新規ファイル・フォルダでやったのと同じ手順です。
今回は、DetailViewControllerのinitに実装します。

		// targetとactionをnilにしたら勝手にundo,redoしてくれるっぽいけど保証されるかわからんのでやめとく
		UIBarButtonItem *undoButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoDidPush)];
		UIBarButtonItem *redoButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo target:self action:@selector(redoDidPush)];
		NSArray *items = [NSArray arrayWithObjects:undoButton, redoButton, nil];
		[toolbar setItems:items];

2. Undo/Redo機能の実装

NSUndoManagerを使って行ないます。
こいつはUIResponderのundoManagerプロパティから取得できます。
TextViewもUIResponderのsubclassの為、もちろんこのプロパティを持っています。
今回はTextViewから取得したundoMangerに対して、undo/redoメソッドを呼び出しています。

- (void)undoDidPush {
	[[textView_ undoManager] undo];
}

- (void)redoDidPush {
	[[textView_ undoManager] redo];	
}

[1.]のコメントにも書きましたが、
実はUIBarButtonSystemItemUndo・Redoのボタンを作るとき、
selfとactionをnilにしておけば勝手にやってくれるようです。
※ ドキュメントに記載が見当たらなかったので使うのは止めてます。

[完成]

できました!
Undo/Redoボタンの実装

[まとめ]

これでおしまいです。
そんなに苦労しないだろうと思ってましたが、
予想を上回る簡単さでした。

[コード]

今回のコードは「Edhita5.zip」に入っています。
GitHubのdownloadページからダウンロードして下さい。

Downloads for tnantoka's Edhita - GitHub

IB不使用&オープンソースなiPadアプリ(テキストエディタ)を作る 第4回:Settings.bundleと色・サイズ・フォント変更

第4回。

今回は予定を変更して設定変更を実装します。
あまりにも文字が小さくて見づらいので。

超簡単なのでちゃっちゃと終わらせまーす。

参考

今回は、constの使い方について以下のサイトで勉強させていただきました。
結局使わなかったけど…。
svartalfheim.jp - Objective-Cについて

参考書籍は第1回をご参照下さい。

素材

引き続き、Soft * Accessory様の素材をお借りしています。

1. 設定画面の追加

設定画面はアプリ独自で持つこともできるのですが、
アップル的にはデフォルトの設定アプリの方に統一したいらしいので、
ここではそれに従います。

手順は以下のとおり。
XMLファイルを作るだけで画面作ってくれて非常に簡単です。

(1) 設定ファイルの作成

グループとファイルのResourcesを右クリック。
追加 → 新規ファイル → iPhone OS → Resouce → Settings Bundleを選択。
デフォルトのまま次へ進めば完了。
設定ファイルの作成

(2) 設定項目の作成

Resources内のRoot.plistを編集します。
今回はフォントや色・サイズを選択させるので、PSMultiValueSpecifierを使います。

完成したファイルがこちら。
※ fontはとりあえず使えるやつ全部を一覧化。
 sizeは10から50まで2刻みで用意してます。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>StringsTable</key>
	<string>Root</string>
	<key>PreferenceSpecifiers</key>
	<array>
		<dict>
			<key>Type</key>
			<string>PSGroupSpecifier</string>
			<key>Title</key>
			<string>Color</string>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSMultiValueSpecifier</string>
			<key>Title</key>
			<string>Text Color</string>
			<key>Key</key>
			<string>textColor</string>
			<key>DefaultValue</key>
			<integer>0</integer>
			<key>Values</key>
			<array>
				<integer>0</integer>
				<integer>1</integer>
				<integer>2</integer>
				<integer>3</integer>
				<integer>4</integer>
				<integer>5</integer>
				<integer>6</integer>
				<integer>7</integer>
				<integer>8</integer>
				<integer>9</integer>
				<integer>10</integer>
				<integer>11</integer>
				<integer>12</integer>
				<integer>13</integer>
			</array>
			<key>Titles</key>
			<array>
				<string>Black</string>
				<string>DarkGray</string>
				<string>LightGray</string>
				<string>White</string>
				<string>Gray</string>
				<string>Red</string>
				<string>Green</string>
				<string>Blue</string>
				<string>Cyan</string>
				<string>Yellow</string>
				<string>Magenta</string>
				<string>Orange</string>
				<string>Purple</string>
				<string>Brown</string>
			</array>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSMultiValueSpecifier</string>
			<key>Title</key>
			<string>Background Color</string>
			<key>Key</key>
			<string>backgroundColor</string>
			<key>DefaultValue</key>
			<integer>3</integer>
			<key>Values</key>
			<array>
				<integer>0</integer>
				<integer>1</integer>
				<integer>2</integer>
				<integer>3</integer>
				<integer>4</integer>
				<integer>5</integer>
				<integer>6</integer>
				<integer>7</integer>
				<integer>8</integer>
				<integer>9</integer>
				<integer>10</integer>
				<integer>11</integer>
				<integer>12</integer>
				<integer>13</integer>
			</array>
			<key>Titles</key>
			<array>
				<string>Black</string>
				<string>DarkGray</string>
				<string>LightGray</string>
				<string>White</string>
				<string>Gray</string>
				<string>Red</string>
				<string>Green</string>
				<string>Blue</string>
				<string>Cyan</string>
				<string>Yellow</string>
				<string>Magenta</string>
				<string>Orange</string>
				<string>Purple</string>
				<string>Brown</string>
			</array>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSGroupSpecifier</string>
			<key>Title</key>
			<string>Font</string>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSMultiValueSpecifier</string>
			<key>Title</key>
			<string>Font Name</string>
			<key>Key</key>
			<string>fontName</string>
			<key>DefaultValue</key>
			<string>Helvetica</string>
			<key>Values</key>
			<array>
				<string>AcademyEngravedLetPlain</string>
				<string>AmericanTypewriter</string>
				<string>AmericanTypewriter-Bold</string>
				<string>AppleGothic</string>
				<string>Arial-BoldItalicMT</string>
				<string>Arial-BoldMT</string>
				<string>Arial-ItalicMT</string>
				<string>ArialHebrew</string>
				<string>ArialHebrew-Bold</string>
				<string>ArialMT</string>
				<string>ArialRoundedMTBold</string>
				<string>Baskerville</string>
				<string>Baskerville-Bold</string>
				<string>Baskerville-BoldItalic</string>
				<string>Baskerville-Italic</string>
				<string>BodoniOrnamentsITCTT</string>
				<string>BodoniSvtyTwoITCTT-Bold</string>
				<string>BodoniSvtyTwoITCTT-Book</string>
				<string>BodoniSvtyTwoITCTT-BookIta</string>
				<string>BodoniSvtyTwoOSITCTT-Bold</string>
				<string>BodoniSvtyTwoOSITCTT-Book</string>
				<string>BodoniSvtyTwoOSITCTT-BookIt</string>
				<string>BodoniSvtyTwoSCITCTT-Book</string>
				<string>BradleyHandITCTT-Bold</string>
				<string>Chalkduster</string>
				<string>Cochin</string>
				<string>Cochin-Bold</string>
				<string>Cochin-BoldItalic</string>
				<string>Cochin-Italic</string>
				<string>Copperplate</string>
				<string>Copperplate-Bold</string>
				<string>Courier</string>
				<string>Courier-Bold</string>
				<string>Courier-BoldOblique</string>
				<string>Courier-Oblique</string>
				<string>CourierNewPS-BoldItalicMT</string>
				<string>CourierNewPS-BoldMT</string>
				<string>CourierNewPS-ItalicMT</string>
				<string>CourierNewPSMT</string>
				<string>DBLCDTempBlack</string>
				<string>Didot</string>
				<string>Didot-Bold</string>
				<string>Didot-Italic</string>
				<string>Futura-CondensedExtraBold</string>
				<string>Futura-Medium</string>
				<string>Futura-MediumItalic</string>
				<string>GeezaPro</string>
				<string>GeezaPro-Bold</string>
				<string>Georgia</string>
				<string>Georgia-Bold</string>
				<string>Georgia-BoldItalic</string>
				<string>Georgia-Italic</string>
				<string>GillSans</string>
				<string>GillSans-Bold</string>
				<string>GillSans-BoldItalic</string>
				<string>GillSans-Italic</string>
				<string>Helvetica</string>
				<string>Helvetica-Bold</string>
				<string>Helvetica-BoldOblique</string>
				<string>Helvetica-Oblique</string>
				<string>HelveticaNeue</string>
				<string>HelveticaNeue-Bold</string>
				<string>HelveticaNeue-BoldItalic</string>
				<string>HelveticaNeue-Italic</string>
				<string>HiraKakuProN-W3</string>
				<string>HiraKakuProN-W6</string>
				<string>HiraMinProN-W3</string>
				<string>HiraMinProN-W6</string>
				<string>HoeflerText-Black</string>
				<string>HoeflerText-BlackItalic</string>
				<string>HoeflerText-Italic</string>
				<string>HoeflerText-Regular</string>
				<string>MarkerFelt-Thin</string>
				<string>MarkerFelt-Wide</string>
				<string>Optima-Bold</string>
				<string>Optima-BoldItalic</string>
				<string>Optima-Italic</string>
				<string>Optima-Regular</string>
				<string>Palatino-Bold</string>
				<string>Palatino-BoldItalic</string>
				<string>Palatino-Italic</string>
				<string>Palatino-Roman</string>
				<string>Papyrus</string>
				<string>PartyLetPlain</string>
				<string>STHeitiJ-Light</string>
				<string>STHeitiJ-Medium</string>
				<string>STHeitiK-Light</string>
				<string>STHeitiK-Medium</string>
				<string>STHeitiSC-Light</string>
				<string>STHeitiSC-Medium</string>
				<string>STHeitiTC-Light</string>
				<string>STHeitiTC-Medium</string>
				<string>SnellRoundhand</string>
				<string>SnellRoundhand-Bold</string>
				<string>Thonburi</string>
				<string>Thonburi-Bold</string>
				<string>TimesNewRomanPS-BoldItalicMT</string>
				<string>TimesNewRomanPS-BoldMT</string>
				<string>TimesNewRomanPS-ItalicMT</string>
				<string>TimesNewRomanPSMT</string>
				<string>Trebuchet-BoldItalic</string>
				<string>TrebuchetMS</string>
				<string>TrebuchetMS-Bold</string>
				<string>TrebuchetMS-Italic</string>
				<string>Verdana</string>
				<string>Verdana-Bold</string>
				<string>Verdana-BoldItalic</string>
				<string>Verdana-Italic</string>
				<string>ZapfDingbatsITC</string>
				<string>Zapfino</string>
			</array>
			<key>Titles</key>
			<array>
				<string>AcademyEngravedLetPlain</string>
				<string>AmericanTypewriter</string>
				<string>AmericanTypewriter-Bold</string>
				<string>AppleGothic</string>
				<string>Arial-BoldItalicMT</string>
				<string>Arial-BoldMT</string>
				<string>Arial-ItalicMT</string>
				<string>ArialHebrew</string>
				<string>ArialHebrew-Bold</string>
				<string>ArialMT</string>
				<string>ArialRoundedMTBold</string>
				<string>Baskerville</string>
				<string>Baskerville-Bold</string>
				<string>Baskerville-BoldItalic</string>
				<string>Baskerville-Italic</string>
				<string>BodoniOrnamentsITCTT</string>
				<string>BodoniSvtyTwoITCTT-Bold</string>
				<string>BodoniSvtyTwoITCTT-Book</string>
				<string>BodoniSvtyTwoITCTT-BookIta</string>
				<string>BodoniSvtyTwoOSITCTT-Bold</string>
				<string>BodoniSvtyTwoOSITCTT-Book</string>
				<string>BodoniSvtyTwoOSITCTT-BookIt</string>
				<string>BodoniSvtyTwoSCITCTT-Book</string>
				<string>BradleyHandITCTT-Bold</string>
				<string>Chalkduster</string>
				<string>Cochin</string>
				<string>Cochin-Bold</string>
				<string>Cochin-BoldItalic</string>
				<string>Cochin-Italic</string>
				<string>Copperplate</string>
				<string>Copperplate-Bold</string>
				<string>Courier</string>
				<string>Courier-Bold</string>
				<string>Courier-BoldOblique</string>
				<string>Courier-Oblique</string>
				<string>CourierNewPS-BoldItalicMT</string>
				<string>CourierNewPS-BoldMT</string>
				<string>CourierNewPS-ItalicMT</string>
				<string>CourierNewPSMT</string>
				<string>DBLCDTempBlack</string>
				<string>Didot</string>
				<string>Didot-Bold</string>
				<string>Didot-Italic</string>
				<string>Futura-CondensedExtraBold</string>
				<string>Futura-Medium</string>
				<string>Futura-MediumItalic</string>
				<string>GeezaPro</string>
				<string>GeezaPro-Bold</string>
				<string>Georgia</string>
				<string>Georgia-Bold</string>
				<string>Georgia-BoldItalic</string>
				<string>Georgia-Italic</string>
				<string>GillSans</string>
				<string>GillSans-Bold</string>
				<string>GillSans-BoldItalic</string>
				<string>GillSans-Italic</string>
				<string>Helvetica</string>
				<string>Helvetica-Bold</string>
				<string>Helvetica-BoldOblique</string>
				<string>Helvetica-Oblique</string>
				<string>HelveticaNeue</string>
				<string>HelveticaNeue-Bold</string>
				<string>HelveticaNeue-BoldItalic</string>
				<string>HelveticaNeue-Italic</string>
				<string>HiraKakuProN-W3</string>
				<string>HiraKakuProN-W6</string>
				<string>HiraMinProN-W3</string>
				<string>HiraMinProN-W6</string>
				<string>HoeflerText-Black</string>
				<string>HoeflerText-BlackItalic</string>
				<string>HoeflerText-Italic</string>
				<string>HoeflerText-Regular</string>
				<string>MarkerFelt-Thin</string>
				<string>MarkerFelt-Wide</string>
				<string>Optima-Bold</string>
				<string>Optima-BoldItalic</string>
				<string>Optima-Italic</string>
				<string>Optima-Regular</string>
				<string>Palatino-Bold</string>
				<string>Palatino-BoldItalic</string>
				<string>Palatino-Italic</string>
				<string>Palatino-Roman</string>
				<string>Papyrus</string>
				<string>PartyLetPlain</string>
				<string>STHeitiJ-Light</string>
				<string>STHeitiJ-Medium</string>
				<string>STHeitiK-Light</string>
				<string>STHeitiK-Medium</string>
				<string>STHeitiSC-Light</string>
				<string>STHeitiSC-Medium</string>
				<string>STHeitiTC-Light</string>
				<string>STHeitiTC-Medium</string>
				<string>SnellRoundhand</string>
				<string>SnellRoundhand-Bold</string>
				<string>Thonburi</string>
				<string>Thonburi-Bold</string>
				<string>TimesNewRomanPS-BoldItalicMT</string>
				<string>TimesNewRomanPS-BoldMT</string>
				<string>TimesNewRomanPS-ItalicMT</string>
				<string>TimesNewRomanPSMT</string>
				<string>Trebuchet-BoldItalic</string>
				<string>TrebuchetMS</string>
				<string>TrebuchetMS-Bold</string>
				<string>TrebuchetMS-Italic</string>
				<string>Verdana</string>
				<string>Verdana-Bold</string>
				<string>Verdana-BoldItalic</string>
				<string>Verdana-Italic</string>
				<string>ZapfDingbatsITC</string>
				<string>Zapfino</string>
			</array>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSMultiValueSpecifier</string>
			<key>Title</key>
			<string>Font Size</string>
			<key>Key</key>
			<string>fontSize</string>
			<key>DefaultValue</key>
			<integer>16</integer>
			<key>Values</key>
			<array>
				<integer>10</integer>
				<integer>12</integer>
				<integer>14</integer>
				<integer>16</integer>
				<integer>18</integer>
				<integer>20</integer>
				<integer>22</integer>
				<integer>24</integer>
				<integer>26</integer>
				<integer>28</integer>
				<integer>30</integer>
				<integer>32</integer>
				<integer>34</integer>
				<integer>36</integer>
				<integer>38</integer>
				<integer>40</integer>
				<integer>42</integer>
				<integer>44</integer>
				<integer>46</integer>
				<integer>48</integer>
				<integer>50</integer>
			</array>
			<key>Titles</key>
			<array>
				<string>10</string>
				<string>12</string>
				<string>14</string>
				<string>16</string>
				<string>18</string>
				<string>20</string>
				<string>22</string>
				<string>24</string>
				<string>26</string>
				<string>28</string>
				<string>30</string>
				<string>32</string>
				<string>34</string>
				<string>36</string>
				<string>38</string>
				<string>40</string>
				<string>42</string>
				<string>44</string>
				<string>46</string>
				<string>48</string>
				<string>50</string>
			</array>
		</dict>
	</array>
</dict>
</plist>

ここまでで自動的に設定画面ができちゃいます。
設定画面

2. 設定変更の実装

さっき作った画面で設定した内容をアプリに反映させます。
今回の設定は全てTextViewに関するものなので、
DetailControllerのinit内で以下のように読み込み・設定しています。

		NSUserDefaults* settings = [NSUserDefaults standardUserDefaults];

		// defaultを設定してもnullが返ってくるので、0やNOがdefaultじゃない場合処理が必要。
		NSInteger textColor = [settings integerForKey:@"textColor"];
		NSInteger backgroundColor = [settings objectForKey: @"backgroundColor"] != NULL ? [settings integerForKey:@"backgroundColor"] : 3;
		NSString *fontName = [settings objectForKey: @"fontName"] != NULL ? [settings stringForKey:@"fontName"] : @"Helvetica";
		NSInteger fontSize = [settings objectForKey: @"fontSize"] != NULL ? [settings integerForKey:@"fontSize"] : 16;
				
		textView_.font = [UIFont fontWithName:fontName size:fontSize];
		textView_.textColor = [self getColorWithIndex:textColor];
		textView_.backgroundColor = [self getColorWithIndex:backgroundColor];

[完成]

できました。
設定反映後

[まとめ]

これで目を凝らして小さい字を読まなくても良くなりました。
今回は簡単だったので、次はもうちょっとプログラミングらしいことをやろうと思います。

[コード]

今回のコードは「Edhita4.zip」に入っています。
GitHubのdownloadページからダウンロードして下さい。

Downloads for tnantoka's Edhita - GitHub

IB不使用&オープンソースなiPadアプリ(テキストエディタ)を作る 第3回:ファイル操作とNavigationController、TableView

第3回。 今回はTableVIew&NavigationControllerでのファイル操作、
TextViewでの内容表示などなどを実装します。
ようやくテキストエディタな機能がつくわけですね。

それでは早速始めます。

ちょっと規模も大きくなってきたのでソースをベタ貼りするのはやめて、
ポイントだけに絞ってます。
また、jQuery.Syntaxを使ってコードを見やすくしてみました。

参考

今回は結構いじったので、参考にさせていただいたサイトもたくさんあります。
ありがとうございました。
なお、参考書籍は第1回をご参照下さい。

素材

今回から、フォルダとファイルの画像を使用しています。
この素材はいつもお世話になっている、Soft * Accessory様にお借りしました。
ハイクオリティな上に非常に緩い使用条件で素晴らしいです。

1. ファイル・ディレクトリの作成・削除

NavigationControllerのToolbarにボタンを表示して、
ファイル・ディレクトリの作成機能を付けます。
また、削除はTableViewの編集機能で行ないます。

Toolbarの表示(EdhitaNavigationController.m)

NavigationControllerのサブクラス、EdhitaNavigationControllerで行ないます。
※ TableViewController側でやるとうまくいきませんでした。
initでtoolbarHiddenを設定するだけです。

- (id)init {
	if (self = [super init]) {
		self.toolbarHidden = NO;
		// sizeToFitしないとportraitで起動したとき、popover内で表示されない。
		[self.toolbar sizeToFit];
	}
	return self;
}

作成・編集ボタンの表示(RootViewController.m)

先程表示したToolbarにファイル・ディレクトリ作成ボタンを追加します。
これはTableViewのサブクラスであるRootViewControllerのinitで設定します。
※ NavigationControllerでやってもうまくいきません。

また編集ボタンはNavigationBarの右に表示します。
この仕組みは標準で提供されており、
rightButtonItemにeditButtonItemを設定するだけです。

		// 画像ボタンを2個作って、それぞれファイル・ディレクトリの作成用のボタンとする
		UIImage* fileImage = [UIImage imageNamed:@"file.png"];
		UIImage* dirImage = [UIImage imageNamed:@"dir.png"];
		images_ = [[NSArray arrayWithObjects:fileImage, dirImage, nil] retain];

		// 右寄せ
		UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
		
		UIBarButtonItem *newFile  = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"file_new.png"] style:UIBarButtonItemStyleBordered target:self action:@selector(newFileDidPush)];
		UIBarButtonItem *newDir  = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"dir_new.png"] style:UIBarButtonItemStyleBordered target:self action:@selector(newDirDidPush)];

		NSArray *items = [NSArray arrayWithObjects:space, newFile, newDir, nil];
		[self setToolbarItems:items];

		// 編集ボタンの表示(selfのeditButtonを設定してやるだけでいい)
		self.navigationItem.rightBarButtonItem = [self editButtonItem];

こうなります。
ボタンの表示

作成・削除処理(RootViewController.m)

Toolbarに追加したbuttonのactionに指定したメソッドでファイル・ディレクトリの作成を行ないます。

これはMSFileManagerの機能を使います。
削除は、呼び出しがボタン押下時ではなく、
TableViewを編集モードにしてCellが削除された時になります。

// 新しいファイルの作成
- (void)newFileDidPush {
	
	NSError *error;

	// 連番のファイル名を取得
	NSString *fileName = [self nextFileName:@"untitled file"];
	NSString *fileContents = @"testあいうえお";

	NSString *filePath = [path_ stringByAppendingPathComponent:fileName];
	[fileContents writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];

	[items_ addObject:fileName];
	[self.tableView reloadData];
}

// 新しいディレクトリの作成
- (void)newDirDidPush {

	NSError *error;
	
	// 連番のディレクトリ名を取得
	NSString *dirName = [self nextFileName:@"untitled folder"];

	NSString *dirPath = [path_ stringByAppendingPathComponent:dirName];
	[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:NO attributes:nil error:&error];
	
	[items_ addObject:dirName];
	[self.tableView reloadData];
}

// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    
	// Cellが削除された時、ファイルとitemsからも削除する
    if (editingStyle == UITableViewCellEditingStyleDelete) {
		
		NSString *path = [path_ stringByAppendingPathComponent:[items_ objectAtIndex:indexPath.row]];
		NSError* error;
		
		[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
		
		// 配列からも消さないと落ちる
		[items_ removeObjectAtIndex:indexPath.row];
		
        // Delete the row from the data source
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
    }   
/*
	else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }
*/
}

2. ファイルブラウジング

新規作成したディレクトリをTableViewからアクセスして、
ファイルをDetailViewControllerに追加したTextViewで表示します。

ディレクトリ アクセス

Cell選択時にディレクトリだった場合、新たにRootViewControllerを作成し、
NavigationControllerにPushします。

- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    /*
     When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
     */
 //   detailViewController.detailItem = [NSString stringWithFormat:@"Row %d", indexPath.row];
	
	NSString *path = [path_ stringByAppendingPathComponent:[items_ objectAtIndex:indexPath.row]];

	BOOL isDir;

	[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];

	// ディレクトリだった場合、そのPathを設定したRootViewControllerを作成
	if (isDir) {		
		RootViewController *rootViewController = [[RootViewController alloc] initWithPath:path];
		// detailはrootがもつ必要ないんじゃ?(navあたりに持たせればいい)
		rootViewController.detailViewController = self.detailViewController;
		[self.navigationController pushViewController:rootViewController animated:YES];
	}

ファイル表示(RootViewController.m, DetailViewController.m)

選択されたのがファイルだった場合は、DetailViewControllerに追加したTextViewに表示します。

// RootViewController.m

	// ファイルだった場合はDetailに内容を表示
	else {
		detailViewController.path = path;		
	}
// DetailViewController.m

// pathプロパティが変化した時にTextViewの内容を変更する
- (void)setPath:(NSString *)path {
	
	[self saveContents];

	path_ = [path retain];
	NSError *error;
	textView_.text = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
}

こうなります。
説明は省略しましたが、アイコンもつけています。
ファイルブラウズ

3. ファイル情報の表示・名前の変更

最後におまけ的な機能ですが、ファイル更新日・サイズの表示と、
ファイル名変更機能を追加します。

ファイル情報画面の呼び出し(RootViewController.m)

ファイル情報画面の表示はCellのアクセサリボタンのタップで行ないます。

// アクセサリボタンがタップされた時はファイル情報表示画面に遷移する
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {

	NSString *path = [path_ stringByAppendingPathComponent: [items_ objectAtIndex:indexPath.row]];
	EdhitaTableViewController *tableViewController = [[EdhitaTableViewController alloc] initWithPath:path];
	[self.navigationController pushViewController:tableViewController animated:YES];
	[tableViewController release];	
}

ファイル情報表示・名前変更(EdhitaTableViewController.m)

情報の表示とファイル名の変更は新たに作成した、
EdhitaTableViewControllerで実装します。
RenameはMSFileManagerのMove機能を使います。

// ファイル情報をCellに表示する。
// かなり汚いのでリファクタリング必要
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
		cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

	// Configure the cell...
	cell.textLabel.text = [items_ objectAtIndex:indexPath.row];
	
	
	NSError *error;
	NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path_ error:&error];
	
	// switch内では変数宣言できないからif文の方が楽。
    if (indexPath.row == 0) {
		textField_ = [[UITextField alloc] initWithFrame:CGRectMake(cell.frame.size.width * 0.3, 0, cell.frame.size.width * 0.6, cell.frame.size.height)];
		textField_.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
		textField_.textAlignment = UITextAlignmentRight;
		textField_.delegate = self;
		textField_.text = [path_ lastPathComponent];
		textField_.returnKeyType = UIReturnKeyDone;
		textField_.clearButtonMode = UITextFieldViewModeWhileEditing;
		[textField_ becomeFirstResponder];		
		[cell.contentView addSubview:textField_];
	}
	else {
		UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(cell.frame.size.width * 0.3, 0, cell.frame.size.width * 0.6, cell.frame.size.height)];
		label.textAlignment = UITextAlignmentRight;
		label.backgroundColor = [UIColor clearColor];
		[cell.contentView addSubview:label];

		if(indexPath.row == 1) {
// timezoneが入ってくるので却下
//			textField.text = [[attributes objectForKey:NSFileModificationDate] description];
//			textField.text = [[attributes objectForKey:NSFileModificationDate] descriptionWithLocale:nil];

// documentに載ってるくせに。
//			textField.text = [[attributes objectForKey:NSFileModificationDate] descriptionWithCalendarFormat:@"%Y-%m-%d %H:%M:%S" timeZone:nil locale:nil];

			NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
			[dateFormatter setDateFormat:@"Y-MM-dd HH:mm:ss"];
			label.text = [dateFormatter stringFromDate:[attributes objectForKey:NSFileModificationDate]];						
		}
		else if(indexPath.row == 2) {
			label.text = [NSString stringWithFormat:@"%@ bytes", [attributes objectForKey:NSFileSize]];
		}
	}

    return cell;
}


// ファイル名変更(textField編集完了時と、Viewの非表示化の際に呼ばれる)
- (void)renameFile {
	NSError *error;
	NSString *dstPath = [[path_ stringByDeletingLastPathComponent] stringByAppendingPathComponent:textField_.text];
	[[NSFileManager defaultManager] moveItemAtPath:path_ toPath:dstPath error:&error];
}

完成

できました。 ファイル情報

[まとめ]

これで最低限のテキストエディタ機能はできました。
次回はいよいよSyntax Highlightです。
(まだどうやればいいか、検討もついてませんが。)

その前に、細かいバグ潰しをやらないとなぁ…。

[コード]

今回のコードは「Edhita3.zip」に入っています。
GitHubのdownloadページからダウンロードして下さい。

Downloads for tnantoka's Edhita - GitHub

Home

Search
Loading
Feeds
Links
スポンサードリンク

Page Top