忍者ブログ

Home > Edhita

Edhita Archive

[PR]

  • 2017-06-28 (Wed)
×

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

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

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

PR

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

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

第2回です。

今回はSplitViewを作ります。iPadアプリなUIといえばこれですね。
※ ちなみに、IBを使えば新規プロジェクトでSplitVIew basedを選ぶだけで終わりです。

作るのは2ペインの単純なアプリ。
左ペインで選んだ文字列を、右ペインに表示するだけです。
(Portraitモードの時は1ペインにして、代わりにPopViewを使用します)
まだテキストエディタとしての機能はありません。

それでは早速始めます。

[参考文献]

今回の参考サイトは以下のとおり。
参考書籍は第1回をご参照下さい。

iPadで追加されたUISplitViewを試してみる « CLSmooth BLOG…

RootViewController

まずはleft paneとなるRootViewControllerを作ります。
と言っても特別なことはしておらず、何の変哲もないTableViewControllerです。

RootViewController.h

ヘッダファイルはこんな感じです。

#import 

@class DetailViewController;

@interface RootViewController : UITableViewController {
    DetailViewController *detailViewController;
}

//@property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
@property (nonatomic, retain) DetailViewController *detailViewController;

@end

RootViewController.m

お次は実装ファイル。

#import "RootViewController.h"
#import "DetailViewController.h"


@implementation RootViewController

@synthesize detailViewController;


#pragma mark -
#pragma mark View lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
    self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
}

/*
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}
*/
/*
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}
*/
/*
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}
*/
/*
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
}
*/

// Ensure that the view controller supports rotation and that the split view can therefore show in both portrait and landscape.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES;
}


#pragma mark -
#pragma mark Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView {
    // Return the number of sections.
    return 1;
}


- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    return 10;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"CellIdentifier";
    
    // Dequeue or create a cell of the appropriate type.
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    
    // Configure the cell.
    cell.textLabel.text = [NSString stringWithFormat:@"Row %d", indexPath.row];
    return cell;
}


/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return NO if you do not want the specified item to be editable.
    return YES;
}
*/


/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // 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
    }   
}
*/


/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
}
*/


/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return NO if you do not want the item to be re-orderable.
    return YES;
}
*/


#pragma mark -
#pragma mark Table view delegate

- (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];
}


#pragma mark -
#pragma mark Memory management

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Relinquish ownership any cached data, images, etc. that aren't in use.
}

- (void)viewDidUnload {
    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
    // For example: self.myOutlet = nil;
}


- (void)dealloc {
    [detailViewController release];
    [super dealloc];
}


@end

もうお気づきかもしれませんが、IBでSplitVIew basedを選択した時に自動作成されるファイルを流用しています。
プロな方はどうか知りませんが、少なくとも僕はこんな内容を一から書くスキルも元気もありません。
というわけで、太字部分以外はそのままです。
(つまりヘッダファイルのプロパティからIBOutletを削っただけ)

DetailViewController

次にright paneとなる、DetailVIewControllerです。
見た目は単なるToolbar付きViewControllerですが、
UISplitViewControllerのDelegteとなっており、
画面が回転した際、ToolbarにPopover用のボタンを追加・削除する機能を持ちます。

DetailViewController.h

まずはヘッダファイル。rootview同様、IBOutletを消すだけです。

#import 

@interface DetailViewController : UIViewController  {
    
    UIPopoverController *popoverController;
    UIToolbar *toolbar;
    
    id detailItem;
    UILabel *detailDescriptionLabel;
}

//@property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
@property (nonatomic, retain) UIToolbar *toolbar;

@property (nonatomic, retain) id detailItem;
//@property (nonatomic, retain) IBOutlet UILabel *detailDescriptionLabel;
@property (nonatomic, retain) UILabel *detailDescriptionLabel;

@end

DetailViewController.m

次に実装。長いですが、最後にinitメソッドを追加して、
IBの代わりにtoolbar、labelを作成しているだけです。

#import "DetailViewController.h"
#import "RootViewController.h"


@interface DetailViewController ()
@property (nonatomic, retain) UIPopoverController *popoverController;
- (void)configureView;
@end



@implementation DetailViewController

@synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;

#pragma mark -
#pragma mark Managing the detail item

/*
 When setting the detail item, update the view and dismiss the popover controller if it's showing.
 */
- (void)setDetailItem:(id)newDetailItem {
    if (detailItem != newDetailItem) {
        [detailItem release];
        detailItem = [newDetailItem retain];
        
        // Update the view.
        [self configureView];
    }

    if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }        
}


- (void)configureView {
    // Update the user interface for the detail item.
    detailDescriptionLabel.text = [detailItem description];   
}


#pragma mark -
#pragma mark Split view support

- (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)pc {

    barButtonItem.title = @"Root List";
    NSMutableArray *items = [[toolbar items] mutableCopy];
    [items insertObject:barButtonItem atIndex:0];
    [toolbar setItems:items animated:YES];
    [items release];
    self.popoverController = pc;
}


// Called when the view is shown again in the split view, invalidating the button and popover controller.
- (void)splitViewController: (UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
    
    NSMutableArray *items = [[toolbar items] mutableCopy];
    [items removeObjectAtIndex:0];
    [toolbar setItems:items animated:YES];
    [items release];
    self.popoverController = nil;
}


#pragma mark -
#pragma mark Rotation support

// Ensure that the view controller supports rotation and that the split view can therefore show in both portrait and landscape.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES;
}


#pragma mark -
#pragma mark View lifecycle

/*
 // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
}
 */

/*
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}
*/
/*
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}
*/
/*
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}
*/
/*
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
}
*/

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


#pragma mark -
#pragma mark Memory management

/*
- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}
*/

- (void)dealloc {
    [popoverController release];
    [toolbar release];
    
    [detailItem release];
    [detailDescriptionLabel release];
    [super dealloc];
}

- (id)init {
	if (self = [super init]) {
		self.view.backgroundColor = [UIColor whiteColor];
		
		toolbar = [[UIToolbar alloc] init];
		[toolbar sizeToFit];		
		[self.view addSubview:toolbar];
		// これやっとかないとpopview出すボタンが追加できない([toolbar items]がnullになるから)
		[toolbar setItems: [NSArray array]];
		
		// heightはdefaultのfontsizeに合わせて17
		detailDescriptionLabel = [[UILabel alloc] initWithFrame: CGRectMake(0, self.view.bounds.size.height * 0.5, self.view.bounds.size.width, 17)];
		// 常に上下中央
		detailDescriptionLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
		detailDescriptionLabel.textAlignment = UITextAlignmentCenter;

		detailDescriptionLabel.text = @"Detail view content goes here";
		[self.view addSubview:detailDescriptionLabel];		
	}
	return self;
}

@end

AppDelegate

最後にAppDelegate。
実はこいつが一番修正が多かったりする。
(太字が前回からの修正点)

AppDelegate.h

rootとdetailのヘッダファイルをimport。

#import 

#import "RootViewController.h"
#import "DetailViewController.h"

@interface EdhitaAppDelegate : NSObject  {
    UIWindow *window;
}

//@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

AppDelegate.m

前回はwindowにlabelを突っ込んでたけど、
今回はrootとdetailをもとにSplitViewを作成。
※ rootはNavigationControllerのrootとして設定

#import "EdhitaAppDelegate.h"

@implementation EdhitaAppDelegate

// @synthesize window;


#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
	
    // Override point for customization after application launch.
	
	window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

	RootViewController *rootViewController = [[RootViewController alloc] init];
	// tableview単体じゃ仕方ないのでnavviewでwrap
	UINavigationController* navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
	
	DetailViewController *detailViewController = [[DetailViewController alloc] init];

	rootViewController.detailViewController = detailViewController;

	UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
	splitViewController.viewControllers = [NSArray arrayWithObjects:navigationController, detailViewController, nil];
	splitViewController.delegate = detailViewController;
	
	[window addSubview:splitViewController.view];
	[rootViewController release];
	[navigationController release];
	[detailViewController release];
	
    [window makeKeyAndVisible];
	
	return YES;
}


- (void)applicationWillResignActive:(UIApplication *)application {
    /*
     Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
     Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
     */
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    /*
     Restart any tasks that were paused (or not yet started) while the application was inactive.
     */
}


- (void)applicationWillTerminate:(UIApplication *)application {
    /*
     Called when the application is about to terminate.
     */
}


#pragma mark -
#pragma mark Memory management

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    /*
     Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later.
     */
}


- (void)dealloc {
    [window release];
    [super dealloc];
}


@end

完成

landscapeモード
poatraitモード

[まとめ]

第2回目の今回は単純なSplitViewアプリを作ってみました。
ようやくIBでSplitViewプロジェクトを作成したものに追いついたわけですね。

次回以降いよいよテキストエディタの機能を追加していきます。
ご期待下さい。

[コード]

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

Downloads for tnantoka's Edhita - GitHub

IB不使用&オープンソースなiPadアプリ(テキストエディタ)を作る 第1回:プロジェクトの作成

3月にv1.0、7月にv1.5(.1)を公開したjsanyですが、
たくさんの方に使っていただいているようで、嬉しい限りです。

ただ、何もわかってない頃に作り始めたこともあり、こいつの内部実装はもうぐちゃぐちゃです。
その結果、1.5で重大なバグを出してしまい、すぐ1.5.1を出すハメになりました。

このままいくといずれ破綻しそうなので、
オープンソースなアプリで腕を磨いて、改めてObjective-Cを勉強しなおしたいと思います。

題材はテキストエディタ、名前はまんまですが「edhita」です。
中身を深く理解する為、IBは使用しません。
また、ソースコードは全てgithubで公開します。

このアプリ作成で得たノウハウをjsanyにFeedbackしていこうという魂胆です。
※ ホントはjsanyをオープンソースにしたいんですが、
 今のコードは恥ずかしすぎてとても晒せないので。
 (ただでさえ高くないプログラマとしての評判がガタ落ちしちゃいます)

では、始めます。
初回はIB不使用なiPadアプリプロジェクトを作成するところまでやります。

[参考書籍]

と、その前に参考にした書籍を紹介しておきます。
他に参考にした情報があれば、それぞれの回で記載します。

photo
iPhoneプログラミングUIKit詳解リファレンス
所 友太 京セラコミュニケーションシステム株式会社
リックテレコム 2010-01-12

by G-Tools , 2010/08/08

photo
詳解 Objective-C 2.0
荻原 剛志
ソフトバンククリエイティブ 2008-05-28

by G-Tools , 2010/08/08

photo
iPadプログラミングの作法
橋本佳幸
秀和システム 2010-06-15

by G-Tools , 2010/08/08

2010/09/09 追加

photo
日経ソフトウエア 2010年 10月号 [雑誌]
日経ソフトウエア
日経BP社 2010-08-24

by G-Tools , 2010/09/09

[IB不使用なプロジェクトの作成]

前置きが長くなりました。

IBを使わないプロジェクトは、以下の手順で作成できます。
簡単ですね。

  1. Xcodeの新規プロジェクトから、「Window-based Appication」、Productは「iPad」を選択。
    新規プロジェクト作成
  2. MainWindow.xibファイルを削除する。
    (参照を削除じゃなく、一緒にゴミ箱に入れる)
    xibファイルの削除
  3. 「プロジェクト名-info.plist」の「Main nib file base name」を項目ごと削除。
    plist削除前
    plist削除後
  4. main.mのmain()を修正。
    (plistに指定したnib fileから自動で取得されていたものをコードで指定)
    int main(int argc, char *argv[]) {
        
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    //    int retVal = UIApplicationMain(argc, argv, nil, nil);
        int retVal = UIApplicationMain(argc, argv, nil, @"EdhitaAppDelegate");
        [pool release];
        return retVal;
    }
  5. AppDelegateの修正。
    (自動で作成されていたUIWindowインスタンスを作成し、viewとlabelを追加。)
    //  EdhitaAppDelegate.m
    
    // @synthesize window;
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    	
        // Override point for customization after application launch.
    	
    	window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    	
    	UIView *view = [[UIView alloc] initWithFrame: [window frame]];
    	[window addSubview:view];
    
    	UILabel *label = [[UILabel alloc] initWithFrame: [window frame]];
    	label.text = @"Hello, iPad!";
    	label.textAlignment = UITextAlignmentCenter;
    	label.font = [[UIFont alloc] fontWithSize: 50];
    	[view addSubview:label];
    	
        [window makeKeyAndVisible];
    	
    	return YES;
    }
    //  EdhitaAppDelegate.h
    
    //@property (nonatomic, retain) IBOutlet UIWindow *window;
  6. ビルドと実行。
    実行結果

[まとめ]

今回の作業はこれで終了です。
俗に言うHello, world!ですね。

次回以降、テキストエディタとしての機能を追加していきます。
宜しくお付き合い下さい。

突っ込みもお待ちしています。

[コード]

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

Downloads for tnantoka's Edhita - GitHub

[おまけ:目次]

こんな感じで進めたい。

  • 第1回:プロジェクトの作成
  • 第2回:SplitView
  • 第3回:ファイル操作とNavigationController
  • 第4回:Syntax Highlighting
  • 第5回:Accessory View
  • 第6回:広告
  • 第7回:FTP
  • 第?回:未定

Home > Edhita

Search
Loading
Feeds
Links
スポンサードリンク
Ad
デル株式会社
Ad

Page Top