MindTo01s https://www.mindto01s.com/ ( ゚∀゚)人( ゚∀゚) en-us Mon, 17 Feb 2020 00:00:00 +0900 https://www.mindto01s.com/2020/02/17/f444a892_fb25_4515_938d_faa938db0ac2.html https://www.mindto01s.com/2020/02/17/f444a892_fb25_4515_938d_faa938db0ac2.html <![CDATA[キーボードショートカットのデバッグ用コード]]> キーボードショートカットのデバッグ用コード

タイトルそのまま。ソース有り、解説なし。

KeyTouchViewer.zip

]]>
Mon, 17 Feb 2020 00:00:00 +0900
https://www.mindto01s.com/2019/10/29/fbe5914c_2046_462f_a7d3_e0ee4d575f7b.html https://www.mindto01s.com/2019/10/29/fbe5914c_2046_462f_a7d3_e0ee4d575f7b.html <![CDATA[非公開APIを使わない、Migemoみたいな逐次検索]]> 非公開APIを使わない、Migemoみたいな逐次検索

Cocoa勉強会2019/10/29の資料。

Cocoa_2019-10-30.zip を解凍すること。

辞書データはライセンスの関係で添付していない。

]]>
Tue, 29 Oct 2019 00:00:00 +0900
https://www.mindto01s.com/2019/09/19/5022d13e_6c93_4032_8847_60fc62104179.html https://www.mindto01s.com/2019/09/19/5022d13e_6c93_4032_8847_60fc62104179.html <![CDATA[TSMが変換候補を表示しているか否かの判定]]> TSMが変換候補を表示しているか否かの判定

TSMが変換候補を評しているか否かを返す。 trueの場合は変換候補の表示中。

extension NSApplication
{
    @objc var isShowInputManagerPanel: Bool
    {
        return self.windows.first(where:{$0.className == "NSPanelViewBridge"}) != nil
    }
}
]]>
Thu, 19 Sep 2019 00:00:00 +0900
https://www.mindto01s.com/2019/09/16/e0d9b9fa_ac2b_48c6_ab7d_6bb40da704d9.html https://www.mindto01s.com/2019/09/16/e0d9b9fa_ac2b_48c6_ab7d_6bb40da704d9.html <![CDATA[ローマ字かな変換表]]> ローマ字かな変換表

ローマ字かな変換規則には、ヘボン式、訓令式、日本式の3つがある。

MacOSXのデフォルトのインプットメソッドでは、上記のどれでもない方式を採用している。

CFStringTransformよりも柔軟な変換が必要なために、変換規則の詳細を知る必要があったので調べていると、以下のパスに変換表があった。

“/System/Library/Input Methods/JapaneseIM.app/Contents/PlugIns/JapaneseIM.appex/Contents/Resources/RomajiRule_Default.txt”

同じディレクトリに、”RomajiRule_Kotoeri.txt”と”RomajiRule_Windows.txt”もあったが、kotoeriはおそらく古いMacOSXの変換規則が記載されている。windowsは文字どうりWindiwsの変換規則なのだろう。

]]>
Mon, 16 Sep 2019 00:00:00 +0900
https://www.mindto01s.com/2019/08/29/d2a6566d_08a9_4676_aad8_0f27e17c197c.html https://www.mindto01s.com/2019/08/29/d2a6566d_08a9_4676_aad8_0f27e17c197c.html <![CDATA[NSTextViewにCommand Paletteをつける]]> NSTextViewにCommand Paletteをつける

Cocoa勉強会2019/09/11の資料。

ソースコードだけ、資料は頭が回らない。あと、よく落ちるバグ付き。

CommandPalette.zip を解凍すること。

ソースコードの動作はムービーで動作確認したほうが早い( CommandPalette.mov )

エアコンはフィルターを掃除したら治った。

]]>
Thu, 29 Aug 2019 00:00:00 +0900
https://www.mindto01s.com/2019/08/04/6d98ac0b_1b62_4b88_bf96_37d285c17b21.html https://www.mindto01s.com/2019/08/04/6d98ac0b_1b62_4b88_bf96_37d285c17b21.html <![CDATA[午前1時、室温は29度。エアコンの調子がおかしい。]]> 午前1時、室温は29度。エアコンの調子がおかしい。

すごく、恐怖を感じる。

]]>
Sun, 04 Aug 2019 00:00:00 +0900
https://www.mindto01s.com/2019/07/30/f0ab28e1_1b0d_439f_826d_3f09a1e584cb.html https://www.mindto01s.com/2019/07/30/f0ab28e1_1b0d_439f_826d_3f09a1e584cb.html <![CDATA[NSUserDefaultsControllerでbinding出来るデータ構造についてのメモ]]> NSUserDefaultsControllerでbinding出来るデータ構造についてのメモ

NSUserDefaultsControllerとcocoa bindingを使い、初期設定WindowのUIを作ろうとして失敗した。 その失敗についての箇条書きのメモ

  • NSUserDefaultsが持っているデータ構造は、plist形式と考えて良い。
  • NSUserDefaultsがデータ構造に情報を溜め込むアクセッサ、setStringやsetIntgerなどは、keyを指定し、keyPathではない。
  • plistなデータ構造の深い位置にアクセスするにはkeyPathで指定できるべきである。
  • NSUserDefaultsで保持するデータは、keyで指定可能な位置の物は、mutableである。keyPathが必要な場所は、immutableになっているように見える。
  • ググると、mutableCopyした後に変更を加えて、setObjectしている事例がいくつかある。
  • NSUserDefaultsControllerは、深い階層へのPathのアクセスの不具合や、immutableなArrayやdictonaryの問題を解決するわけではない。
  • NSUserDefaultsControllerで使うPathは”values.key”のようにドットは1つまでにする必要がある。”value.key1.key2”のようにする、中間のimmutableなobjectがKVOに対応していないとログに警告とエラーが出る。
  • 泥臭い解決方法は、plist形式と言いつつ、最初の階層だけのフラットな構造にすると良い。”values.key1.key2”を”values.key1_key2”のようにする。
  • NSUserDefaultsControllerのselectedObjectにさらにNSArrayControllerを噛ませて、TableViewで表示する方法は、NSUserDefaultsControllerがNSArrayController側の変更を検知できないようだ。
  • おそらく、NSUserDefaultsControllerで取得した値をdeepMutableCopyして、ViewControllerに持たせ、そのデータが変わった時に、NSUserDefaultsControllerに反映させる方法が良いようだ。

もうやり気が出ない。

]]>
Tue, 30 Jul 2019 00:00:00 +0900
https://www.mindto01s.com/2019/07/25/92329121_f2fc_486c_814b_91c49a312a1a.html https://www.mindto01s.com/2019/07/25/92329121_f2fc_486c_814b_91c49a312a1a.html <![CDATA[室温が30度を超える。]]> 室温が30度を超える。

平年並みとの事。頭痛がするほど暑く感じる。

扇風機だけでは頭が働かない。仕事部屋にクーラーが欲しい。

]]>
Thu, 25 Jul 2019 00:00:00 +0900
https://www.mindto01s.com/2019/07/15/870817e1_2571_4ca9_9567_290244e4a88f.html https://www.mindto01s.com/2019/07/15/870817e1_2571_4ca9_9567_290244e4a88f.html <![CDATA[Symbol font を使う]]> Symbol font を使う

Cocoa勉強会2019/07/17の資料。

SymbolFontを使うためにpngファイルを出力するソースコードと資料。

SymbolFontApp.zip を解凍すること。

]]>
Mon, 15 Jul 2019 00:00:00 +0900
https://www.mindto01s.com/2019/07/14/b5cf0d81_ac21_43ab_9201_93a0046766e7.html https://www.mindto01s.com/2019/07/14/b5cf0d81_ac21_43ab_9201_93a0046766e7.html <![CDATA[NSUserDefaultsControllerでUndo/Redo]]> NSUserDefaultsControllerでUndo/Redo

Cocoa勉強会2019/07/17の資料。

NSUserDefaultsControllerでUndo/Redoさせるためのソースコードと資料。

NSUserDefaultsController&UndoRedo.zip を解凍すること。

]]>
Sun, 14 Jul 2019 00:00:00 +0900
https://www.mindto01s.com/2019/06/03/87648c77_3dd0_4cbb_89e8_680804a3aca1.html https://www.mindto01s.com/2019/06/03/87648c77_3dd0_4cbb_89e8_680804a3aca1.html <![CDATA[DocumentとDefault]]> DocumentとDefault

Cocoa勉強会2019/06/12の資料。

DocumentDefaultsを解説するためのソースコードと資料。

DocumentDefault.zip を解凍すること。

なお、元ネタは”CoreData Apple’s API for Persisting Data on Mac OSX”のP255からの、”Dynamic Parameters”。コードはObjective-Cに出来るだけ似せているつもり。

元の本のswift版がすでに出ているようだがそちらは入手していない。

]]>
Mon, 03 Jun 2019 00:00:00 +0900
https://www.mindto01s.com/2019/04/22/2bb3b8d7_34d4_4804_8beb_c51dfb48ef4c.html https://www.mindto01s.com/2019/04/22/2bb3b8d7_34d4_4804_8beb_c51dfb48ef4c.html <![CDATA[CoreDataとDrag&Drop]]> CoreDataとDrag&Drop

Cocoa勉強会2019/04/25の資料。

CoreDataとDrag&Dropを解説するためのソースコード。

memoList.zip を解凍すること。

ソースコードだけで、解説はなし。

]]>
Mon, 22 Apr 2019 00:00:00 +0900
https://www.mindto01s.com/2019/02/23/39689664_c712_472c_8284_df5d51806dc2.html https://www.mindto01s.com/2019/02/23/39689664_c712_472c_8284_df5d51806dc2.html <![CDATA[Tab機能の拡張を諦める。]]> Tab機能の拡張を諦める。

VisualにTabを選択したいため、タブを一覧表示で、カレントなWindowを示すコードを書こうとした。

../../../_images/CurrentTab.png

一応、以下の非公開メソッドを使うことでできそうだが実装は諦める。

tabの切り替えのための over view の画面は格好良いけど、”select next tab”にショートカットをつける方が画面がすぐに変わるので便利だ。

つまり、以下のコードを無駄。

@IBAction func log(_ sender: Any?)
{
    if let theStack = self.window!.value(forKeyPath:
        "windowStackController.tabPickerViewController.tabPickerController" ) as? NSViewController
    {
        if let orderedTabViewItems = theStack.value(forKey: "orderedTabViewItems") as? NSArray
        {
            if let indexOfSelectedTab = theStack.value(forKey: "indexOfSelectedTab") as? NSNumber
            {
                if let theSelectedObject = orderedTabViewItems.object(at: indexOfSelectedTab.intValue) as? NSObject
                {
                    // タイトルを" this is a Current Tab "に変更
                    theSelectedObject.setValue("this is a Current Tab", forKey: "label")
                    // 再描画させる
                    theStack.perform(NSSelectorFromString("reloadTabBarItem:"), with: theSelectedObject)
                }
            }
        }
    }
}
]]>
Sat, 23 Feb 2019 00:00:00 +0900
https://www.mindto01s.com/2019/02/08/faf1e311_3fdd_4f4d_914f_4a643732cf0d.html https://www.mindto01s.com/2019/02/08/faf1e311_3fdd_4f4d_914f_4a643732cf0d.html <![CDATA[Servicesメニューの実装]]> Servicesメニューの実装

Cocoa勉強会2019/02/20の資料。NSServicesメニューの実装の説明。

プロジェクトファイルは、 NSServicesメニューの実装.zip に置いておきます。

]]>
Fri, 08 Feb 2019 00:00:00 +0900
https://www.mindto01s.com/2019/02/06/1089942e_b4cd_4364_aa53_231ad6116030.html https://www.mindto01s.com/2019/02/06/1089942e_b4cd_4364_aa53_231ad6116030.html <![CDATA[ヒレガス本 21章 課題1 swift版]]> ヒレガス本 21章 課題1 swift版

昨日のヒレガス本の課題をswiftへ移植。あまり綺麗なコードにはならなかった。

それぞれのメソッドを地道に移植した。注意事項はない。必要なのは努力だけ。

プロジェクトファイルは、 TypingTutor_swift.zip に置いておきます。

]]>
Wed, 06 Feb 2019 00:00:00 +0900
https://www.mindto01s.com/2019/02/05/a9643198_1105_4881_bdd9_fae63db9e86d.html https://www.mindto01s.com/2019/02/05/a9643198_1105_4881_bdd9_fae63db9e86d.html <![CDATA[ヒレガス本 21章 課題1]]> ヒレガス本 21章 課題1

サンプルコードの雛形が欲しかったので、久しぶりにヒレがス本の課題の続きを行なった。

第21章でBigLetterViewにコピー&ペースト機能をつけた。

課題は、コピー時に、テキストだけでなくPDFもペーストボードに書き込めとのこと。

単純にwriteToPasteboardメソッドを書き換える。

- (void)writeToPasteboard:(NSPasteboard*)pb
{
    [pb clearContents];

    // pdfデータの準備
    NSData* thePDFData  = [self dataWithPDFInsideRect:self.bounds];

    NSPasteboardItem* thePDFItem = [[NSPasteboardItem alloc] init];
    [thePDFItem setData:thePDFData forType:NSPasteboardTypePDF];

    NSPasteboardItem* theTextItem = [[NSPasteboardItem alloc] init];
    [theTextItem setString:self.string forType:NSPasteboardTypeString];

    [pb writeObjects:@[thePDFItem, theTextItem]];
}

一種類のデータだけならば、NSPasteboardを使うだけで良いが、複数のデータを扱う場合は、NSPasteboardItemを使う必要がある。

プロジェクトファイルは、 TypingTutor_3.zip に置いておきます。

]]>
Tue, 05 Feb 2019 00:00:00 +0900
https://www.mindto01s.com/2019/01/16/6483d7b4_60b8_478b_bff2_6d9a5bf1ba9f.html https://www.mindto01s.com/2019/01/16/6483d7b4_60b8_478b_bff2_6d9a5bf1ba9f.html <![CDATA[Cellを使ったControllサブクラス化]]> Cellを使ったControllサブクラス化

Cocoa勉強会2019/01/16の資料。NSCellの説明。

ソースはこれ。( Cellを使ったControllサブクラス化.zip.zip )

]]>
Wed, 16 Jan 2019 00:00:00 +0900
https://www.mindto01s.com/2019/01/15/b9ad9e05_b435_4c72_898c_46c11bccc0b7.html https://www.mindto01s.com/2019/01/15/b9ad9e05_b435_4c72_898c_46c11bccc0b7.html <![CDATA[CocoaでPACパターン]]> CocoaでPACパターン

Cocoa勉強会2019/01/16の資料。PACパターンの説明。

ソースはこれ。( PACパターンサンプル.zip )

]]>
Tue, 15 Jan 2019 00:00:00 +0900
https://www.mindto01s.com/2018/12/28/7a5fd1e1_4983_4a67_ac07_5b93832dddfe.html https://www.mindto01s.com/2018/12/28/7a5fd1e1_4983_4a67_ac07_5b93832dddfe.html <![CDATA[FontWellの再実装]]> FontWellの再実装

FontWellをswiftで再実装した。

BSFontWellを参考にしたつもりで、NSButtonとNSButtonCellのサブクラスから作成した。

Interface builder関連のコードやcocoa binding関連のコードを変更したら、BSFontWellとはコード上の関連は無くなってしまった。その上、コードの意味が分かりにくくなった気がする。

../../../_images/fontWell.png

ソースはこれ。( ControlSikenjo3.zip )

IB上からのbindingの設定方法

  1. “value”等の既存の値でGUI上でbindingを作る。
  2. “open as”で、.xibや.storybordファイルをxmlファイルとして開く
  3. “connections/binding”を探して、”value”から”fontValue”へ書き換える

これを

<connections>
    <binding destination="yT7-o4-ML5" name="value" keyPath="self.modelFont2" id="m1U-oD-u8W"/>
</connections>

こうする

<connections>
    <binding destination="yT7-o4-ML5" name="fontValue" keyPath="self.modelFont2" id="m1U-oD-u8W"/>
</connections>
  1. “open as”で、.xibや.storybordファイルをIBファイルとして開く

そーすると、以下のようにGUIで設定できるようになってる。

../../../_images/fontWellIB.png
]]>
Fri, 28 Dec 2018 00:00:00 +0900
https://www.mindto01s.com/2018/12/11/09613c24_c2b5_4851_a53d_53307201a985.html https://www.mindto01s.com/2018/12/11/09613c24_c2b5_4851_a53d_53307201a985.html <![CDATA[NSViewControllerのサンプルコードその2]]> NSViewControllerのサンプルコードその2

Inspectorパネルも共有するコードは失敗した。 Cocoa bindingがうまく結合できない。

失敗したソースはこれ。( ViewControllerSikenjo2.zip )

成功していないので、成功したソースはない。

]]>
Tue, 11 Dec 2018 00:00:00 +0900
https://www.mindto01s.com/2018/12/10/fe3017f0_3326_4f86_aedd_00a7b4b5755a.html https://www.mindto01s.com/2018/12/10/fe3017f0_3326_4f86_aedd_00a7b4b5755a.html <![CDATA[NSViewControllerのサンプルコードその1]]> NSViewControllerのサンプルコードその1

1つのViewControllerをsheet, dialog, popoverで共有する、サンプルコード。

../../../_images/ViewController1.png ../../../_images/ViewController2.png ../../../_images/ViewController3.png

ソースはこれ。( ViewControllerSikenjo1.zip )

]]>
Mon, 10 Dec 2018 00:00:00 +0900
https://www.mindto01s.com/2018/12/07/d239122a_8fc5_4c1d_a3b3_4b388cb33be5.html https://www.mindto01s.com/2018/12/07/d239122a_8fc5_4c1d_a3b3_4b388cb33be5.html <![CDATA[内側に影が付いている ScrollView]]> 内側に影が付いている ScrollView

タイトルそのまま。ScrollerViewに影を付ける事で、スクロールできる領域ですよと、控えめに主張するクラスを作った。

ソースはこれ。( ControlSikenjo2.zip )

サンプルコードの注意事項は、「真四角」と名前がつけられた画像ファイルには、「たてなが」と描かれています。

ややこしいですが、640x640の正方形の画像です。

バグではなくて仕様です。

]]>
Fri, 07 Dec 2018 00:00:00 +0900
https://www.mindto01s.com/2018/12/04/6a77dec8_f378_4a0f_b3c7_d053e0dfa8cc.html https://www.mindto01s.com/2018/12/04/6a77dec8_f378_4a0f_b3c7_d053e0dfa8cc.html <![CDATA[長押しするPopupmenuのボタン]]> 長押しするPopupmenuのボタン

タイトルそのまま。ActionPopupButton、長押しするPopupmenuのボタン。

ソースはこれ。( ControlSikenjo1.zip )

>> ソースへのリンクが切れているように思います ありがとう。リンク切れ直しました。

]]>
Tue, 04 Dec 2018 00:00:00 +0900
https://www.mindto01s.com/2018/12/01/51fd631a_6e9e_4948_8de6_00db7f5e99ed.html https://www.mindto01s.com/2018/12/01/51fd631a_6e9e_4948_8de6_00db7f5e99ed.html <![CDATA[Responder chainの表示]]> Responder chainの表示

パレットに、first responder からの responder chain を表示する。 デバッグ用。

サンプルコードでは、control-Rでパレットを表示すするようにしている。

カギカッコの中のアルファベットの意味は、”R”はNSResponder、”D”はdelegate、”O”はその他。

delegate, NSApplication, NSDocumentController, NSDocumentのresponder chainの中での位置づけは、NSResponderのサブクラスの物とは異なるので、別に処理している。

../../../_images/ResponderChain.png

アップし直した。ソースはこれ。( BoxShadowExample2.zip )

]]>
Sat, 01 Dec 2018 00:00:00 +0900
https://www.mindto01s.com/2018/11/26/86fd0e18_57fc_4cc6_b201_f65e94a4c082.html https://www.mindto01s.com/2018/11/26/86fd0e18_57fc_4cc6_b201_f65e94a4c082.html <![CDATA[Cocoaで Box Shadow]]> Cocoaで Box Shadow

ほんのりと、片側がめくれる感じで。

../../../_images/BoxShadowExample.png

ソースはこれ。( BoxShadowExample.zip )

]]>
Mon, 26 Nov 2018 00:00:00 +0900
https://www.mindto01s.com/2018/10/28/d433f41f_6862_4f3f_874a_57d06c2c93e8.html https://www.mindto01s.com/2018/10/28/d433f41f_6862_4f3f_874a_57d06c2c93e8.html <![CDATA[NSPasteboardの動作を調べるために小さなアプリを作ってみた(2)]]> NSPasteboardの動作を調べるために小さなアプリを作ってみた(2)

ソースはこれ。( pasteApp2.zip )

改良点は、NSPasteboardItemのtypeの中身を表示するように変更した。

]]>
Sun, 28 Oct 2018 00:00:00 +0900
https://www.mindto01s.com/2018/10/26/33f533e3_43e2_4928_a1d7_741cd93200fa.html https://www.mindto01s.com/2018/10/26/33f533e3_43e2_4928_a1d7_741cd93200fa.html <![CDATA[NSPasteboardの動作を調べるために小さなアプリを作ってみた]]> NSPasteboardの動作を調べるために小さなアプリを作ってみた

結論

  1. NSPasteboardItemを複数持つアプリは、自作アプリしか確認できなかった。

    enumerateDraggingItemsWithOptionsはあまり使われていないのではないだろうか?

  2. NSPasteboardItemに入るUTIには”dyn.xxx”のような不明なUTIも存在する。

    おそらく、同一アプリ内でのCopy&Pasteする場合の判定用の一時的なUTIと思われる。

  3. NSPasteboardItemに持たせるデータの種類は意外に多い

    同じテキストでもutf8とutf16で別々に持たせていた。

  4. Drag & Drop 中のデータのUTIが観れるのは楽しい。

    macOS7時代では、Drag & Drop をサポートするのに苦労したから楽できるのは良い。

ソースはこれ。( pasteApp.zip )

]]>
Fri, 26 Oct 2018 00:00:00 +0900
https://www.mindto01s.com/2018/10/17/0701622a_cdad_4987_bdd1_63154a880247.html https://www.mindto01s.com/2018/10/17/0701622a_cdad_4987_bdd1_63154a880247.html <![CDATA[TabUIの実験]]> TabUIの実験

こっち。( TabUI.zip )

]]>
Wed, 17 Oct 2018 00:00:00 +0900
https://www.mindto01s.com/2018/09/12/b3c69653_67f6_4e8a_85ef_73fa85a81568.html https://www.mindto01s.com/2018/09/12/b3c69653_67f6_4e8a_85ef_73fa85a81568.html <![CDATA[ライフゲームその5 swiftで書き直した]]> ライフゲームその5 swiftで書き直した

すまん。こっちだ。( haifusiryou.zip )

]]>
Wed, 12 Sep 2018 00:00:00 +0900
https://www.mindto01s.com/2018/09/02/785a4c5f_5de1_42ef_9b07_2f94d43a2eb4.html https://www.mindto01s.com/2018/09/02/785a4c5f_5de1_42ef_9b07_2f94d43a2eb4.html <![CDATA[ライフゲームその4 swiftで書き直した]]> ライフゲームその4 swiftで書き直した

Documentベースにして、NSMatrxiの使用をやめて、Swiftで書き直した。

ソースコードはここ。( ConwaysGameOfLife.zip )

]]>
Sun, 02 Sep 2018 00:00:00 +0900
https://www.mindto01s.com/2018/08/16/1bb6f924_2f11_4bb3_bc32_168b66b996e8.html https://www.mindto01s.com/2018/08/16/1bb6f924_2f11_4bb3_bc32_168b66b996e8.html <![CDATA[NSMenuの中にNSViewを入れる]]> NSMenuの中にNSViewを入れる

ObjCの以下のコードを参考にしてSwiftで書いた。

https://developer.apple.com/library/archive/samplecode/MenuItemView/Introduction/Intro.html

ViewInMenu.zip

今回はやくにたつかも

]]>
Thu, 16 Aug 2018 00:00:00 +0900
https://www.mindto01s.com/2018/08/10/036a37ae_42ed_41ad_a751_f369a56a47d7.html https://www.mindto01s.com/2018/08/10/036a37ae_42ed_41ad_a751_f369a56a47d7.html <![CDATA[落穂拾い 2018/08/10]]> 落穂拾い 2018/08/10 ]]> Fri, 10 Aug 2018 00:00:00 +0900 https://www.mindto01s.com/2018/07/24/7b66f018_7d1d_468e_ab70_bcdeac4b0d80.html https://www.mindto01s.com/2018/07/24/7b66f018_7d1d_468e_ab70_bcdeac4b0d80.html <![CDATA[VirtualBoxへのMojaveのインストール]]> VirtualBoxへのMojaveのインストール

概要

インストールする上での問題は2つある。

  • MojaveではUSB1.1コントローラはサポート対象外
  • Mojaveのインストーラは、インストール先のHDD/SSDをAPFSに変換するが、VirtualBoxのBIOSはAPFSフォーマットのHDD/SSDからの起動をサポートしていない。

このため、通常の方法では、VirtualBoxへMojaveをインストールすることは出来ない。

USB機器が使えない問題の解決方法は簡単。VirtualBoxのextraPackをインストールして、USB3.0コントローラを使用するようにするだけで解決できる。

APFSフォーマット問題の解決方法は簡単だが面倒。MojaveをインストールしたAPFSフォーマットのHDD/SSDから、別途用意したHFS+フォーマットのHDD/SSDへコピーする事で解決する。

インストール手順

準備

“Oracle_VM_VirtualBox_Extension_Pack”を入手してインストールする。

入手先は、http://www.oracle.com/technetwork/server-storage/virtualbox/downloads/index.html

Mojaveベータの入手は、Developer Programに入っていればダウンロード方法は分かるはずなので明記しない。

ダウンロード後に、VirtualBox用のmacOSインストールディスクを作る を参考にしてisoファイルを作る。

$ hdiutil create -o /tmp/Mojave -size 8G -layout SPUD -fs HFS+J -type SPARSE

$ hdiutil attach /tmp/Mojave.sparseimage -noverify -mountpoint /Volumes/install_build
/dev/disk3                  Apple_partition_scheme
/dev/disk3s1                Apple_partition_map
/dev/disk3s2                Apple_HFS                       /Volumes/install_build

$ sudo ./Install\ macOS\ Mojave\ Beta.app/Contents/Resources/createinstallmedia  --volume /Volumes/install_build

Password:
Ready to start.
To continue we need to erase the volume at /Volumes/install_build.
If you wish to continue type (Y) then press return: Y
Erasing disk: 0%... 10%... 20%... 30%... 100%
Copying to disk: 0%... 10%... 20%... 100%
Making disk bootable...
Copying boot files...
Install media now available at "/Volumes/Install macOS Mojave Beta"
$ hdiutil detach /Volumes/Install\ macOS\ Mojave\ Beta/
"disk3" unmounted.
"disk3" ejected.
$ hdiutil convert /tmp/Mojave.sparseimage -format UDTO -o /tmp/Mojave.iso
Driver Descriptor Map(DDM: 0)を読み込み中…
Apple(Apple_partition_map: 1)を読み込み中…
(Apple_Free: 2)を読み込み中…
disk image(Apple_HFS: 3)を読み込み中…
.............................................................................................................................
経過時間: 19.920s
速度: 411.2Mバイト/秒
節約率: 0.0%
created: /tmp/Mojave.iso.cdr
$ mv /tmp/Mojave.iso.cdr ~/Desktop/Mojave.iso
$ rm /tmp/Mojave.sparseimage

デスクトップに以下のようなアイコンが出来る。ファイル名は、”Mojave.iso”

../../../_images/isoIcon1.png

VMの作成

VirtualBox上のmacOS用VMの作成 を参考にしてVMを作成する。

設定する値は以下の通り。

名前 任意
タイプ Mac OS X
バージョン mac OS 10.13 Hight Sierra (64-bit)
メモリーサイズ 4096
ハードディスク 仮想ハードデスクを作成する
ファイルサイズ 50.00GB
ハードデスクのファイルタイプ VDI
物理ハードディスクにあるストレージ 可変サイズ

上記の設定でVMを設定後、各種細かい設定を修正してゆく。

システム ‣ マザーボード ‣ 起動順序 ‣ フロッピー のチェックボックスを外す。

システム ‣ プロセッサー ‣ プロセッサー数 の値を2に変更

システム ‣ プロセッサー ‣ 使用率制限 の値を80%に変更

ディスプレイ ‣ スクリーン ‣ ビデオメモリー の値を128Mに変更

オーディオ ‣ オーディオを有効化 のチェックボックスを外す。

ストレージ ‣ ストレージデバイス ‣ 内蔵ディスクを選ぶ(名前はそれぞれ) ‣ 属性 ‣ SSD のチェックボックスを外す。

ストレージ ‣ ストレージデバイス ‣ 光学デバイスを選ぶ(名前は空) ‣ 光学ドライブ の右側の光学ドライブボタンを押す。そこは、ポップアップメニューになっている。”Mojave.iso”を選ぶ。

ストレージ ‣ ストレージデバイス ‣ 下のひし形にプラス記号のアイコン を選び、USBコントローラを追加する。

../../../_images/MJV_SET01.png

ストレージ ‣ ストレージデバイス ‣ コントローラ:USB ‣ HDDにプラス記号のアイコン を選び、仮想外付HDを作成する。

../../../_images/MJV_SET02.png ../../../_images/MJV_SET03.png ../../../_images/MJV_SET04.png

ポート ‣ USB ‣ USBコントローラを有効 ‣ USB 3.0 コントローラ を選ぶ。

../../../_images/MJV_SET05.png

メディアの初期化

作成したVMを起動すると、Mojave.isoから起動される。

日本語を選ぶ。

../../../_images/MJV_SET06.png

仮想内蔵HDと仮想外付HDをHFS+にフォーマットするために、Disk Utilityを選ぶ。

../../../_images/MJV_SET07.png

ここで、Disk Utilityを選び、仮想内蔵HDと仮想外付HDをHFS+にフォーマットする。

仮想内蔵HDをHFS+にフォーマットする。

../../../_images/MJV_SET08.png

仮想外付HDをHFS+にフォーマットする。

../../../_images/MJV_SET09.png

Disk Utilityを終了し、最初の画面に戻り、MacOSインストールを選ぶ。

../../../_images/MJV_SET10.png

インストールの設定を開始する。

../../../_images/MJV_SET11.png

インストール先のHDは仮想外付HDの”External_HD”。アイコンが違うので名前が違っても間違わないはず。

../../../_images/MJV_SET12.png

インストール作業が続行される。

../../../_images/MJV_SET13.png

しばらくすると、コンソール画面のようになった後、再起動され再び、以下の画面になる。

../../../_images/MJV_SET07.png

インストール開始

VMを一度終了させる。

リンゴ ‣ システム終了 を選ぶ。

再び、VMの設定を開き、光学デバイスのDVDをアンマウントする。

../../../_images/MJV_SET14.png

ここで、VMを起動する。起動できるメディアがないので以下のような画面になる。

../../../_images/MJV_SET15.png

“exit”と打ち込み”return”キーを押すと、以下のような画面へゆく。

../../../_images/MJV_SET16.png

VMへのmacOSのインストールの続き を参考にbootファイルを設定する。

../../../_images/MJV_SET17.png

Boot Maintenance Manager ‣ Boot From File ‣ PociRoot….HD(2,GPT,…)] を選ぶ。

../../../_images/MJV_SET18.png

<macOS Install Data> ‣ <Locked Files> ‣ <Boot Files> ‣ boot.efi] を選ぶ。

再び、コンソール画面のようにログが流れる。

落ち着いた頃に、以下の画面になる。

../../../_images/MJV_SET19.png

これで、仮想外付HDはAPFSにフォーマットされ、mojaveがインストールされる。この後、再起動されるがVirtualBoxはAPFSをサポートしないので、OSは起動しない。

そして再び、以下の画面になる。

../../../_images/MJV_SET15.png

ここで、またVMを終了(Power off)する。

ファイルのコピー

再びインストールメディアを使う。正確にはその中のターミナルを使う。

VMの設定で、ストレージ ‣ ストレージデバイス ‣ 光学デバイスを選ぶ(名前は空) ‣ 光学ドライブ の右側の光学ドライブボタンを押す。そこは、ポップアップメニューになっている。”Mojave.iso”を選ぶ。

VMを起動。再び以下の画面になる。

../../../_images/MJV_SET10.png

ユーティリティ ‣ ターミナル を選ぶ。

../../../_images/MJV_SET20.png

以下のコマンドで外付HDから内蔵HDに全部コピーする。

cp -f -p -R /Volumes/外付HD名/ /Volumes/内蔵HD名

次に、以下のコマンドで、内蔵HDを起動可能にする

bless -folder /Volumes/内蔵HD名/System/Library/CoreServices
../../../_images/MJV_SET21.png

VMを終了させ、仮想DVDをアンマウントする。再び、VMを起動する。

アカウントの設定画面になるので、設定すると、以下のような画面になってインストールが完了する。

../../../_images/MJV_SET22.png

なお、仮想外付HDは不要なのでアンマウントして削除して良い。

]]>
Tue, 24 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/07/13/29c89a9d_25c1_4da9_b521_8fade247de58.html https://www.mindto01s.com/2018/07/13/29c89a9d_25c1_4da9_b521_8fade247de58.html <![CDATA[Guest/HostもmacなVirtualBoxのストレージの拡張方法]]> Guest/HostもmacなVirtualBoxのストレージの拡張方法

コマンドラインを使ってVMのストレージ容量を拡張

まずは、VMのUUIDを調べる。

$ VBoxManage list hdds
    UUID:           1fee78f5-ae4a-4d74-8571-7ac918ff56cb
    Parent UUID:    base
    State:          created
    Type:           normal (base)
    Location:       ~/VirtualBox VMs/VmMac_13/VmMac01.vdi
    Storage format: VDI
    Capacity:       30720 MBytes
    Encryption:     disabled

対象とする、ストレージののUUIDがわかったら、対象のストレージのサイズを変更する

$ VBoxManage modifyhd 1fee78f5-ae4a-4d74-8571-7ac918ff56cb --resize 51200
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

こんな面倒な事をやらなくてもGUIで設定画面があった。

../../../_images/mediaMgr.png

スライダーで容量を調整できる。

diskutilでパーティションの変更

マシンを再起動して、F12を押しまくり、BIOSに入る。

そこで、リカバリ用の boot file を選択してbootする。

Boot Maitenance Manger ‣ Boot From File ‣ HD(3,GPT….) ‣ <com.apple.recovery.boot> ‣ boot.efi を選ぶ。

起動プロセスを継続して、しばらくすると、こんな画面になるので日本語を選ぶ。

../../../_images/selectLang.png

この画面で、ユーティリティ ‣ ターミナル を選ぶ。

../../../_images/selectUtility.png

これで、ターミナルのコマンドが使える。が、コピペが使えないので、手打ち。

まず、デバイスの確認。

$ diskutil list

多分、表示が多すぎのはずなので、絞り込む。

$ diskutil list /dev/disk0

ここで、出てくる。”Apple_HFS Machintosh HD”のサイズを変更したい。この行の末尾にある文字列”disk0s2”を識別子として使う。

最大容量の確認をする。

$ diskutil resizeVolume /dev/disk0s2 limits

最大容量が表示されるので、その値を使う。ここでは”52G”

$ diskutil resizeVolume /dev/disk0s2 52G

なぜか、エラーが発生した。”diskutil repairDisk”を実効せいとの事。 素直に従う。

$ diskutil repairDisk /dev/disk0

その後、再び、resizeVolumeを実行する。

$ diskutil resizeVolume /dev/disk0s2 52G

その後再起動をすると、VMの中のHDの容量が増えている。

]]>
Fri, 13 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/07/12/493a82fb_a657_43e1_bb4b_4e89907b6cfa.html https://www.mindto01s.com/2018/07/12/493a82fb_a657_43e1_bb4b_4e89907b6cfa.html <![CDATA[VirtualBoxでのmacOS10.13.5から10.13.6へのアップデート]]> VirtualBoxでのmacOS10.13.5から10.13.6へのアップデート

MacAppStoreからアップデートのお知らせが来たので、アップデート。

何も考えずに行うと、アップデートできない。

VirtualBoxがboot用のファイルの指定に失敗しているようだ。

macOS10.13のインストールの時のように手動で指定する必要がある。

macOS 10.13.6アップデータのダウンロード

ダウンロード後に、”再起動しない”を選ぶ。

スクリーンショットは撮り忘れた。

その後手動で、再起動する。

VMのBIOSか何かの設定を変える

起動直後に”F12”をおす。

失敗すると、以前のmacOSのバージョンが立ち上がり、アップデータのデータは削除される模様。 再び、アップデータのダウンロードから始まる。

こんな画面になる。

../../../_images/ud01.png

boot file を指定する。

boot fileは、”/macOS Install Data/Locked Files/Boot Files/boot.efi” にある

../../../_images/ud02.png

パーティションは、”2,GPT….”を選ぶ。”3,GPT…”はリストア用のboot fileが入っているので間違わないこと。

../../../_images/ud03.png

ディレクトリを1個づつ下ってゆく。

../../../_images/ud04.png ../../../_images/ud05.png ../../../_images/ud06.png ../../../_images/ud07.png

ブートプロセスが再開されたら、画面を眺めてゆけばOK。

失敗した場合

こんな画面が出た時は、アップデータのダウンロードからやり直し。

../../../_images/upErr.png
]]>
Thu, 12 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/07/06/144d63a0_2a1f_4c14_aca4_b9b466f863a8.html https://www.mindto01s.com/2018/07/06/144d63a0_2a1f_4c14_aca4_b9b466f863a8.html <![CDATA[VMへのmacOSのインストールの続き]]> VMへのmacOSのインストールの続き

無限ループ状態から脱出するために、BIOSの設定を変える。

VMのBIOSか何かの設定を変える

BIOSの設定画面を出すには、VMの起動直後に”F12”を押し続ける必要がある。

こんな画面になる。

../../../_images/BIOS01.png

“Boot Maintenance Manager”を選択する。

../../../_images/BIOS02.png

“Boot From File”を選択する。

../../../_images/BIOS03.png

“HD(2,GPT)…”を選択する。

../../../_images/BIOS04.png

“<macOS Install Data>”を選択する。

../../../_images/BIOS05.png

“<Locked Files>”を選択する。

../../../_images/BIOS06.png

“<Boot Files>”を選択する。

../../../_images/BIOS07.png

“boot.efi”を選択する。

../../../_images/BIOS08.png

すると、以下のようなログが流れる画面になる。

../../../_images/BIOS09.png

macOSの最初の設定

しばらくすると、以下のような画面でプログレス表示になる。

../../../_images/instMac01.png

ここら辺で、途中で光学デバイスを強制Ejectした気がする。が、関係あるかは不明。 以前成功した時は、光学デバイスの起動順序を下げていた。が、今回はEjectで行った。

../../../_images/instMac02.png

日本を選ぶ。

../../../_images/instMac03.png

各種アカウントの設定を行うと、以下の画面になりインストール完了。

../../../_images/instMac04.png
]]>
Fri, 06 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/07/06/390a00ca_4e06_4744_baeb_e8df90457ff2.html https://www.mindto01s.com/2018/07/06/390a00ca_4e06_4744_baeb_e8df90457ff2.html <![CDATA[VMの内蔵HDのフォーマットとmacOSのインストールの開始]]> VMの内蔵HDのフォーマットとmacOSのインストールの開始

VMの内蔵HDはmac用にフォーマットしないと使えない。

フォーマットしたのちに、インストールを行う。

ただし、macOSのインストールは、内蔵HDから起動できないので、途中から前に進まず最初の画面へ戻ってしまう。

VMの内蔵HDのフォーマット

VMを起動すると、コンソール画面にたくさん文字が表示される。

../../../_images/VMStart01.png

しばらく待つと、以下のような画面になる。

../../../_images/VMStart02.png

言語は日本語を選択する。

../../../_images/VMStart03.png

まずは、内蔵HDのフォーマットをするために、ディスクユーティリティーを立ち上げる。

../../../_images/VMStart04.png

“VBOX HARDDISK Media”を選択して、”消去”ボタンをおす

../../../_images/VMStart05.png

名前は適当に”Macintosh HD”でいいと思う。

名前 Macintosh HD
フォーマット Mac OS拡張(ジャーナリング)
方式 GUIDパーティションマップ
../../../_images/VMStart06.png

消去を実行するとすぐに終わって、以下のような画面になる。

../../../_images/VMStart07.png

macOSのインストール

macOSのインストールは、途中で小細工をしないと最初の画面からの無限ループにハマる。

最初の1週目を行い。小細工をする前まで書いてゆく。

ディスクユーティリティーの終了後にインストーラを立ち上げる。

../../../_images/VMStart08.png

普通のインストーラの画面で続けてゆく

../../../_images/VMStart09.png

続けてゆく。

../../../_images/VMStart10.png

ハードディスクを選んで続けてゆく

../../../_images/VMStart11.png

プログレスバーが続いてゆく。

../../../_images/VMStart12.png

プログレスバーの進行が終わると、VMが再起動され、こんなコンソール画面風になる。

../../../_images/VMStart13.png

再び、光学ディスクから起動される。

../../../_images/VMStart14.png

最初の画面に戻る。

../../../_images/VMStart15.png

プログレスバーの進行が終わった後に、内蔵ハードディスクから起動して欲しいのだが、起動されない。

小細工を追加すると内蔵ハードディスクで起動されるようになる。

]]>
Fri, 06 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/07/05/a3c6c7ce_bdbf_43c7_8bce_0cbe4e05ae76.html https://www.mindto01s.com/2018/07/05/a3c6c7ce_bdbf_43c7_8bce_0cbe4e05ae76.html <![CDATA[VirtualBox上のmacOS用VMの作成]]> VirtualBox上のmacOS用VMの作成

macOSをインストールするためのVMの作成。以下の設定で作成する。

VMの作成

新規ボタンを押せばOK。あとは、指示に従い、以下の設定を行えば問題ない。

../../../_images/newVM01.png
名前 任意
タイプ Mac OS X
バージョン mac OS 10.13 Hight Sierra (64-bit)
メモリーサイズ 4096
ハードディスク 仮想ハードデスクを作成する
../../../_images/newVM02.png
ファイルサイズ 30.00GB
ハードデスクのファイルタイプ VDI
物理ハードディスクにあるストレージ 可変サイズ

作成したVMの設定の調整

設定ボタンを押すと、設定ウインドウが表示される。

設定ウインドウの以下の値を設定してゆく

システム ‣ マザーボード ‣ 起動順序 ‣ フロッピー のチェックボックスを外す。

../../../_images/setVM01.png

システム ‣ プロセッサー ‣ プロセッサー数 の値を2に変更

システム ‣ プロセッサー ‣ 使用率制限 の値を80%に変更

../../../_images/setVM02.png

ディスプレイ ‣ スクリーン ‣ ビデオメモリー の値を80%に変更

../../../_images/setVM03.png

ストレージ ‣ ストレージデバイス ‣ 内蔵ディスクを選ぶ(名前はそれぞれ) ‣ 属性 ‣ SSD のチェックボックスを外す。

../../../_images/setVM04.png

ストレージ ‣ ストレージデバイス ‣ 光学デバイスを選ぶ(名前は空) ‣ 光学ドライブ の右側の光学ドライブボタンを押す。そこは、ポップアップメニューになっている。”HighSierra.iso”を選ぶ。

../../../_images/setVM05.png

オーディオ ‣ オーディオを有効化 のチェックボックスを外す。

../../../_images/setVM06.png
]]>
Thu, 05 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/07/05/befb9703_935b_43bd_9383_6fbdce038b48.html https://www.mindto01s.com/2018/07/05/befb9703_935b_43bd_9383_6fbdce038b48.html <![CDATA[VirtualBox用のmacOSインストールディスクを作る]]> VirtualBox用のmacOSインストールディスクを作る

元データの入手

macOS High Sierraは、Mac App Storeから入手できる。 ダウンロードは ここ

../../../_images/InstallerIcon.png

このアプリケーションの中に、インストールディスクを作るためのリソースが全て入っている。

.
└── Contents
    ├── Frameworks
    ├── Info.plist
    ├── MacOS
    ├── PkgInfo
    ├── PlugIns
    ├── Resources
    ├── SharedSupport
    │   ├── AppleDiagnostics.chunklist
    │   ├── AppleDiagnostics.dmg
    │   ├── BaseSystem.chunklist
    │   ├── BaseSystem.dmg
    │   ├── InstallESD.dmg
    │   └── InstallInfo.plist
    ├── _CodeSignature
    └── version.plist

isoデータの作成

ダウンロードしたインストーラから、以下のコマンドでDesktopにisoファイルを作れる。

$ hdiutil create -o /tmp/HighSierra -size 8G -layout SPUD -fs HFS+J -type SPARSE
created: /tmp/HighSierra.sparseimage
$ hdiutil attach /tmp/HighSierra.sparseimage -noverify -mountpoint /Volumes/install_build
/dev/disk9                  Apple_partition_scheme
/dev/disk9s1                Apple_partition_map
/dev/disk9s2                Apple_HFS                       /Volumes/install_build
$ sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/install_build
Password:
Ready to start.
To continue we need to erase the volume at /Volumes/install_build.
If you wish to continue type (Y) then press return: Y
Erasing Disk: 0%... 10%... 20%... 30%...100%...
Copying installer files to disk...
Copy complete.
Making disk bootable...
Copying boot files...
Copy complete.
Done.
$ hdiutil detach /Volumes/Install\ macOS\ High\ Sierra/
"disk9" unmounted.
"disk9" ejected.
$ hdiutil convert /tmp/HighSierra.sparseimage -format UDTO -o /tmp/HighSierra.iso
Driver Descriptor Map(DDM: 0)を読み込み中…
Apple(Apple_partition_map: 1)を読み込み中…
(Apple_Free: 2)を読み込み中…
disk image(Apple_HFS: 3)を読み込み中…
..........................................................................................................................................
経過時間: 17.273s
速度: 474.3Mバイト/秒
節約率: 0.0%
created: /tmp/HighSierra.iso.cdr
$ mv /tmp/HighSierra.iso.cdr ~/Desktop/HighSierra.iso
$ rm /tmp/HighSierra.sparseimage

デスクトップに以下のようなアイコンが出来る。ファイル名は、”HighSierra.iso”

../../../_images/isoIcon.png
]]>
Thu, 05 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/07/05/21595cce_195e_4c48_be03_65ce67a3f90f.html https://www.mindto01s.com/2018/07/05/21595cce_195e_4c48_be03_65ce67a3f90f.html <![CDATA[落穂拾い 2018/07/05]]> 落穂拾い 2018/07/05

Apple Developer Center

リリースノート

AppKitのリリースノートはLibraryの方に記載してほしい。

News - Apple Developer

]]>
Thu, 05 Jul 2018 00:00:00 +0900
https://www.mindto01s.com/2018/06/14/e44dcf5f_accc_49da_bcd5_780e45fdbe29.html https://www.mindto01s.com/2018/06/14/e44dcf5f_accc_49da_bcd5_780e45fdbe29.html <![CDATA[The Bash in Tokyo 発表資料]]> The Bash in Tokyo 発表資料

なぜか、渋谷で行う、BUKURO.swiftの発表資料です。

Cocoaの「逐次検索」を支援するクラスNSTextFinderについての発表をします。

以下のリンクから、資料をダウンロードしてください。

TableViewFinder2.zip

元々は関東swift勉強会2017-11の資料です。 NSTextFinderの使い方 サンプルコードをコンパイルエラーが出ないように修正してます。

]]>
Thu, 14 Jun 2018 00:00:00 +0900
https://www.mindto01s.com/2018/06/06/841aefc3_9c09_4b25_875e_6abdeab87ca9.html https://www.mindto01s.com/2018/06/06/841aefc3_9c09_4b25_875e_6abdeab87ca9.html <![CDATA[落穂拾い 2018/06/06]]> 落穂拾い 2018/06/06

Apple Developer Center

]]>
Wed, 06 Jun 2018 00:00:00 +0900
https://www.mindto01s.com/2018/05/23/badf4f3e_0d7e_4f1c_8ad9_213cb2e449b8.html https://www.mindto01s.com/2018/05/23/badf4f3e_0d7e_4f1c_8ad9_213cb2e449b8.html <![CDATA[落穂拾い 2018/05/23]]> 落穂拾い 2018/05/23 ]]> Wed, 23 May 2018 00:00:00 +0900 https://www.mindto01s.com/2018/05/11/75112797_7c33_4c91_b918_b0d71809c635.html https://www.mindto01s.com/2018/05/11/75112797_7c33_4c91_b918_b0d71809c635.html <![CDATA[SpotlightとQuickLookをSwiftで作る]]> SpotlightとQuickLookをSwiftで作る

関東swift勉強会2018-05の資料です。

]]>
Fri, 11 May 2018 00:00:00 +0900
https://www.mindto01s.com/2018/05/08/a4e439ca_3310_49e9_ae37_887c1066a827.html https://www.mindto01s.com/2018/05/08/a4e439ca_3310_49e9_ae37_887c1066a827.html <![CDATA[落穂拾い 2018/05/08]]> 落穂拾い 2018/05/08 ]]> Tue, 08 May 2018 00:00:00 +0900 https://www.mindto01s.com/2018/04/25/27c73892_1047_4812_868b_7ac6c7af7f8f.html https://www.mindto01s.com/2018/04/25/27c73892_1047_4812_868b_7ac6c7af7f8f.html <![CDATA[落穂拾い 2018/04/25]]> 落穂拾い 2018/04/25

News - Apple Developer

]]>
Wed, 25 Apr 2018 00:00:00 +0900
https://www.mindto01s.com/2018/04/11/55d89f99_0abe_4765_a856_091c2a3a0431.html https://www.mindto01s.com/2018/04/11/55d89f99_0abe_4765_a856_091c2a3a0431.html <![CDATA[落穂拾い 2018/04/11]]> 落穂拾い 2018/04/11 ]]> Wed, 11 Apr 2018 00:00:00 +0900 https://www.mindto01s.com/2018/04/07/cc4887fa_79b2_416e_9e15_5ce05dacd074.html https://www.mindto01s.com/2018/04/07/cc4887fa_79b2_416e_9e15_5ce05dacd074.html <![CDATA[tinker運用ノート(4)]]> tinker運用ノート(4)

macPortsのupdateでtinkerが動かなくなった。

原因は、以下の2つ。

  • macPortsで入れるsphinxのバージョンが1.7x系になっていた。
  • 一方、tinkerは、sphinxのバージョンは1.6系で動く。

今までは、macPortsでupdateの時に気をつけて、sphinxのアップデートをしないようにしていた。 しかし、先日、誤ってupdateした上に、古いバージョンを消去してしまった。

この機会にと思い、Pythonのパッケージシステムの仮想環境を試してみた。

環境の構築

Tinkerの1.7のを使うと、Sphinxのバージョンを下げて仮想環境にインストールしてくれる。

$ # 適当なディレクトリに仮想のパッケージシステム環境を作る
$ python3 -m venv ./tinkerer_env
$ source ./tinkerer_env/bin/activate
$ # Tinker 1.7をインストール
$ pip install git+https://github.com/vladris/tinkerer.git@master --
$ # バージョンの確認
$ tinker --version
Tinkerer version 1.7.0
$ # さらに各種extensionもインストール
$ pip install sphinxcontrib-actdiag sphinxcontrib-blockdiag sphinxcontrib-nwdiag sphinxcontrib-seqdiag sphinxcontrib-tikz

ターミナルで設定済みの仮想のパッケージシステム環境を使うには

$ # 仮想環境のactivate
$ source ./tinkerer_env/bin/activate
$ # 確認( Tinkerer version 1.7.0 )と出てくるはず
$ tinker --version
Tinkerer version 1.7.0

あとは普段の通りに、Tinkererを使う。

]]>
Sat, 07 Apr 2018 00:00:00 +0900
https://www.mindto01s.com/2018/04/04/77f9f41b_fbab_4838_ac32_35402e63a94c.html https://www.mindto01s.com/2018/04/04/77f9f41b_fbab_4838_ac32_35402e63a94c.html <![CDATA[矩形の移動]]> 矩形の移動

関東swift勉強会2018-04の資料です。CGRectのextensionを作る。

]]>
Wed, 04 Apr 2018 00:00:00 +0900
https://www.mindto01s.com/2018/03/19/723deecf_2923_4bbd_a03f_1afb5bbb8458.html https://www.mindto01s.com/2018/03/19/723deecf_2923_4bbd_a03f_1afb5bbb8458.html <![CDATA[落穂拾い 2018/03/19]]> 落穂拾い 2018/03/19

日本語ドキュメントのRSSがまだ取れない。”https://developer.apple.com/jp/rss/adcjapan.rss

諦めるか。

Apple Developer Center

ガイド

]]>
Mon, 19 Mar 2018 00:00:00 +0900
https://www.mindto01s.com/2018/02/15/f526f84c_4b4a_4229_aa37_d834525a6461.html https://www.mindto01s.com/2018/02/15/f526f84c_4b4a_4229_aa37_d834525a6461.html <![CDATA[NSDcoument複数ファイルフォーマットその2]]> NSDcoument複数ファイルフォーマットその2

関東swift勉強会2018-02の資料です。今回もNSDcoumentで複数のファイルフォーマットをサポートするコード周りの解説です。

感想

今回も、多分、誰の役にも立たない。

https://cocoa-kanto.connpass.com/event/75430/

NSDocument-fileFormat2.zip

]]>
Thu, 15 Feb 2018 00:00:00 +0900
https://www.mindto01s.com/2018/01/14/a564c400_e24a_42ca_984e_15f35c280b4c.html https://www.mindto01s.com/2018/01/14/a564c400_e24a_42ca_984e_15f35c280b4c.html <![CDATA[落穂拾い 2018/01/14]]> 落穂拾い 2018/01/14

日本語ドキュメントのRSSが取れなくなった。”https://developer.apple.com/jp/rss/adcjapan.rss

Apple Developer Center

]]>
Sun, 14 Jan 2018 00:00:00 +0900
https://www.mindto01s.com/2017/12/31/51425e25_e90b_4073_b8e3_e9c083cbaf47.html https://www.mindto01s.com/2017/12/31/51425e25_e90b_4073_b8e3_e9c083cbaf47.html <![CDATA[NSDcoument複数ファイルフォーマット]]> NSDcoument複数ファイルフォーマット

関東swift勉強会2018-01の資料です。NSDcoumentで複数のファイルフォーマットをサポートするコード周りの解説です。

感想

今回も、多分、誰の役にも立たない。

https://cocoa-kanto.connpass.com/event/75006/

NSDocument-fileFormat.zip

]]>
Sun, 31 Dec 2017 00:00:00 +0900
https://www.mindto01s.com/2017/11/19/68f86f53_1435_4172_80eb_378c1f847475.html https://www.mindto01s.com/2017/11/19/68f86f53_1435_4172_80eb_378c1f847475.html <![CDATA[NSTextFinderの使い方]]> NSTextFinderの使い方

以下の2つの資料を纏めて、サンプルコードを実装した。

Appleのリファレンス

NSTextFinder Magic

サンプルコードと感想

今回も、多分、誰の役にも立たない。

関東swift勉強会2017-11の資料です。 https://cocoa-kanto.connpass.com/event/70765/

TableViewFinder.zip

]]>
Sun, 19 Nov 2017 00:00:00 +0900
https://www.mindto01s.com/2017/11/11/6f4b7543_fcce_411f_aa66_ca29667c9912.html https://www.mindto01s.com/2017/11/11/6f4b7543_fcce_411f_aa66_ca29667c9912.html <![CDATA[落穂拾い 2017/11/11]]> 落穂拾い 2017/11/11

Apple Developer Center

]]>
Sat, 11 Nov 2017 00:00:00 +0900
https://www.mindto01s.com/2017/11/01/eca210cc_92d1_43ca_87ae_4f1052cc31f7.html https://www.mindto01s.com/2017/11/01/eca210cc_92d1_43ca_87ae_4f1052cc31f7.html <![CDATA[Cocoaからのライブ変換(LiveConversion)の変更と監視その2]]> Cocoaからのライブ変換(LiveConversion)の変更と監視その2

進捗があった。非公開プロトコルを適応することで、ライブ変換を制御できる。

変更と監視は出来ない。TextFieldに入力中にライブ変換を一時的に無効にすることができるだけ。

プロトコルNSTextInputClient_IncrementalSearch

NTextViewのサブクラスで、NSTextInputClient_IncrementalSearchに適合しているクラスではライブ変換は無効になる。

ヘッダは以下のようになっている。

@class NSEvent;

@protocol NSTextInputClient_IncrementalSearch
- (BOOL)wouldHandleEvent:(NSEvent *)arg1;
- (unsigned long long)incrementalSearchClientGeometry;
@end

メソッドそれぞれの詳細は不明だが、wouldHandleEventではfalseを返し、incrementalSearchClientGeometryでは0を返すことで動作した。

incrementalSearchClientGeometryで0以外の場合は、変換候補のWindowが表示されなかった。

field editor

NSWindow上にNSTextFieldを複数配置した場合を考えます。この時、Textの編集が出来るNSTextFieldは常に、その内のどれか一つです。その他のNSTextFieldは、表示されているだけです。

この特徴を特性を利用して、Cocoaフレームワークでは、NSTextFieldは表示だけを行い、編集するときは同じ場所で別のオブジェクト(NSTextView)に任せるという方式を採用しています。

この別のオブジェクトは、NSWindow上のNSTextFieldで共有されており、それをField Editorと呼びます。

昔々のNextStepの時代では、CPUのパワーは今よりも貴重でした。Cocoaフレームワークには、その時代の設計の名残があります。field editorもその名残です。

このことは、先ほどのライブ変換を制御と関係があります。ライブ変換を制御するには、NSTextFieldのサブクラスだけではなくて、NSTextInputClient_IncrementalSearchに適応したNSTextViewのサブクラスを作る必要があるのです。

さらには、上記の text field を挿げ替えて、特定のTextFieldの時だけ、こちらが指定した custom field editor を使用するように指示する必要があります。

この仕組みは、以下のようになります。

class WindowController: NSWindowController, NSWindowDelegate
{
        .
        .
        .
    @objc dynamic var custumFieldEditor: MINTLIncrementalSearchFieldEditor? = nil

    public func windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any?
    {
        // カスタムのfieldEditorを使う
        if client is MINTLIncrementalSearchField?
        {
            if self.custumFieldEditor == nil
            {
                self.custumFieldEditor = MINTLIncrementalSearchFieldEditor()
                self.custumFieldEditor!.isFieldEditor = true
            }

            return self.custumFieldEditor
        }

        // デフォルトのfieldEditorを使う
        return nil
    }
        .
        .
        .
}

サンプルコードと感想

多分、誰の役にも立たない。

BUKURO.swift 2017-11の資料です。 https://cocoa-kanto.connpass.com/event/68404/

IncrementalSearchField.zip

]]>
Wed, 01 Nov 2017 00:00:00 +0900
https://www.mindto01s.com/2017/10/20/b23b9d83_3b5c_47c1_b162_b9f34e762b4f.html https://www.mindto01s.com/2017/10/20/b23b9d83_3b5c_47c1_b162_b9f34e762b4f.html <![CDATA[Outliner 巻き上げUI]]> Outliner 巻き上げUI

関東swift勉強会2017-10の資料です。

巻き上げUIの実装

]]>
Fri, 20 Oct 2017 00:00:00 +0900
https://www.mindto01s.com/2017/09/24/d8b0858a_7358_4e5c_8a97_5db4b7005755.html https://www.mindto01s.com/2017/09/24/d8b0858a_7358_4e5c_8a97_5db4b7005755.html <![CDATA[落穂拾い 2017/09/24]]> 落穂拾い 2017/09/24

Apple Developer Center

ガイド

]]>
Sun, 24 Sep 2017 00:00:00 +0900
https://www.mindto01s.com/2017/09/19/a90f41a8_f969_4f9e_a5b7_e0f4e7ff3e8b.html https://www.mindto01s.com/2017/09/19/a90f41a8_f969_4f9e_a5b7_e0f4e7ff3e8b.html <![CDATA[9月23日の関東Swift勉強会の資料]]> 9月23日の関東Swift勉強会の資料

9月23日の関東Swift勉強会の資料。「メニューのショートカット表記の変更」。

資料色々

]]>
Tue, 19 Sep 2017 00:00:00 +0900
https://www.mindto01s.com/2017/09/03/40de0ac1_238b_4b3a_abc8_9a7f48a5b644.html https://www.mindto01s.com/2017/09/03/40de0ac1_238b_4b3a_abc8_9a7f48a5b644.html <![CDATA[9月4日のcocoa:Bukuro.swiftの資料]]> 9月4日のcocoa:Bukuro.swiftの資料

Objective-Cで書いていたライブラリをSwiftへ移植中に得られた知見など色々。

資料色々

]]>
Sun, 03 Sep 2017 00:00:00 +0900
https://www.mindto01s.com/2017/08/24/37d22c3e_092c_4a54_969c_3e852753060d.html https://www.mindto01s.com/2017/08/24/37d22c3e_092c_4a54_969c_3e852753060d.html <![CDATA[落穂拾い 2017/08/24]]> 落穂拾い 2017/08/24 ]]> Thu, 24 Aug 2017 00:00:00 +0900 https://www.mindto01s.com/2017/08/01/20751ba1_84d8_4d36_a89f_daeffb912803.html https://www.mindto01s.com/2017/08/01/20751ba1_84d8_4d36_a89f_daeffb912803.html <![CDATA[落穂拾い 2017/08/01]]> 落穂拾い 2017/08/01 ]]> Tue, 01 Aug 2017 00:00:00 +0900 https://www.mindto01s.com/2017/07/18/3a006274_9f3f_47ea_abbe_c20b4371d132.html https://www.mindto01s.com/2017/07/18/3a006274_9f3f_47ea_abbe_c20b4371d132.html <![CDATA[PlaygroundでStoryboardの試作]]> PlaygroundでStoryboardの試作

Swiftでロジックを組み立てるのにPlaygroundは便利だが、UIに関してはいまいち使いにくかった。

色々と調べたら、UIに関しても、Playgroundでプレビューするする方法が分かった。

要約すると以下のようになる。

  1. playground側でフレームワークを “import” すると、そのフレームワークが使える
  2. テストしたいクラスやリソースを含めたフレームワークを作る
  3. アプリケーションのソースコード中でplaygorundeで実行したいものは片っ端から、上記のフレームワークに入れる。
  4. フレームワークをビルドしてから、playgroudで使えるようになる
  5. 上記の、アプリのプリジェクト、フレームワークのプロジェクト、プレイグラウンドをまとめるために、ワークスペースを作り、上記の3つをその中に入れる。
  6. バンドルの指定が、メインバンドルでないことを気をつければふつーに使える。
../../../_images/PGWS01.png

準備

Playgroundとアプリケーションプロジェクトではお互いにソースファイルの共有がうまくゆかない。

シンボリックリンクやエリアスなども試したが、Xcodeの誤作動が多かった。

その上、Playgroundのリソースフォルダの中ではxibのコンパイルも自動ではやってくれない。

結局、以下のように間にフレームワークプロジェクトを挟む方法が最も簡単だった。

  1. ワークスペースの作成

file > New… > Workspace… で、ワークスペースの作成。

../../../_images/PGWS02.png
  1. アプリケーションプロジェクトの作成

先ほどのワークスペースのWindowの左下の”+”マークから、”New Project…”を選んで、アプリケーションプロジェクトの作成と追加。

この時、プロジェクトの種類はCocoaアプリケーションを選ぶ

../../../_images/PGWS03.png
  1. フレームワークプロジェクトの作成

同じくワークスペースのWindowの左下の”+”マークから、”New Project…”を選んで、フレームワークプロジェクトの作成と追加。

この時、プロジェクトの種類はCocoaフレームワークを選ぶ

../../../_images/PGWS04.png

playgroundでフレームワークをimport出来るように、ここでフレームワークをビルドしておく。

フレームワークの中のソースを変更する毎にビルドしてから、playgroundの実行を行う事。

  1. プレイグラウンドの作成

ワークスペースのWindowの左下の”+”マークから、今度は”New file…”を選んで、Playgroundファイルの作成と追加。

この時、ファイルの種類はPlaygroundを選ぶ。ここで気がついたが、Playgroundはファイルであり、プロジェクトではないために、ファイルの共有が出来ないのかもしれない。

../../../_images/PGWS05.png
  1. playgroundからフレームワークをimportする

フレームワークの名称がPGWS2FMなので、”import PGWS2FM”を追加する。

PlaygroundSupportはplaygroundでNSViewやNSViewControllerを使用するためのユーティリティフレームワーク。

@testableをつけてimportすると、同じモジュールにあるように扱えます。

ここでの目的は、アプリケーションプロジェクトの支援なので、@testableをつけてます。

//: Playground - noun: a place where people can play

import Cocoa
import PlaygroundSupport
@testable import PGWS2FM

var str = "Hello, playground"

そのほかに、playgroundの自動実行の停止や、ショートカットの設定を行うと便利。

使用例1:カスタムViewを作成し、プレイグラウンドで試す

適当なNSViewのサブクラスPGWS2View.swiftをアプリケーションプロジェクトに作る。

import Cocoa

class PGWS2View: NSView
{
    // 背景色
    var backgroundColor: NSColor = NSColor.blue

    override func draw(_ dirtyRect: NSRect)
    {
        super.draw(dirtyRect)

        // Drawing code here.
        backgroundColor.set()
        NSBezierPath.fill(self.bounds)
    }
}

作成後、フレームワークプロジェクトにDrag&Dropして、フレークワークをビルドする。

../../../_images/PGWS06.png

これで、PlaygroundでPGWS2Viewが使えるようになる。以下のソースをPlaygroundに打ち込むとTimeLineに青い四角が現れるはず。

//: Playground - noun: a place where people can play

import Cocoa
import PlaygroundSupport
@testable import PGWS2FM

var str = "Hello, playground"

var theView: PGWS2View
let theRect = NSMakeRect(0,0,100,100)

theView = PGWS2View(frame:theRect)

//theView.backgroundColor = NSColor.black

PlaygroundPage.current.liveView = theView

PlayGroundを実行させると以下のような画面になる。

../../../_images/PGWS07.png

エラーが起きた場合は最初に、以下の3点を確認する事。よく忘れる。

  1. フレームワークのビルドを忘れていないか?
  2. フレームワークにファイルをコピーし忘れていないか?
  3. palygroundをマニュアル起動にしたのに、実行ボタンを押し忘れていないか?

なお、TimeLine表示にするには、Window左上の丸が2つあるマークを押す必要がある。

私は間抜けなのでこれに気がつくのに時間がかかった。

うまく動いたら、コメントアウトされた行を有効にして、黒い四角を表示させてみてください。カスタムViewの作成が捗るような気がしてくるはずです。

使用例2:アプリケーションプロジェクト内のViewControllerをプレイグラウンドで試す

次は、ViewControllerです。storyboadrdから再生したViewControllerを表示させます。

../../../_images/PGWS11.png

ソースコードは、動作確認のための、アクションを2つ。ログを出力後に、ViewControllerを閉じるだけの簡単なコードです。

import Cocoa

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    @IBAction          func okOperation(_ sender: Any?)
    {
        print("OK_BUTTON")
        self.dismissViewController(self)
    }

    @IBAction override func cancelOperation(_ sender: Any?)
    {
        print("Cancel_BUTTON")
        self.dismissViewController(self)
    }

}

OKボタンとCancelボタンを配置して、actionの設定をします。

../../../_images/PGWS08.png

SotorybordIDを設定します。ここでは、クラス名と同じく、ViewControllerにしてます。

../../../_images/PGWS09.png

最後に、アプリケーションプロジェクトからフレームワークプロジェクトにファイルをDrg&Dropします。ファイルの参照がフレームワークプロジェクトに追加されるようにするためです。

../../../_images/PGWS10.png

ここで、フレームワークのビルドを忘れずに。

これで、PlaygroundでViewControllerが使えるようになる。以下のソースをPlaygroundに打ち込むとTimeLineにボタンが2つ見えるはず

//: Playground - noun: a place where people can play

import Cocoa
import PlaygroundSupport
@testable import PGWS2FM

let theBundle = Bundle(identifier:"com.mindto01s.PGWS2FM")

let theStoryboard = NSStoryboard(name: "Main",
                                     bundle:theBundle)

var vc:ViewController

vc = theStoryboard.instantiateController(withIdentifier: "ViewController") as! ViewController

PlaygroundPage.current.liveView = vc

実行させると、以下のようにボタンが2つ表示される。

../../../_images/PGWS11.png

ここで、OKボタンを押すとログに”OK_BUTTON”と表示され、アクションが実行されていることが確認できます。

ここでのコツは、

  1. Storyboradをフレームワークに登録することを忘れない
  2. フレームワークに入っているリソースはメインバンドル内では無い事を忘れない。

この2点さえ覚えておけば、普通に使えてます。

参考文献とソースコード

プロジェクトファイル

]]>
Tue, 18 Jul 2017 00:00:00 +0900
https://www.mindto01s.com/2017/07/14/b6344745_bc42_4257_84b1_5372590b4e90.html https://www.mindto01s.com/2017/07/14/b6344745_bc42_4257_84b1_5372590b4e90.html <![CDATA[落穂拾い 2017/07/14]]> 落穂拾い 2017/07/14 ]]> Fri, 14 Jul 2017 00:00:00 +0900 https://www.mindto01s.com/2017/07/13/76638d62_bf9c_4387_9e5f_e8a1b7a46749.html https://www.mindto01s.com/2017/07/13/76638d62_bf9c_4387_9e5f_e8a1b7a46749.html <![CDATA[swiftで迷路の脱出]]> swiftで迷路の脱出

swift playgrouds での8番目のプログラミング。今回は迷路の脱出。

../../../_images/mazeMov2.gif

こんな迷路を作るアプリケーションを作った。gifアニメの後半で迷路からの脱出経路を描画している。

青が壁、黒は通路、赤はスタートからゴールまでの順路、黄色は行き止まり。

ゴールまで達すれば、探索は終了するので、見探索の通路が残ります。

使い方

GUIからの使い方は、以下の通り。

  1. アプリの起動
  2. コマンド+N を押すか file -> New を選ぶと、迷路作成の開始
  3. 迷路が完成すると、すぐに迷路からの脱出経路の探索を開始する

(4) スタートは左下、ゴールは右上 (4) スライダーを左右に動かすと、迷路の作成の途中結果を確認できる。

Playgroundからも実行できる。今回も本当はこちらがメイン。

import Cocoa

let maze = MazeBoard(width:20, height:20) // 20 * 20の大きさの迷路を作る
maze.doDig(MazePos(1,1))                  // (1,1)の地点から迷路を彫り始める
let resolver = MazeResolver(maze)         // 迷路の脱出機を作る
resolver.resolve(MazePos(1,1))            // (1,1)の地点(左上)から開始
resolver.DebugPrint()                     // 結果の出力

このように書くと、以下のような出力が得られる。

# # # # # # # # # # # # # # # # # # # #
# S . * * x x x x x x x x x * . . . * #
# * . * x x * * * * * * * x . . * . * #
# * . * * * * . . . . . . * . * * . . #
# * . . * . . . * * * * . * . . . * . #
# * * . * . * * *   * * . . * * . * . #
# x * . * . . *         * . * . . * . #
# . . . * * . *   * *   * . . . * * . #
# . * * * . . *   * *   * * * * * * . #
# . * * . . *     *     * * * . . . . #
# . * * . * *   * *   * *     . * * * #
# . . * . * * * * *         * . . . . #
# * . * . * . . . . * * * * * * * * . #
# * . * . . . * * . . . . . . * * * . #
# * . . * x * * * * * x * * . * . . . #
# * * . * * * . . . * x * * . * . * * #
# x * . * * * . * . * x * * . * . * * #
# x x . . . * . * . * * * * . * . .   #
# * x * * . . . * . . . . . . * * . . #
# # # # # # # # # # # # # # # # # # # #

Sがスタート地点で、G(左下)がゴール地点です。(バグでゴールを.で塗りつぶしてしまってるけど) Xは行き止まりの通路、.が順路です。何も書かれていない場所は、探索していない通路です。

resolver.resolve(MazePos(1,1))                  // (1,1)の地点から迷路を彫り始める

この部分を

resolver.resolve(MazePos(1,1), { $0.DebugPrint() } )

このように変えると、作成途中の迷路が見れる。

ソースコードの解説

面倒なので、

「関東swift勉強会2017-7」 https://cocoa-kanto.connpass.com/event/61475/

で、解説します。

参考文献とソースコード

プロジェクトファイル

  • Swift Macアプリ開発入門
    https://www.amazon.co.jp/Swift-Macアプリ開発入門―次世代iOS、macOSプログラマーのための-中山-茂/dp/487783396X これを読んで、macOSのアプリをサクサク作れるようになるとは思わないが、サンプルコードの選定が私の好みに合ってる。業務用のアプリケーションの雛型でなくて、学習用アプリケーションの雛型なのが良い感じ。
]]>
Thu, 13 Jul 2017 00:00:00 +0900
https://www.mindto01s.com/2017/07/10/de507bc4_045a_4008_9e3c_2b2fa7a81fcc.html https://www.mindto01s.com/2017/07/10/de507bc4_045a_4008_9e3c_2b2fa7a81fcc.html <![CDATA[swiftで迷路の作成]]> swiftで迷路の作成

swift playgrouds での7番目のプログラミング。今回は迷路の作成。そして、Playgroundsからの脱皮してアプリケーションを作った。

../../../_images/mazeMov.gif

こんな迷路を作るアプリケーションを作った。目的は、迷路を解くプログラムを作るための準備。

使い方

GUIからの使い方は、以下の通り。

  1. アプリの起動
  2. コマンド+N を押すか file -> New を選ぶと、迷路作成の開始
  3. 迷路が完成すると、ウインドウの下部のスライダーがenableになる。
  4. スライダーを左右に動かすと、迷路の作成の途中結果を確認できる。

Playgroundからも実行できる。と、いうかこちらがメイン。

import Cocoa

let maze = MazeBoard(width:20, height:20) // 20 * 20の大きさの迷路を作る
maze.doDig(MazePos(1,1))                  // (1,1)の地点から迷路を彫り始める
maze.DebugPrint()                         // 迷路を出力

このように書くと、以下のような出力が得られる。

# # # # # # # # # # # # # # # # # # # #
# S * *         * *   * * *   * * * * #
#     *   * *   * *       *           #
# *       * *         *     *   * *   #
# * * * *   * * * * * * *   *     * * #
#           *           *     *     * #
#     *   * *   * * *   * *   * *     #
#   * *   * *     * *   * *   * * *   #
#   * *       *   *       *           #
#   * * * *       *   *     * * * *   #
#           * * * *     *     *       #
# * * * *     * * * *     *   *   * * #
#       * *         * *   * * *   * * #
#   *       * * *   * *     * *       #
#   * * *       *     * *     * *   * #
#     * * * *   * *   * * *         * #
# *   *   * *         *   * * * * * * #
# *   *       * * * *       * * *     #
# *       *             *           G #
# # # # # # # # # # # # # # # # # # # #

Sがスタート地点で、Gがゴール地点です。

maze.doDig(MazePos(1,1))                  // (1,1)の地点から迷路を彫り始める

この部分を

maze.doDig(MazePos(1,1), { $0.DebugPrint() } )

このように変えると、作成途中の迷路が見れる。

ソースコードの解説

面倒なので、

「関東swift勉強会2017-7」 https://cocoa-kanto.connpass.com/event/61475/

で、解説します。

参考文献とソースコード

プロジェクトファイル

  • コンピュータアルゴリズム事典
    https://www.amazon.co.jp/Software-Technology-12-コンピュータアルゴリズム事典-奥村/dp/4874089135 Pascalで書かれてます。当初これに載っていた、「壁を伸ばす」アリゴリズムで書いていたが、「穴を掘る」アルゴリズムに変更した。しかし、進めなくなったら、分岐箇所からもう一度伸ばす考え方は同じはず。
  • Swift Macアプリ開発入門
    https://www.amazon.co.jp/Swift-Macアプリ開発入門―次世代iOS、macOSプログラマーのための-中山-茂/dp/487783396X これを読んで、macOSのアプリをサクサク作れるようになるとは思わないが、サンプルコードの選定が私の好みに合ってる。業務用のアプリケーションの雛型でなくて、学習用アプリケーションの雛型なのが良い感じ。
]]>
Mon, 10 Jul 2017 00:00:00 +0900
https://www.mindto01s.com/2017/07/04/4a4a46b0_13c6_46b9_b73f_565eb5f49f68.html https://www.mindto01s.com/2017/07/04/4a4a46b0_13c6_46b9_b73f_565eb5f49f68.html <![CDATA[落穂拾い 2017/07/04]]> 落穂拾い 2017/07/04 ]]> Tue, 04 Jul 2017 00:00:00 +0900 https://www.mindto01s.com/2017/07/03/0e4b1743_cc3a_4e92_9c66_00f523e5250b.html https://www.mindto01s.com/2017/07/03/0e4b1743_cc3a_4e92_9c66_00f523e5250b.html <![CDATA[ソースコード用にcssを書いた]]> ソースコード用にcssを書いた

今まで、sphinxで書いたソースコードがソフト改行されていたために、読みづらかった。

ソフト改行されるのは、所定の矩形に入れるためなので、仕方がない事だと思っていた。

ググるとcssをうまく設定すると改行せずに横スクロールさせる設定があるとの事。

以下のコードを使っているcssファイルに追加するだけ。

/* ソースコードは改行しないで、スクロールさせる */

div.highlight pre {
    white-space: pre;
    word-wrap: normal
}

div.highlight {
    overflow:auto;
}

当然、Safariでしか確認していない。

]]>
Mon, 03 Jul 2017 00:00:00 +0900
https://www.mindto01s.com/2017/07/02/4a803da2_4433_4cff_9ecb_eeead050d71d.html https://www.mindto01s.com/2017/07/02/4a803da2_4433_4cff_9ecb_eeead050d71d.html <![CDATA[swiftでアナグラム]]> swiftでアナグラム

swift playgrouds での6番目のプログラミング。今回はアナグラムの生成。

アナグラムとは、単語の文字をいくつか入れ替えることで、別の単語を作り上げて、別の意味を持たせる遊びです。

ノストラダムスがナンダッテー ΩΩΩ に出てくる文字を入れ替えて別の単語にするアレです。

“/usr/share/dict/words” に英単語集があります。この単語集とコレクションクラスの各種メソッドを使って、与えられた英単語からアナグラムになる英単語を出力します。

入力例

let theAnagram = MTFAnagrams()
print(theAnagram.anagrams("thinker")) // thinker(思想家) -> rethink(再考)
print(theAnagram.anagrams("canoe"))   // canoe(カヌー) -> ocean(海)
print(theAnagram.anagrams("orange")) // orange(オレンジ) -> onager(投石機)
print(theAnagram.anagrams("valencia")) // valencia(スペインの地名?) -> valiance(たれ布?カーテン?)

出力例

["rethink"]
["acone", "ocean"]
["onager"]
["valiance"]

ソースコードを分けた

ソースコードを分けて、別ファイルに入れると実行スピードが上がる。 おそらく、別ファイルにすることでトレース機能が及ばなくなるのだと思われる。

../../../_images/xcodeSwiftFile.png

アナグラムを探し出すクラスは以下のようになってます。ファイルanagrams.swiftに書いている。

import Foundation

public struct MTFAnagrams
{
    let wordSet: Set<String>

    public init()
    {
        //   - テキストファイルから読み込み、単語の集合を作る
        let wordArray:[String] =
            try! String.init(contentsOfFile:"/usr/share/dict/words")    // ファイルから読み込み
                .components(separatedBy:"\n")                               // 改行で分割し

        self.wordSet   = Set<String>(wordArray) // 単語の存在確認のために、集合にしておく。
    }

    public func anagrams(_ word:String) -> Set<String>
    {
        // 1単語毎に単語を入れ替えた仮のアナグラムを生成。
        let anagrams = Set(
            word.characters // 文字毎に分解
                .flatMap{ String($0) }        // 型を文字列に変換
                .permutations()               // 文字順を入れ替えた仮のアナグラムの配列を生成
                .map{ $0.joined() }           // 1文字毎だったものを連結
                .flatMap                      // 仮のアナグラムが単語集wordSetにある場合は、アナグラムである。元の文字列と同じものは除く
                {
                    (anagram:String) -> String? in

                    word != anagram && self.wordSet.contains(anagram) ? anagram : nil
                }
        )

        return anagrams
    }
}

swiftで特徴的なのは、クラスやメソッドの可視スコープです。 今まで、プレイグラウンドで1つのファイルでやっていたので気がつかなかったが、ファイルや、フレームワークがスコープの範囲になっています。

ファイルを分けたことで、別ファイルから利用するには、 “public” キーワードを付けました。

アルゴリズムに関してはコメントを参考にしてください。

また、アナグラムを作るのに、arrayにメソッド “permutations” を追加しました。ファイル “ArrayExtension.swift” にエクステンションを書いています。

import Foundation

extension Array
{
    // 各要素の順列違いを全て生成する。n!個のパターンを作る。nが8を越えると猛烈に遅くなる。
    // 注意:nが大きくなると爆発的に巨大な数になるのでカードゲームのシャッフルなどには使わないこと
    // https://stackoverflow.com/questions/34968470/calculate-all-permutations-of-a-string-in-swift
    public func permutations() -> [[Element]]
    {
        var scratch = Array(self) // This is a scratch space for Heap's algorithm
        var result: [[Element]] = [] // This will accumulate our result

        // Heap's algorithm
        func heap(_ n: Int)
        {
            if n <= 1
            {
                result.append(scratch)

                return
            }

            for i in 0..<n-1
            {
                heap(n-1)
                let j = (n%2 == 1) ? 0 : i
                swap(&scratch[j], &scratch[n-1])
            }

            heap(n-1)
        }

        // Let's get started
        heap(scratch.count)

        // And return the result we built up
        return result
    }
}

これは、可能な組合せの配列を返すメソッドです。コードは stackOverFlow から引用したものをコンパイルが通るように修正を加えただけ。

参考文献のサンプルコードからの移植の感想

元のコードはC++のSTLを使ったサンプルコードです。以下C++やObjective-Cとの違いの感想です。

  • C++よりもエラーメッセージがわかりやすい
  • クロージャーのおかげで、関数オブジェクトの事を考えなくて良く楽
  • unixのフィルターを使った1行プログラミングに近い感じがする
  • forよりもmapを使うと見た目がスッキリする事が多い
  • 今回は使用しなかったが、forとカウンタ変数を使うよりも数列 ( 1…5 ) の表記とmapを組み合わせた方がキレイ。
  • reduceよりも使えるならばjoinedを使うと見た目がキレイ。

後の課題は、オプショナルとアンラップがまだよくわかっていない。

参考文献とソースコード

プロジェクトファイル

  •  STL標準テンプレートライブラリによるC++プログラミング
    https://www.amazon.co.jp/STL―標準テンプレートライブラリによるC-プログラミング-Higher-education-computer/dp/4795296987
]]>
Sun, 02 Jul 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/30/a6c64bb2_e104_44f6_9cd7_87dbb51d512b.html https://www.mindto01s.com/2017/06/30/a6c64bb2_e104_44f6_9cd7_87dbb51d512b.html <![CDATA[Swiftで8人の女王その2]]> Swiftで8人の女王その2

reduceの代わりにjoinを使うとメソッドdebugPrintが簡潔にかけるようになった。 文字列の結合や配列の結合くらいでは、joinの方が見た目が綺麗。

// debug用出力
func debugPrint()
{
    // 数列に対するmap&joinedを行い、デバッグ文字列を作成する
    print(
        ( 0..<queens.count ).map
            {
                i -> String in // ここで引数に名前をつけないと内側のmapのクロージャでアクセスできない。

                (0..<sizeN).map{ $0 == queens[i] ? " q" : " ." }.joined() + "\n"

            }.joined()
    )
}

メソッドresolveの方はreduceの代わりにflatMapを二重にかけた方が簡潔に見える。

func resolve() -> [EQBoard]
{
    precondition(queens.count <= self.sizeN)

    func resolve_intarnal( _ count:Int, _ boards:[EQBoard]) -> [EQBoard]
    {
        return count == 0 ? boards :
            resolve_intarnal(
                                count - 1,
                                boards.flatMap
                                {
                                    i -> [EQBoard] in // この記述がないと曖昧(ambiguous)でわからぬとエラーが出る
                                    (0..<i.sizeN).flatMap
                                    {
                                        i.tryAddAQueen($0) ? i.boardAddAQueen($0) : nil
                                    }
                                })
    }

    return resolve_intarnal(self.sizeN, [self])
}

参考文献とソースコード

プロジェクトファイル

  •  Logo 人工知能へのアプローチ
    https://www.amazon.co.jp/Logo-人工知能へのアプローチ-ラジオ技術選書-150-祐安-重夫/dp/4844301500
]]>
Fri, 30 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/27/2a27d39e_92c4_42b8_a3e0_74d5c7a0252e.html https://www.mindto01s.com/2017/06/27/2a27d39e_92c4_42b8_a3e0_74d5c7a0252e.html <![CDATA[Swiftで8人の女王]]> Swiftで8人の女王

swift playgrouds での5番目のプログラミング。今回は8Queenパズル。

チェスのクイーンは、チェス盤上で最も自由に動けまわれるコマです。 縦横斜めの8方向に、任意の数だけ駒を進めることが出来ます。将棋でいう飛車と角を合わせた動きです。

このクイーンを8x8のチェス盤上に8個、お互いに取られないように配置するのが8Queenパズルです。

解法は 8Queen から見てゆくとわかりやすいです。

参考文献ではバックトラッキングの例題として書かれて素直に移植したつもりだった。 しかし書いているうちに、深さ優先の検索でなく広さ優先検索になっていた。

作ったクラスの動き

以下のようにチェス版の大きさを指定して、結果を出力させます。sizeは板の大きさです。8Queenなので8の大きさにしてます。

let queen = EQBoard(size:8)
let QueenAnser = queen.resolve()

QueenAnser.map{ $0.debugPrint() }
print("---\(QueenAnser.count)")

出力結果は以下のようになります。

 q . . . . . . .
 . . . . q . . .
 . . . . . . . q
 . . . . . q . .
 . . q . . . . .
 . . . . . . q .
 . q . . . . . .
 . . . q . . . .

 q . . . . . . .
 . . . . . q . .
 . . . . . . . q
 . . q . . . . .
 . . . . . . q .
 . . . q . . . .
 . q . . . . . .
 . . . . q . . .

 q . . . . . . .
 . . . . . . q .
 . . . q . . . .
 . . . . . q . .
 . . . . . . . q
 . q . . . . . .
 . . . . q . . .
 . . q . . . . .

    .
    .
    .

 . . . . . . . q
 . . . q . . . .
 q . . . . . . .
 . . q . . . . .
 . . . . . q . .
 . q . . . . . .
 . . . . . . q .
 . . . . q . . .

---92

qの文字はQueenの位置を示しています。最後の–92は、結果が92パターンの配置があるとの結果です。

作ったクラスの中み

構造体の名前は “EQBoard” 、プロパティは2つ、メソッド数は 6つ。

初期化メソッドが2つ、デバッグメソッドが1つなので、計算をする部分は事実上3つのメソッドで行なっています。

で、メインの3つのメソッドは以下の通り。

  • resolve
    ここで、解を求める
  • boardAddAQueen
    今の盤にqueenを1個追加した盤を返す。resolveの補助。
  • tryAddAQueen
    今の盤に配置可能かのチェックを行う。resolveの補助。

ソースはこんな感じ。

func resolve() -> [EQBoard]
{
    precondition(queens.count <= self.sizeN)

    func resolve_intarnal( _ count:Int, _ boards:[EQBoard]) -> [EQBoard]
    {
        if count == 0
        {
            return boards;
        }

        // 与えられた板の次のrawの配置の配列を計算する
        return resolve_intarnal( count - 1,
                                 boards.reduce([])// 計算結果を配列にひとまとめにする
                                 {
                                    let i = $1

                                    return $0 + ( 0..<i.sizeN ) // 横のマスの数だけ、置いて試す
                                        .flatMap
                                        {
                                            i.tryAddAQueen($0) ? i.boardAddAQueen($0) : nil
                                        }
                                })
    }

    return resolve_intarnal(self.sizeN, [self])
}



// queenを1個追加した盤を返す
func boardAddAQueen(_ newQueen: Int) -> EQBoard
{
    precondition(newQueen >= 0)
    precondition(newQueen < self.sizeN)
    precondition(queens.count < self.sizeN)

    return EQBoard(size:self.sizeN, queens: self.queens + [newQueen])
}


// 配置可能かのチェックを行う
func tryAddAQueen(_ newQueen: Int) -> Bool
{
    precondition(newQueen >= 0)
    precondition(newQueen < self.sizeN)

    // 縦方向と左右の斜めにQueenがないかチェックする
    return ( self.queens.enumerated().map
                {
                    ( $0.element == newQueen)                                  || /* 縦が一致       */
                    ( $0.element == newQueen - self.queens.count + $0.offset ) || /* 斜め左方向が一致 */
                    ( $0.element == newQueen + self.queens.count - $0.offset ) ?  /* 斜め右方向が一致 */
                     false : true
                }.filter{ $0==false }.count ) == 0
}

解説は、次回のCocoa勉強会で行います。じゃあね。

参考文献とソースコード

プロジェクトファイル

  •  Logo 人工知能へのアプローチ
    https://www.amazon.co.jp/Logo-人工知能へのアプローチ-ラジオ技術選書-150-祐安-重夫/dp/4844301500
]]>
Tue, 27 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/26/0c5e9b1a_150e_4dce_b7f8_2f0fd83fde8f.html https://www.mindto01s.com/2017/06/26/0c5e9b1a_150e_4dce_b7f8_2f0fd83fde8f.html <![CDATA[落穂拾い 2017/06/26]]> 落穂拾い 2017/06/26 ]]> Mon, 26 Jun 2017 00:00:00 +0900 https://www.mindto01s.com/2017/06/23/d0dc0786_b3fc_40b5_a46c_8d2508dd5d78.html https://www.mindto01s.com/2017/06/23/d0dc0786_b3fc_40b5_a46c_8d2508dd5d78.html <![CDATA[swiftで最小限タートルグラフィック]]> swiftで最小限タートルグラフィック

swift playgrouds での4番目のプログラミング。今回はタートルグラフィックスの実装。

機能は最小限の LOGO の機能で有名なタートルグラフィックスをswiftで実装した。

実装したコマンドは、以下の6つ。カラーは未実装。

  • right
  • left
  • forward
  • backward
  • penDown
  • penUp

これらのコマンドの組み合わせで、以下のようなグラフィックを表現できる。

../../../_images/tg01.png ../../../_images/tg02.png ../../../_images/tg03.png ../../../_images/tg04.png ../../../_images/tg05.png

作ったクラスとその概要

ユーティリティー的なコードを除外すると、以下のような単純なコードです。

struct tinyTurtle
{
    let path             = NSBezierPath()
    var position         = CGPoint(x:0, y:0) // 絶対値での現在地
    var degree           = CGFloat(90.0)     // 角度。水平方向の右側が0度なので最初は上を向ける
    var penState         = true              // ペンを置いているか否か

    init()
    {
        self.path.move(to: position)
    }

    // 現在の方向から計回りに角度を曲げる
    mutating func right(_ degree: CGFloat)
    {
        self.degree -= degree
    }

    // 逆回転も追加
    mutating func left(_ degree: CGFloat)
    {
        right(-degree)
    }

    // 現在の位置から前進
    mutating func forward(_ length: CGFloat)
    {
        // 角度と長さと現在地から、前進後の位置を求める
        let radian   = self.degree * (CGFloat.pi / 180) //ラジアンに変換
        let vector   = length * CGPoint(x: cos(radian), y: sin(radian))
        let absPoint = self.path.currentPoint + vector

        if penState
        {
            self.path.line(to: absPoint)
        }
        else
        {
            self.path.move(to: absPoint)
        }
    }

    // バックも追加
    mutating func backward(_ length: CGFloat)
    {
        forward(-length)
    }

    // 描画用のペンの上げ下げ
    mutating func penDown()
    {
        self.penState = true
    }

    mutating func penUp()
    {
        self.penState = false
    }

    // 絶対値で示す位置へ移動 描画ポイントが迷子になっちゃうので追加
    mutating func goto(_ x: CGFloat, _ y: CGFloat)
    {
        self.position = CGPoint(x:x, y:y)
        self.path.move(to: self.position)
    }

    func draw()
    {
        self.path.stroke()
    }
}

解説は、 関東swift勉強会2017-6 で行います。

使い方

多角形を描く。

NSImage.imageWithBlock(NSSize(width:300, height:200))
{
    var t = tinyTurtle()

    t.goto(150,100)

    func poly(_ n: Int, _ size: CGFloat)
    {
        for _ in 1...n
        {
            t.forward(size)
            t.right(360.0 / CGFloat(n))
        }
    }

    poly(3, 50)
    poly(5, 50)
    poly(8, 50)

    t.draw()
}

出力結果は、以下のようになる。

../../../_images/tg01.png

ユーティリテーメソッドを利用すると、以下のようにもかける。

tinyTurtle.tinyTurtleWithBlock(NSSize(width:300, height:200))
{
    t in

    func poly(_ n: Int, _ size: CGFloat)
    {
        for _ in 1...n
        {
            t.forward(size)
            t.right(360.0 / CGFloat(n))
        }
    }

    poly(3, 50)
    poly(5, 50)
    poly(8, 50)
}

他にも、コッホ曲線を描く場合はこんな感じになります。

tinyTurtle.tinyTurtleWithBlock(NSSize(width:300, height:200))
{
    t in

    func koch(_ size: CGFloat, _ angle:CGFloat, _ n:Int)
    {
        if n == 0
        {
            t.forward(size)
            return
        }
        else
        {
            koch(size/3, angle, n-1)
            t.left(angle)
            koch(size/3, angle, n-1)
            t.right(angle * 2)
            koch(size/3, angle, n-1)
            t.left(angle)
            koch(size/3, angle, n-1)
        }
    }

    t.goto(50,20)
    t.right(90)

    koch(200, 60, 5)
}

結果は、以下のようになります。

../../../_images/tg05.png

参考文献とソースコード

プロジェクトファイル

  •  Logo 人工知能へのアプローチ
    https://www.amazon.co.jp/Logo-人工知能へのアプローチ-ラジオ技術選書-150-祐安-重夫/dp/4844301500
]]>
Fri, 23 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/22/25f6cce1_9236_4028_ad82_2ffc3419d0ec.html https://www.mindto01s.com/2017/06/22/25f6cce1_9236_4028_ad82_2ffc3419d0ec.html <![CDATA[swiftで三段論法]]> swiftで三段論法

swift playgrouds での3番目のプログラミング。今回は、三段論法を使用する人工知能っぽい何か。

../../../_images/theSandbox04.png

機械に推論させるのは、難しいと思われるかもしれない。三段論法くらいであれば、結構単純なアルゴリズムで推論が実行できる。

大雑把な解説

プログラムは、structが一つ、メソッドは6つ、プロパティが1つ、の単純な構成です。

大きく分けて、2つの部分からなっています。

  • 三段論法を実行する部分
    • プロパティ: “知識集” 三段論法で使用する事実を保持します
    • メソッド : “デバッグ表示” デバッグ用に “知識集” の内容を出力します。
    • メソッド : “登録” 知識の登録をします。
    • メソッド : “検索” 引数で与えられた単語の知識を検索します。
    • メソッド : “推論” 引数2つで与えられた知識の真偽を推論します。
  • 自然言語の解析部分
    • メソッド : “分ち書き” 文字列を単語に分けます。
    • メソッド : “入力” 与えられた文字列の内容に従って、知識の登録、知識の真偽を判定します。

データ構造も非常にシンプルです。

var 知識集: [(lhs: String, rhs: String)] = []

タプルの配列です。「lhsはrhsである」との形式で知識を溜め込みます。

例えば、「ソクラテスは人間です」は、タプルとして

(lhs: "ソクラテス", rhs: "人間")

とタプルの形式にして、プロパティ “知識集” に蓄積されてゆきます。

参考文献とソースコード

プロジェクトファイル

  •  Logo 人工知能へのアプローチ
    https://www.amazon.co.jp/Logo-人工知能へのアプローチ-ラジオ技術選書-150-祐安-重夫/dp/4844301500
]]>
Thu, 22 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/22/3c84a54d_4dad_49f3_b3b3_fd819a4fd271.html https://www.mindto01s.com/2017/06/22/3c84a54d_4dad_49f3_b3b3_fd819a4fd271.html <![CDATA[swiftでハノイの塔]]> swiftでハノイの塔

swift playgrouds で2番目に書いたプログラム。

機能はパズル ハノイの塔 の手順を出力するだけ。

手順は以下のような、アニメーション(wikipediaより引用)ではなくて、単なる文字列です。

../../../_images/Tower_of_Hanoi_4.gif

作った関数

func hanoi(diskCount n: Int64)  -> [(pickUp: String, putDown: String)]

今回のメイン関数。円盤の枚数を与えると、手順をタプルのリストとして返す。 “pickUp” から “putDown” へ円盤を移動する。

func printHanoi01(hanoiArray: [(pickUp: String, putDown: String)])

関数 “hanoi” の表示をログに出力するための関数。

printHanoi01(hanoiArray: hanoi(diskCount:3))

上記のように評価すると、

(pickUp: "left", putDown: "right")
(pickUp: "left", putDown: "center")
(pickUp: "right", putDown: "center")
(pickUp: "left", putDown: "right")
(pickUp: "center", putDown: "left")
(pickUp: "center", putDown: "right")
(pickUp: "left", putDown: "right")

と出力される。

func hanoi(diskCount n: Int64)  -> [(pickUp: String, putDown: String)]

こちらは、出力を日本語にして見た。

同じく出力は以下のようになる。

円盤を 左側 から 右側 に移動する
円盤を 左側 から 中央 に移動する
円盤を 右側 から 中央 に移動する
円盤を 左側 から 右側 に移動する
円盤を 中央 から 左側 に移動する
円盤を 中央 から 右側 に移動する
円盤を 左側 から 右側 に移動する

コードを書いての感想

関数hanoiの中で、関数hanoi_internalを定義して使っている。Pascal風にローカル関数が書けるようになったのは便利。

hanoi本体は以下のような感じです。

// お皿の枚数を渡すと、ハノイの塔の手順を示す関数
func hanoi(diskCount n: Int64)  -> [(pickUp: String, putDown: String)]
{
    // 関数の中で関数定義ができる
    func hanoi_internal(diskCount n: Int64, from x:String, to y:String, work z:String) -> [(pickUp: String, putDown: String)]
    {
        if n != 0
        {
            return hanoi_internal( diskCount:n - 1, from:x, to:z, work:y)
                    + [(pickUp: x, putDown: y)]
                    + hanoi_internal( diskCount:n - 1, from:z, to:y, work:x)
        }
        else
        {
            return []
        }
    }

    return hanoi_internal(diskCount:n, from:"left", to:"right", work:"center")
}

hanoi_internalを無名関数にしてより簡潔に表記する方法がありそうだが見当たらなかった。

無名関数の中で自分自身へのポインターを示す特殊なキーワードがありそう。引数だと$0, $1, $2…などの表記があるので、$$がや$_あたりにあれば良いなと妄想中。

// こんな表記ができると最高なんだが...... $_を関数自身へのポインターとみなした場合
func hanoi(diskCount n: Int64)  -> [(pickUp: String, putDown: String)]
{
    return {
                diskCount n: Int64, from x:String, to y:String, work z:String)
                -> [(pickUp: String, putDown: String)] in

                if n != 0
                {
                    return $_( diskCount:n - 1, from:x, to:z, work:y)
                            + [(pickUp: x, putDown: y)]
                            + $_( diskCount:n - 1, from:z, to:y, work:x)
                }
                else
                {
                    return []
                }
    }(diskCount:n, from:"left", to:"right", work:"center")
}

参考文献とソースコード

プロジェクトファイル

  •  Logo 人工知能へのアプローチ
    https://www.amazon.co.jp/Logo-人工知能へのアプローチ-ラジオ技術選書-150-祐安-重夫/dp/4844301500
]]>
Thu, 22 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/18/f7daf735_d81e_4bfe_9383_12d5af08376c.html https://www.mindto01s.com/2017/06/18/f7daf735_d81e_4bfe_9383_12d5af08376c.html <![CDATA[MacPortsでInstallしたPythonを有効にした時のメモ]]> MacPortsでInstallしたPythonを有効にした時のメモ

解決するのに非常に以外に時間がかかった。

MacPorts側の解決。

sudo port select pip pip34
sudo port select python python34
sudo port select python3 python34
sudo port select sphinx py34-sphinx

ここまでは、install時にコメントが出るので簡単。

さらにPython系のツール群へパスを通すために、bash_profileに以下を追加。

# Python系のツール群へパスを通す
export PATH="/opt/local/Library/Frameworks/Python.framework/Versions/3.4/bin:$PATH"

このパスの位置がMacPortsの流儀のようで探すのに苦労した。

MacPortsでインストールしたツールは、/opt/local/bin/へシンボリックリンクを貼が貼られる。

一方、pipでinstallしたものは、シンボリックリンクは貼られない。pipはMacPortsの事は知らないので当然か。

自分でツール毎にシンボリックリンクを作るか、/opt/local/Library/Frameworks/Python.framework/Versions/3.4/binへパスを通すか迷ったが、パスを通す方を選んだ。

]]>
Sun, 18 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/16/db23143b_a7cd_4b7e_858e_700ba94c9246.html https://www.mindto01s.com/2017/06/16/db23143b_a7cd_4b7e_858e_700ba94c9246.html <![CDATA[落穂拾い 2017/06/16]]> 落穂拾い 2017/06/16 ]]> Fri, 16 Jun 2017 00:00:00 +0900 https://www.mindto01s.com/2017/06/15/daca0d98_0cdb_4dd3_b636_a8b43758b451.html https://www.mindto01s.com/2017/06/15/daca0d98_0cdb_4dd3_b636_a8b43758b451.html <![CDATA[渦巻く幻燈]]> 渦巻く幻燈

swift playgrouds で初めて書いたプログラム。

../../../_images/RDS-02-11.png

機能はランダムドット画像を少しだけ回転と拡大を加えた図を表示するだけ。

playgroudsは書き捨ての実験プログラムにはちょうど良い手軽さ。昔読んだけど試さなかったアルゴリズムの本のコードを色々と実験したら面白そうだ。

回転を大きくすると螺旋を認識できなくなる

同じランダムドット画像を2枚重ねて見ているような図。重ね方は矩形の真ん中を中心点にして、回転、拡大している。 そのため、プログラムコード自体にも、出力した画像データにも螺旋構造は存在しない。

../../../_images/RDS-00-11.png

回転角度:0 , 拡大率:1.1

回転はなし、拡大だけの場合は、「爆発している感」がある。

../../../_images/RDS-02-11.png

回転角度:2 , 拡大率:1.1

回転2度、拡大の場合は、「渦巻いてる感」が出てくる。

../../../_images/RDS-03-11.png

回転角度:3 , 拡大率:1.1

回転2度、拡大の場合は、「渦巻いてる感」が出てくるが、角度2と対して変わらないかな?

../../../_images/RDS-10-11.png

回転角度:10 , 拡大率:1.1

回転10度、拡大の場合は、「渦巻いてる感」が真ん中の方だに存在する。 真ん中の方がドットの間の間隔が狭いからかもしれない。 爆発している感じもなくなる。

../../../_images/RDS-20-11.png

回転角度:20 , 拡大率:1.1

回転20度、拡大の場合は、「渦巻いてる感」も、「爆発している感」もさっぱりなくなる。

図形とは認識できないランダムドットでも条件が加われば「フレーザーの渦巻き錯視」と同じような錯覚が生じているように見える。

参考とソースコード

プロジェクトファイル

]]>
Thu, 15 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/13/35e2e1fe_c1c1_46dc_aa72_da317aad8946.html https://www.mindto01s.com/2017/06/13/35e2e1fe_c1c1_46dc_aa72_da317aad8946.html <![CDATA[sphinx1.6でpdf出力できないのは私の設定ミスでしたその2]]> sphinx1.6でpdf出力できないのは私の設定ミスでしたその2

今度は、html出力時に動作がおかしい

“language = ‘ja‘“だとsphinxcontrib.tikzの出力がエラーになる。

仕方がないので、makefileに以下のような記述を追加してエラーを回避。

# html出力でlanguageがjaだとTikZ Extentionがエラーを吐く。理由は不明。
html: Makefile makeBB
    @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -Dlanguage='en'

# こっちは素直にbbを作ってからコンパイル
latexpdf: Makefile makeBB
    @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

# TikZ使用時にxbbがないエラーを回避するために、画像ファイルにはxbbを作成しておく。
# (macPortでインストールした場合は、自動でxbbを生成しない設定らしい)
makeBB:
    find ./source -name \*.png -or -name \*.jpg -or -name \*.pdf \
    | while read theFile; do extractbb $$theFile; done

latexもpythonもわかっていないので、原因は不明。

原因の予想

  1. sphinx1.6のLaTeXBuilder出力の方法がlatexmkを使うように変更になった。 (http://www.sphinx-doc.org/en/stable/builders.html#sphinx.builders.latex.LaTeXBuilder)

2. sphinxcontrib.tikzの中ではconfig.latex_engineで設定されているlatexを使っている。 (https://bitbucket.org/philexander/tikz/src/36f13083d61e91eb7cfea6f1ed959118749e371c/sphinxcontrib/tikz.py?at=master&fileviewer=file-view-default#tikz.py-245)

  1. sphinxcontrib.tikzの記述がlatexmkと衝突するような記述になっているかもしれない。

対策

makeファイルを変更する対策は小手先の対策すぎる。

私の目的には、tikzは強力すぎるのでもう少し単純な描画言語で画像にマークアップしたい。

sphinxcontrib.tikzのコードを修正するか、新たにextensionを作るかで迷う。

どっちらにしても、pythonは読み書きできないのでこれから勉強する必要がある。

プロジェクトファイル

]]>
Tue, 13 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/13/756f076b_76f9_4f54_b015_953bc240474e.html https://www.mindto01s.com/2017/06/13/756f076b_76f9_4f54_b015_953bc240474e.html <![CDATA[sphinx1.6でpdf出力できないのは私の設定ミスでした]]> sphinx1.6でpdf出力できないのは私の設定ミスでした

:doc:`../10/1c8b065d_3368_4c24_b50c_2f2045de0ac8`にて、sphinx1.6でpdf出力できないと書きました。

これは、間違いです。出来ます。

設定ミスしたところは、ファイルconf.pyの中のlanguage設定です。

jpとするのは間違い。

language = 'jp'

jaとするのが正しい。

language = 'ja'

jaはジャマイカではなくて、日本とのこと。

また、ビルドターゲットも変更があったようです。

以下のように、latexpdfjaを指定すると、エラーで止まります。./build/latex/makeにターゲット’all-pdf-ja’が存在しないとの事。

make latexpdfja

ビルドするには、以下のように、latexpdfを使用するだけで良い。

make latexpdf
]]>
Tue, 13 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/13/266a34fd_3125_4fd3_b6ae_e1ad709473bc.html https://www.mindto01s.com/2017/06/13/266a34fd_3125_4fd3_b6ae_e1ad709473bc.html <![CDATA[落穂拾い 2017/06/13]]> 落穂拾い 2017/06/13

Apple Developer Center

]]>
Tue, 13 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/11/822b69ef_eb78_4dfa_b31b_d000ee9c206d.html https://www.mindto01s.com/2017/06/11/822b69ef_eb78_4dfa_b31b_d000ee9c206d.html <![CDATA[TikZ Sphinx Extensionを導入のメモその3]]> TikZ Sphinx Extensionを導入のメモその3

tikz_latex_preambleにマクロを書くことで、簡潔にかけるようになった。

マークアップ

conf.pyのtikz_latex_preambleを追加。

tikz_latex_preamble = r'\newcommand{\markUpBox}[3]{\draw[red,ultra thick,rounded corners] (#1) rectangle (#2); \node[draw = red, fill=white, ultra thick,rounded corners, anchor=north east] at (#2) {\textbf{#3}}; }'

以下の表記法で、矩形とラベルを同時に書く。

\markUpBox{左下}{右上}{LabelName}

以下のように書くと、

.. tikz:: マークアップ
   :stringsubst:

   \node[anchor=south west] (image) at (0,0) {\includegraphics[width=10cm]{$wd/source/xcode01.png}};
   %\draw[step=1,lightgray] (0,0) grid (image.north east);
   %\draw[step=10,gray] (0,0) grid (image.north east);
   \markUpBox{0.5cm, 0.7cm}{2.2cm,5.7cm}{A};
   \markUpBox{8.0cm, 0.7cm}{9.7cm,5.7cm}{B};
   \markUpBox{2.5cm, 0.7cm}{7.5cm,5.7cm}{C};

以下のような画像が得られる。

../../../_images/tikz-154c9b51dff795c46e2a026b368c763aee590d84.png

感想

目的も果たせた。簡潔な表記も可能になった。conf.pyの表記が煩雑になるのは気にくわないが、許容範囲に収まった。

プロジェクトファイル

]]>
Sun, 11 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/10/1c8b065d_3368_4c24_b50c_2f2045de0ac8.html https://www.mindto01s.com/2017/06/10/1c8b065d_3368_4c24_b50c_2f2045de0ac8.html <![CDATA[TikZ Sphinx Extensionを導入のメモその2]]> TikZ Sphinx Extensionを導入のメモその2

tikzを使う目的はスクリーンショット画像へのマークアップ。

プロジェクトファイル

html出力の設定

conf.pyのextensionsに以下を設定するだけ。

#
extensions = ['sphinxcontrib.tikz']

pdf出力の設定

作業中に、sphinxのバージョンを1.5系から1.6へアップデートしたら、pdfの生成がエラーで止まるようになった。 色々とググると、sphinx1.6からは latexmk と呼ばれるコマンドを使うようになったらしい。

http://www.sphinx-doc.org/en/stable/builders.html#sphinx.builders.latex.LaTeXBuilder

Changed in version 1.6: Use of latexmk for make latexpdf on GNU/Linux and Mac OS X

latexmkのお勉強は後回しにして、pdf出力は今は諦める。

以下サンプル

単純な描画

../../../_images/tikz-a2b4cec0987802fd3f9b5d9d1fbdd64020e114cb1.png

上記のソースコード

.. tikz:: tikzで描いた図

    \draw[thick,rounded corners=8pt]
   (0,0)--(0,2)--(1,3.25)--(2,2)--(2,0)--(0,2)--(2,2)--(0,0)--(2,0);

画像ファイルの読み込み

既存の画像を読み込むには、フルパスを指定する必要がある。

フルパスの指定は、以下の手順。

  1. オプション”:stringsubst:”を指定する文字列の置き換えを行う宣言をする。
  2. “Makefile”があるディレクトリまでを、文字列”$wd”で示す。
../../../_images/tikz-2233f044a81a15d1b6fd64f95524ca96548daa6a.png

上記のソースコード

.. tikz:: 画像ファイルの読み込み
   :stringsubst:

   \node {\includegraphics[width=10cm]{$wd/source/xcode01.png}};

マークアップの下準備のための、格子の描画

../../../_images/tikz-f5f62616f219e30cbb28290f303f923379302eab.png

上記のソースコード

.. tikz:: 格子付き
   :stringsubst:

   \node[anchor=south west] (image) at (0,0) {\includegraphics[width=10cm]{$wd/source/xcode01.png}};
   \draw[step=1,lightgray] (0,0) grid (image.north east);
   \draw[step=10,gray] (0,0) grid (image.north east);

マークアップ

目的のマークアップ

../../../_images/tikz-84836e56fdcb4b958242bf6d4e0242e62d07612c.png

上記のソースコード。

.. tikz:: マークアップ
   :stringsubst:

   \node[anchor=south west] (image) at (0,0) {\includegraphics[width=10cm]{$wd/source/xcode01.png}};
   %\draw[step=1,lightgray] (0,0) grid (image.north east);
   %\draw[step=10,gray] (0,0) grid (image.north east);
   \node[draw = red, fill=white, ultra thick,rounded corners, anchor=north east] at (2.2cm,5.7cm) {\textbf{A}};
   \draw[red,ultra thick,rounded corners] (0.5cm, 0.7cm) rectangle (2.2cm,5.7cm);
   \node[draw = red, fill=white, ultra thick,rounded corners, anchor=north east] at (9.7cm,5.7cm) {\textbf{B}};
   \draw[red,ultra thick,rounded corners] (8.0cm, 0.7cm) rectangle (9.7cm,5.7cm);
   \node[draw = red, fill=white, ultra thick,rounded corners, anchor=north east] at (7.5cm,5.7cm) {\textbf{C}};
   \draw[red,ultra thick,rounded corners] (2.5cm, 0.7cm) rectangle (7.5cm,5.7cm);

感想

latexのマクロで簡潔に書こうとしたが、そもそもどこに書けば良いのかわからない。latex_elements/preambleはlatexpdf出力の時のものなので、このtikz拡張には関係なし。

tikz_latex_preambleに書けば良いと思い書いたが、バックスラッシュだらけで意味不明になってバグが取れなかった。

目的は果たせたが、コレジャナイ感が満載。うーん。

]]>
Sat, 10 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/06/09/62f89e9a_3f49_46d4_ae65_9de0eb350b15.html https://www.mindto01s.com/2017/06/09/62f89e9a_3f49_46d4_ae65_9de0eb350b15.html <![CDATA[sphinxのadmonition用にstyを書いた]]> sphinxのadmonition用にstyを書いた

デフォルトの設定のままだと、admonitionのhtml/pdf出力共に味気ない。 cssとstyファイルを書いて、少しだけ見た目を改善した。

html出力例 pdf出力例 プロジェクトファイル

html出力の設定

conf.pyの末尾に以下を設定して、cssを適当にいじる。 前回の sphinxのadmonition用にcssを書いたの続き とほぼ同じ。

# HTMLテーマに独自のCSS/JSファイルを読み込ませてデザイン調整等したい
# http://sphinx-users.jp/reverse-dict/html/custom-css-js.html
def setup(app):
    app.add_stylesheet('custom.css') # ここはhtml用

画像ファイルや、cssは_staticディレクトリ直下へ配置する。

_staticにcustom.cssを配置

.
|-- Makefile
`-- source
    |-- _static
    |   |-- Caution.png    <--- これらは、アイコン用の画像
    |   |-- Danger.png
    |   |-- Note.png
    |   |-- Question.png
    |   |-- Tip.png
    |   |-- Warning.png
    |   `-- custom.css     <--- カスタムcss
    |-- _templates
    |-- admonition.rst
    |-- conf.py
    `-- index.rst

Tip

treeコマンドの出力をそのまま使うと、pdf出力で文字化けしてしまった。 tree --charset=C とオプションを指定することで、文字化けしない文字だけで構成することで回避できる。

custom.cssでは、以下のようになっている。

/* -- div.admonition ------------------------------------------------------ */

/*アイコンの下に付くように、タイトル文の表示位置の設定*/
div.admonition p.admonition-title {
    width: 65px;
    padding-top: 65px;
    text-align: center;
    font-weight: bolder;
    font-size: 18px;
    float:left;
    margin-left: -18px;
}

/*タイトル以外の文をアイコンを避けるように配置*/
p.last {
    padding-top: 0px;
    padding-right: 9px;
    padding-left: 100px;
    text-align: left;
    margin-top: 0px;
}
/*アイコンの配置と背景色の設定*/
div.caution{
    background: url(Caution.png) no-repeat;
    background-position: 9px 9px;
    background-size:64px 64px;

    border-radius: 10px 10px 10px 10px;
    background-color: #ededed;
    min-height: 110px;

    border: none;
}
.
.
.

pdf出力の設定

カスタム設定をまとめる、*.styファイルを作り、読み込ませる。

_staticにcustom.styを配置

.
|-- Makefile
`-- source
    |-- _static
    |   |-- Caution.png
    |   |-- Danger.png
    |   |-- Note.png
    |   |-- Question.png
    |   |-- Tip.png
    |   |-- Warning.png
    |   |-- custom.css
    |   `-- custom.sty     <--- カスタムsty
    |-- _templates
    |-- admonition.rst
    |-- conf.py
    `-- index.rst

_static/custom.styとアイコン群ををconf.pyのlatex_additional_filesを使って、build時にlatexが読み込める位置コピーさせる。 conf.pyに以下の設定を書き足す。

# preambleに長々と書きたく無いので、custom.styへ外出ししたものを読み込むようにする。
latex_additional_files = ['_static/custom.sty',
                          '_static/Caution.png', # admonitionで使用するアイコン画像群
                          '_static/Danger.png',
                          '_static/Note.png',
                          '_static/Question.png',
                          '_static/Tip.png',
                          '_static/Warning.png'
                          ]

custom.styを読み込むために、latex_elements/preambleに設定。 passoptionstopackagesを追加しているが自分はよくわかっていない。おそらく、tcolorboxがxcolorを要求するのだと思っている。 conf.pyに以下の設定を書き足す。

latex_elements = {
        .
        .
        .
        .
    'passoptionstopackages': r'\PassOptionsToPackage{dvipdfmx}{xcolor}',

    # 同じディレクトリにあるcustum.styを読み込む。
    'preamble': r'\usepackage{custom}',
}

custom.styでは、admonitionのマクロの置き換えを行なっている。

  • 灰色の囲いは、tcolorboxパッケージを使って描画している
  • admonitionのマクロは、sphinx.styの中で定義されている以下のマクロを再定義して、実装している。
  • cautionはsphinxcautionマクロ、dangeはsphinxdangerがマクロ定義されている。
\usepackage{tcolorbox}

% sphinxadmonitionを一部上書きするためのHelperマクロ。アイコンと文字列を指定して囲みを作る。
\newenvironment{sphinxadmonition_helper}[2] % アイコン画像名, タイトル文字列
{
    \begin{tcolorbox}[colframe=white ,sidebyside, lower separated=false, lefthand width=1.6cm, arc=5mm]
        \center
        \includegraphics[width=1.6cm]{#1}\\
        \bf #2
        \tcblower
}
{
    \end{tcolorbox}
}

% ".. caution::"の対応。sphinx.styのsphinxadmonitionの中から呼び出されるマクロを上書きしている
\renewenvironment{sphinxcaution}[1]
{\begin{sphinxadmonition_helper}{Caution.png}{#1}}{\end{sphinxadmonition_helper}}

% ".. danger::"の対応。
\renewenvironment{sphinxdanger}[1]
{\begin{sphinxadmonition_helper}{Danger.png}{#1}}{\end{sphinxadmonition_helper}}

        .
        .
        .
]]>
Fri, 09 Jun 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/25/138af259_e44d_4373_ae03_b5dafb6114db.html https://www.mindto01s.com/2017/05/25/138af259_e44d_4373_ae03_b5dafb6114db.html <![CDATA[最小限データーベースその3]]> 最小限データーベースその3

コピペとDrag&Dropのデータタイプを増やす。

今までは、自分自身が作成した、データタイプ “com.mindto01s.M3XNote” しかコピペもDrag&Dropも出来なかった。

そのため、newコマンドで項目ノートを作成してから、TextViewへデータをペーストしていた。

これを、Pasteboard に、以下のデータタイプがある場合は、ペーストコマンドで自動で項目ノートの作成とそのノートへのペーストを一手間でできるようにした。

Dropの受け入れ

[M3XViewController viewDidLoad] で [M3XNote pasteBoardRowsType]を[M3XNote pasteBoardRowsTypes]とするだけ。

[self.browserView registerForDraggedTypes:[M3XNote pasteBoardRowsTypes]];

[M3XNote pasteBoardRowsTypes]の方では、以下のように受け入れ可能なUTIを列挙している。

+ (NSArray<NSString*>*) pasteBoardRowsTypes
{
    return @[
             [self pasteBoardRowsType],
             NSPasteboardTypeRTFD,
             NSPasteboardTypeRTF,
             NSPasteboardTypeString,
             NSPasteboardTypePNG, // <-- 入力のみ
             NSPasteboardTypeTIFF,  // <-- 入力のみ
             ];
}

この変更だけで、Dropはは可能になる。もちろんDropした後のコードは何も書いていないので、Dropした瞬間に落ちるはず。

ペーストボードへの書き込み

この変更は、[M3XNote wreiteToPasteboard:]の中だけ。

ペーストボードには複数のデータタイプを入れられるので、今までのコードの末尾に、書き出したいデータのコードを書き足すだけ。

- (void) wreiteToPasteboard:(NSPasteboard*)pasteboard
{
    .
    .
    .

    // 以下から書き足したコード
    NSAttributedString* theAttributedString = [[NSAttributedString alloc] initWithRTFD:self.memo documentAttributes:nil];

    [pasteboard setData:[theAttributedString RTFDFromRange:NSMakeRange(0, theAttributedString.length)
                                        documentAttributes:@{}]
                forType:NSPasteboardTypeRTFD];

    [pasteboard setData:[theAttributedString RTFFromRange:NSMakeRange(0, theAttributedString.length)
                                       documentAttributes:@{}]
                forType:NSPasteboardTypeRTF];

    [pasteboard setString:theAttributedString.string
                  forType:NSPasteboardTypeString];
}

ペーストボードに対してsetData:forTypeなどで書き出している。

ここまでのコードを書くと、このアプリの項目ノートを他のテキストエディターなどペースト出来るようになる。

ペーストボードからの読み出し

読み出しのコードはかなり汚い。

上から順番に、欲しいデータが存在したら処理をしている。独自データが最優先で、次にRTFDなどの文字列系が続き、最後は画像データの順番になっている。

RTFDなどの文字列系は以下のように単純に、NSData型に変換してCoreDataに入れているだけ。

+ (instancetype) readFromPasteboard:(NSPasteboard*)pasteboard
             inManagedObjectContext:(NSManagedObjectContext *)context
{
    M3XNote* theResult = nil;

    .
    .
    .
    // RTFD
    NSData* theRTFD = [pasteboard dataForType:NSPasteboardTypeRTFD];

    if( theRTFD != nil )
    {
        theResult = [NSEntityDescription insertNewObjectForEntityForName:@"M3XNote"
                                                  inManagedObjectContext:context];

        theResult.memo = theRTFD;
        theResult.isHome = NO;

        return theResult;
    }
    .
    .
    .
}

最後の画像系のデータだけ少しだけ複雑なことを行なっている。

NSTextViewはRTFDのデータを表示する機能はあるが、NSImageViewのように直接Imageを設定はできない。 そのため、NSTextAttachmentを使用して、画像を貼り付ける形になる。

+ (instancetype) readFromPasteboard:(NSPasteboard*)pasteboard
             inManagedObjectContext:(NSManagedObjectContext *)context
{
    M3XNote* theResult = nil;

    .
    .
    .
    // PNG or TIFF
    NSData* theImageData;
    theImageData = [pasteboard dataForType:NSPasteboardTypePNG];
    if( theImageData == nil )
    {
        theImageData = [pasteboard dataForType:NSPasteboardTypeTIFF];
    }

    if( theImageData != nil )
    {
        theResult = [NSEntityDescription insertNewObjectForEntityForName:@"M3XNote"
                                                  inManagedObjectContext:context];

        NSDateFormatter* theDateFormatter = [[NSDateFormatter alloc] init];
        [theDateFormatter setDateFormat:@"yy/MM/dd H:mm:ss"];

        NSString* theMemoString = [NSString stringWithFormat:@"画像 : %@\n", [theDateFormatter stringFromDate:[NSDate date]]];

        NSMutableAttributedString* theAttributedString = [[NSMutableAttributedString alloc] initWithString:theMemoString];

        NSTextAttachment* theAttachment = [[NSTextAttachment alloc] init];
        theAttachment.image = [[NSImage alloc] initWithData:theImageData];
        NSAttributedString* theAttatchText = [NSAttributedString attributedStringWithAttachment:theAttachment];

        [theAttributedString appendAttributedString:theAttatchText];

        theResult.memo = [theAttributedString RTFDFromRange:NSMakeRange(0, theAttributedString.length)
                                         documentAttributes:@{}];
        theResult.isHome = NO;

        return theResult;
    }
    .
    .
    .
}

ここまで出来れば、他のアプリからこのアプリへコピー&ペーストが出来るようになる。

今後の課題

実装していないものとして、URLとhtmlのデータタイプがある。これがないために、SafariからURLのDrag&Dropで項目ノートを作成することは出来ない。

これは、現在のアプリのデータ構造の持ち方では、サーバーからデータを取得する時に、メインスレッドをブロックする必要がありそうなので、実装しなかった。

同じく、ファイルDropも実装しなかった。画像ファイルやRTFDファイルをDrop可能にしようかとも考えたが、サイズの大きいデータを考慮していない設計なのでこれも実装しなかった。

今回はここまで。多分続きはなし。( microMemex3.zip )

なんか、code-blockが上手く働いてくれないなぁ。でも、眠いから寝る。

]]>
Thu, 25 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/25/e7c17742_9d20_4ff1_ad9e_8dfecc253f02.html https://www.mindto01s.com/2017/05/25/e7c17742_9d20_4ff1_ad9e_8dfecc253f02.html <![CDATA[落穂拾い 2017/05/25]]> 落穂拾い 2017/05/25 ]]> Thu, 25 May 2017 00:00:00 +0900 https://www.mindto01s.com/2017/05/21/55fcb8c8_716f_445d_a2f7_beed65f90e4d.html https://www.mindto01s.com/2017/05/21/55fcb8c8_716f_445d_a2f7_beed65f90e4d.html <![CDATA[最小限データーベースその2]]> 最小限データーベースその2

前回に課題として残っていた機能を実装する。

  1. コピー&ペーストを実装し忘れていた問題
  2. 最上位のカラムへDropできない問題
  3. Drag&Drop時のドリルダウンアニメーションに、TextViewが追随しない問題

上記の3件の実装は前回のコードに少しだけ追加の記述をすることで実装出来た。 変更があるのは、M3XViewControllerクラスだけ。

なお、以下の2件は、NSBrowserのサブクラス化が必要になりそうなので、今回はパス。

  1. TrashCanへのDropで削除できない問題
  2. NSBrowser の focus ring が適切に表示されない問題

コピー&ペーストの実装

コピペのコードは、Drag&Dropのコードの流用をしています。

そもそも、Drag&Dropは派手なコピー&ペーストなのでコード自体は同じようなものになります。

コピペメニューの実行

cutを実行するコードは以下の通り。

コピーして削除している。deleteは実装済みなので、copyさえ出来ればこのコードで動く。

- (IBAction) cut:(id)sender
{
    [self copy:sender];
    [self delete:sender];
}

copyを実行するコードは以下の通り。

選択箇所を取得して、pasteboardに書き込むだけ。Drag&DropのコードのDrag開始のコードとほぼ同じ。

異なるのは、書き込むpasteboardがドラッグ用からクリップボード用のgeneralPasteboardに変わっただけ。

- (IBAction) copy:(id)sender
{
    NSIndexPath* theSelectedPath = self.browserView.selectionIndexPath;

    // 選択されているitemの取得
    M3XNote* theSelectedNote       = [[self rootItemForBrowser:self.browserView]
                                      noteFromIndexPath:theSelectedPath];

    // ペーストボードへ書き込み
    [theSelectedNote wreiteToPasteboard:[NSPasteboard generalPasteboard]];
}

pasteを実行するコードも簡単。Dropするコードとほぼ同じ。

pasteboardの変更もcopyメソッドの変更と同じ。

 - (IBAction) paste:(id)sender
 {
     NSIndexPath* theSelectedPath = self.browserView.selectionIndexPath;

     // 選択されているitemの取得
     M3XNote* theSelectedNote = [[self rootItemForBrowser:self.browserView]
                                 noteFromIndexPath:theSelectedPath];

    // ここでCoreDataに追加する
    M3XNote* theNoteFormPasteboard = [M3XNote readFromPasteboard:[NSPasteboard generalPasteboard]
                                          inManagedObjectContext:self.managedObjectContext];

    [theSelectedNote addLinksObject:theNoteFormPasteboard];
}

前回の資料を参考にすれば特に難しいところはないはず。

メニューのenable/disableの制御機構

validateUserInterfaceItemについては過去に何度も書いているので検索してください。

コードを簡潔に表記できるように、「メソッド名の合成」を積極的に使用したコードにしています。

このvalidateUserInterfaceItem部分は、わかりにくいので、2013年頃の記事を参考に色々と悩んでいただけると嬉しい。

cutを許可するコード。単純に選択されていればcutできるようにしています。

- (BOOL) canCut:(nullable id<NSValidatedUserInterfaceItem>)item
{
    return self.browserView.selectionIndexPath != nil;;
}

copyを許可するコード。これも単純に選択されていればcutできるようにしています。

- (BOOL) canCopy:(nullable id<NSValidatedUserInterfaceItem>)item
{
    return self.browserView.selectionIndexPath != nil;;
}

pasteを許可するコード。クリップボードの中のデータをチェックしています。記憶が曖昧ですが、NSDataに変更する前に、どの型が入っているかをチェックするメソッドがあった気がします。ですので以下のコードは動きますが、よくないコードのはずです。

- (BOOL) canPaste:(nullable id<NSValidatedUserInterfaceItem>)item
{
    NSIndexPath* theSelectedPath = self.browserView.selectionIndexPath;

    // 選択されているitemの取得
    M3XNote* theSelectedNote       = [[self rootItemForBrowser:self.browserView]
                                      noteFromIndexPath:theSelectedPath];

    // もう少しスマートな書き方があるはず
    NSPasteboard*   thePasteboard = [NSPasteboard generalPasteboard];
    NSData*         theData = [thePasteboard dataForType:[M3XNote pasteBoardRowsType]];

    return (theSelectedNote != nil) && (theData != nil);
}

コピペの実装はこんな感じ。勉強会で説明したように、コピペのコードは非常に簡単です。

最上位のカラムへDropできない問題への対処

2つのメソッドの修正になる。

Dropを許可するメソッド、browser:validateDrop:proposedRow:column:dropOperation: と、

Drop後にデータを変更するメソッド browser:acceptDrop:atRow:column:dropOperation: の2つ。

必要な情報は、以下の3つ。

  1. 最上位のカラムへDropとは、項目(row)がない部分のcolumへのDrop。NSBrowserDropOperationがNSBrowserDropAboveのものを扱うことになる。
  2. NSBrowserDropOnは項目の上、NSBrowserDropAboveは項目と項目の間を意味している。
  3. rowが-1の時にcolumn全体を意味する。

すると、以下のようなコードになる。

NSBrowserDropAboveであれば、Dropはカラム全体へのDropと見なすようにする。

// Drop先
- (NSDragOperation)browser:(NSBrowser *)browser
              validateDrop:(id <NSDraggingInfo>)info
               proposedRow:(NSInteger *)row
                    column:(NSInteger *)column
             dropOperation:(NSBrowserDropOperation *)dropOperation
{
    // item間やitem以外の場所へのDropはカラム全体へのDropと見なす
    if( *dropOperation == NSBrowserDropAbove ) //<---追加した部分の開始
    {
        *row = -1;
        *dropOperation = NSBrowserDropOn;
    }                                          //<---追加した部分の終了

    if( *dropOperation == NSBrowserDropOn )
    {
        // NSDraggingInfoから
        if( info.draggingSource == self.browserView )
        {
            return NSDragOperationLink;
        }
        else
        {
            return NSDragOperationCopy;
        }
    }

    return NSDragOperationNone;
}

Drop後の処理は先ほどのDrop前の処理がわかれば簡単で、row == -1でリンクを貼るノードを変えるだけ。

// Drop後の処理
- (BOOL)  browser:(NSBrowser *)browser
       acceptDrop:(id <NSDraggingInfo>)info
            atRow:(NSInteger)row
           column:(NSInteger)column
    dropOperation:(NSBrowserDropOperation)dropOperation
{
    // まず、Drop先のNoteを求める。
    // pasteboardにデータをNoteのObjectID入れること
    M3XNote* theParentNote =  [self.browserView itemAtIndexPath:
                               [self.browserView indexPathForColumn:column]];

    M3XNote* theDropNote;

    if( row == -1 )// カラム全体
    {
        theDropNote = theParentNote;
    }
    else
    {
        // Dragできるのは一つだけ
        theDropNote = theParentNote.sortedLinks[row];
    }

    // ここでCoreDataに追加する
    M3XNote* theDraggedNote = [M3XNote readFromPasteboard:info.draggingPasteboard
                                   inManagedObjectContext:self.managedObjectContext];

    [theDropNote addLinksObject:theDraggedNote];

    return YES;
}

Drag&Drop時のドリルダウンアニメーションに、TextViewが追随しない問題

これは正しい解決方法かは不明だが、動く。

// Drag&DrilDown時にDrop先のTextViewへ内容へ変更する小細工
- (void)browser:(NSBrowser *)browser didChangeLastColumn:(NSInteger)oldLastColumn toColumn:(NSInteger)column
{
    self.selectedNode = [[self rootItemForBrowser:
                          self.browserView] noteFromIndexPath:self.browserView.selectionIndexPath];
}

lastColumnが変更されれる時に、self.selectedNodeを更新している。

self.selectedNodeの更新に合わせて、TextViewの内容が変更されるので動く。

また、Drag処理が終わると、NSBrowserのselectionIndexPathが復帰するので、そこそこうまく動いているように見える。

今回はここまで。( microMemex2.zip )

]]>
Sun, 21 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/19/36a71ed9_af33_4351_bf5c_7d7c924282ba.html https://www.mindto01s.com/2017/05/19/36a71ed9_af33_4351_bf5c_7d7c924282ba.html <![CDATA[「ヴォイニッチの書棚」のバックナンバーの取得]]> 「ヴォイニッチの書棚」のバックナンバーの取得

もう随分前に番組は終了したのですが、「ヴォイニッチの書棚」というpodcastが好きでした。長時間の移動時によく聞いてました。

また聴きたくなったので、iTunesの中を探したが最後の20回分しか出てこない。

クリラジのサーバー には音源があるようなので、ちょいとxmlを吐く使い捨てコードを書いてみた。

使い方。

ソースを実行するとクリップボードに、xmlを書き込む動作をする。

  1. 以下のように、適当なファイルにペーストする。
pbpaste > voi-book.xml
  1. 出来上がったファイルを適当なサーバーにアップする。
適当にアップロードする。
  1. itpcスキームでURLを叩いてitunesに取り込む

以下のようにitunesに投げる。

open itpc://アップしたドメイン名/適当なパス/voi-book.xml
  1. 以下の場所に生成したvoi-book.xmlをアップしたので使えるかも。

voi-book.xml

ソースコードは、 backNumber-voi.zip

以下の呼び出しで、itunesが起動するかもしれません。自分の分はダウンロードが終わったので試してません。

open itpc://www.mindto01s.com/_downloads/voi-book.xml

作ってみての感想

Objective-Cで書いたのだが、スクリプト言語で書くべきだったな。

スクリプト言語は知らないけど。

]]>
Fri, 19 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/15/5890adf2_07a7_4fbf_87e6_68fdd1e369d0.html https://www.mindto01s.com/2017/05/15/5890adf2_07a7_4fbf_87e6_68fdd1e369d0.html <![CDATA[Mosa勉強会用資料 最小限データベースの作成]]> Mosa勉強会用資料 最小限データベースの作成
  1. 使用言語はswiftでない。
  2. プラットフォームはiOSでない。
  3. 特定のテクノロジー(CoreAnimation等)のフォーカスしていない。
  4. なんらかのハウツー物ではない。
  5. チュートリアルでもない。

興味がある物を、調べ、作り、公開するだけです。

( microMemex.zip )

]]>
Mon, 15 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/07/fa706abb_d790_4d35_bc84_2a68efdaa9b2.html https://www.mindto01s.com/2017/05/07/fa706abb_d790_4d35_bc84_2a68efdaa9b2.html <![CDATA[TikZ Sphinx Extensionを導入のメモ]]> TikZ Sphinx Extensionを導入のメモ

https://bitbucket.org/philexander/tikz

conf.pyに以下を追加。

extensions = ['sphinxcontrib.tikz']

tikz_proc_suite = 'ImageMagick'

html出力時は上記の2項目で十分だった。pdf出力をする時にエラーになったので、latex_elementsに追記。

latex_elements = {
'passoptionstopackages': r'\PassOptionsToPackage{table}{xcolor} \PassOptionsToPackage{dvipdfmx}{hyperref} \PassOptionsToPackage{dvipdfmx}{graphicx} \PassOptionsToPackage{dvipdfmx}{xcolor}',

dvipdfmxの読み込み時に渡すオプションが足らないようなので、graphicxとxcolorの2つを渡すように変更した。hyperrefなどは以前のポストで話題にした目次を表示させるため。xcolorは表のシマシマ表示用。

.. tikz:: tikzで描いた図

   \draw[thick,rounded corners=8pt]
   (0,0)--(0,2)--(1,3.25)--(2,2)--(2,0)--(0,2)--(2,2)--(0,0)--(2,0);

と書くと、

../../../_images/tikz-a2b4cec0987802fd3f9b5d9d1fbdd64020e114cb.png

と表示される。

  • 現状の個人の環境での問題点。

手元の環境では、tinkerで使用できない。理由は多分MacPortsの管理下(/opt/)にないから。

pythonのインストールの流儀がわからないので、pipで入れたコマンドが何処にあるかがわからないのでパスも通せない。

]]>
Sun, 07 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/05/fe9c2e2c_1e3f_4f31_8004_1dcf5d1ac177.html https://www.mindto01s.com/2017/05/05/fe9c2e2c_1e3f_4f31_8004_1dcf5d1ac177.html <![CDATA[落穂拾い 2017/05/05]]> 落穂拾い 2017/05/05

Apple Developer Center

テクニカルノートとQA

]]>
Fri, 05 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/04/18d88112_1bc5_4719_bde4_7137cafc33cf.html https://www.mindto01s.com/2017/05/04/18d88112_1bc5_4719_bde4_7137cafc33cf.html <![CDATA[sphinxでの控えめな表 2]]> sphinxでの控えめな表 2

前回の続き。pdf出力の表の表示を変更する。

pdfの場合

conf.pyの変更とlatex用にcustom.styを作る。

この表が

../../../_images/spxTbl01pdf.png

この表のようになる。

../../../_images/spxTbl02pdf.png

薄い枠線。シマシマ表示。ヘッダは少し濃い色でボールド体。多分印刷するとそこそこ見やすいようにした(つもり)。

(1) custom.styの作成

色々とググったり、実験してみたところ、以下のような感じになった。

% 使用パッケージ
\usepackage[table]{xcolor}
\usepackage{etoolbox}

% 使用する色の定義
\definecolor{headerColor}{RGB}{192, 192, 192}
\definecolor{oddColor}{RGB}{240, 240, 240}

% tabular環境の開始と終了をホンノリ書き換える
\let\oldtabular\tabular
\let\endoldtabular\endtabular

% 2行目からシマシマ表示を開始する。
\renewenvironment{tabular}{\rowcolors{2}{oddColor}{}\oldtabular}{\endoldtabular}

% sphinxstyletheadを再定義してヘッダー行に色をつける
\renewcommand{\sphinxstylethead}{\cellcolor{headerColor}\small\textbf}

\arrayrulecolor{oddColor} % 罫線も薄くするといい感じになる

配置場所は”source/custom.sty”。conf.pyの”latex_additional_files”でファイルパスを指定できる。

latexは詳しく無いので、コメントに書いている以上のことはわからない。

(2) conf.pyの変更

公式サイトの以下の記事を参考に、conf.pyのを変更。

latex_elements = {
    # 1. xcolorの方は、rowcolorsを使いたいため
    # 2. hyperrefの方は、pdfの目次を作るため
    'passoptionstopackages': r'\PassOptionsToPackage{table}{xcolor} \PassOptionsToPackage{dvipdfmx}{hyperref}',

    # 同じディレクトリにあるcustum.styを読み込む。app.add_latex_package('custom')は使えないようだ。
    'preamble': r'\usepackage{custom}',
}

# preambleに長々と書きたく無いので、custom.styへ外出ししたものを読み込むようにする。
latex_additional_files = ['custom.sty']

latexもpythonも詳しく無いので、コメントに書いている以上のことはわからない。

pythonの文字列リテラル表現で改行も含めて表記できる方法があった気がしたが、忘れた。

忘れたので、custom.styをファイルとして外に出したが、結果的にこれが正解の気がする。

( spxTblPdf.zip )

]]>
Thu, 04 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/05/03/0ea67750_660a_4d7f_aed3_fde2eb74572b.html https://www.mindto01s.com/2017/05/03/0ea67750_660a_4d7f_aed3_fde2eb74572b.html <![CDATA[sphinxでの控えめな表 1]]> sphinxでの控えめな表 1

表の表現が強烈すぎる。もう少し控えめな罫線にしたいし、行毎に背景色を変えて見やすくしたい。

htmlの場合

htmlの場合は簡単。cssを上書きする。

この表が

../../../_images/spxTbl01.png

この表のようになる。

../../../_images/spxTbl02.png

(1) custom.cssの作成

出力されたhtmlを見ると、クラス”row-odd”と”row-even”が適宜されていたので、以下のように奇数行とヘッダ行にだけ色をつけるようにした。

/* 表の奇数偶数で色を分けて見やすくする   */
table.docutils th.head{
    background-color: #eeeeee;
}

table.docutils tr.row-odd{
    background-color: #fafafa;
}

/* 表の枠線の色を薄くして、うるさくないように変更   */
table.docutils td, table.docutils th {
    border: solid #eee;
    border-width: 1px 0.01px 0.01px 1px;
    padding: 0.25em 0.7em;
}

ついでに、枠線の色も薄くした。枠線のの定義で、”0.01px”となっているのは、”0px”にすると別の定義が有効になってしまうようなので、できる限り細くする事で対処した。

custom.cssの配置場所はデフォルトでは”_static/custom.css”。

(2) conf.pyの変更

公式サイトの以下の記事を参考に、conf.pyの末尾に追加。

HTMLテーマに独自のCSS/JSファイルを読み込ませてデザイン調整等したい

# HTMLテーマに独自のCSS/JSファイルを読み込ませてデザイン調整等したい
# http://sphinx-users.jp/reverse-dict/html/custom-css-js.html
def setup(app):
    app.add_stylesheet('custom.css')
]]>
Wed, 03 May 2017 00:00:00 +0900
https://www.mindto01s.com/2017/04/30/c7eef555_974d_42d3_8112_6049ef29dbce.html https://www.mindto01s.com/2017/04/30/c7eef555_974d_42d3_8112_6049ef29dbce.html <![CDATA[落穂拾い 2017/04/30]]> 落穂拾い 2017/04/30

Apple Developer Center

リリースノート

]]>
Sun, 30 Apr 2017 00:00:00 +0900
https://www.mindto01s.com/2017/04/29/7845d5ae_aaa0_4824_be64_bbb75d62f528.html https://www.mindto01s.com/2017/04/29/7845d5ae_aaa0_4824_be64_bbb75d62f528.html <![CDATA[落穂拾い 2017/04/29]]> 落穂拾い 2017/04/29 ]]> Sat, 29 Apr 2017 00:00:00 +0900 https://www.mindto01s.com/2017/04/23/651489ce_94f8_4a38_8351_d2c836866215.html https://www.mindto01s.com/2017/04/23/651489ce_94f8_4a38_8351_d2c836866215.html <![CDATA[キーボードショートカットとメニュー選択を記述する]]> キーボードショートカットとメニュー選択を記述する

ソフトウェアの操作マニュアルやチートシート作成が楽になりそうな記述方法を見つけた。

キーボードショートカットを示す記述と、メニュー選択を示す記述方法。

キーボードショートカットでは、以下の記述をする。

アプリケーションを終了するキーボードショートカットは :kbd:`Command + Q` です。

表示は以下のようになる。

アプリケーションを終了するキーボードショートカットは Command + Q です。

「Command + Q」の部分に淡い色がつく。同じ文書中に繰り返しこの表記が使われる事で読者は地の文とキーボードショートカットの区別を認識しやすくなる効果がある。

同じく、メニュー選択では、以下の記述をする。

アプリケーションを終了するメニューは :menuselection:`File --> Quit` です。

表示は以下のようになる。

アプリケーションを終了するメニューは File ‣ Quit です。

こちらは、より効果的に見える。「File」と「QUit」が地の文とスタイルが異なる事と単語間の三角形がメニュー操作であることを読者に認識させる効果がある。

さらに、csvテーブルの中でも使用可能。以下のようにcsvの中にそのまま書き込める。

.. csv-table:: チートシート見たいな一覧表
    :widths: 25, 75

    "アプリの終了色々", :kbd:`Command + Q`
    "アプリの終了", :menuselection:`File --> Quit`

出力は以下のようになる。tableの細かなレイアウトはテーマファイルなり、styファイルなりを変更すれば好みの物になりそう。

チートシート見たいな一覧表
アプリの終了色々 Command + Q
アプリの終了 File ‣ Quit

余談ですが、操作マニュアルの巻末にメニューやショートカットの一覧があると便利だと思うんです。 操作を覚えるまでは、一覧表を見ながら操作すると上達が早くなるからです。

]]>
Sun, 23 Apr 2017 00:00:00 +0900
https://www.mindto01s.com/2017/04/22/544c8e0d_2231_4d16_ae17_ae86a3c14a4d.html https://www.mindto01s.com/2017/04/22/544c8e0d_2231_4d16_ae17_ae86a3c14a4d.html <![CDATA[Uncrustifyの設定メモ]]> Uncrustifyの設定メモ

参考までにUncrustifyの設定ファイルを置いときます。

defaultからの変更は、以下の3つ。

  • Objective-Cの最低限の対応
  • bsd/allmanスタイルのindent
  • 変数のアライメントの揃え

( uncrustify.cfg.zip )

]]>
Sat, 22 Apr 2017 00:00:00 +0900
https://www.mindto01s.com/2017/04/20/fcc3ad89_04da_41ae_85fb_910a91b8697b.html https://www.mindto01s.com/2017/04/20/fcc3ad89_04da_41ae_85fb_910a91b8697b.html <![CDATA[DoxygenとXCodeのコメント機能のメモ]]> DoxygenとXCodeのコメント機能のメモ

1行あけて記述すると詳細な説明のコメント部分になる、javadoc形式のautobriefを使うとコメントが描きやすい。

JAVADOC_AUTOBRIEF      = YES

デフォルトだと、プライベートな実装までドキュメント化されてしまうので、ヘッダファイルだけコメント摘出したほうが良い。

FILE_PATTERNS          = *.h

Xcodeでは、主な構文の前で、”command + option + /”のショートカットキーでコメントの雛形が挿入される。

例:class

雛形の挿入をサポートしている。

/**
 クラスの概要

 1行あけてから、クラスの主な機能をかく。
 - 何の動作に責任を持つか?
 - 実行時に協調するクラスはどれか?
  */
@interface DXWBAppDelegate : NSObject <NSApplicationDelegate>

@end

なお、ObjCでは”-“などのmarkDown構文はXCodeのQuickHelpではサポートされていない。doxygenだとサポートされている。

例:method

雛形の挿入をサポートしている。

/**
 引数inRangeで示す文字列範囲の描画座標を矩形NSRectを返す

 @param inRange 指定する文字列の範囲
 @return 文字列の描画範囲

 ここに詳細な説明を入れる。
*/
- (NSRect) rectForCharacterRange:(NSRange)inRange;

例:property

雛形の挿入をサポートしている。

/**
 Textの描画領域とboundsの差の値
 */
@property (readonly) NSSize interSpacing;

空白行に続けて詳細は書ける。しかし、propertyに詳細な説明が必要な場合は設計が間違っている可能性があるので見直したほうが良い。

例:enum

雛形の挿入をサポートしていない。列挙体全体だけでなく、ここの値でも雛形の挿入はサポートされていない。

自前で、コメントをつける必要があるので、以下のコードを参考にする。 “/**”と”///”を使い分けると見た目がキレイになる。

/**
 DXWBAppDelegateに関わるエラー定数。

 ここに詳細な説明を入れる。
 */

enum E_DXWBAppDelegateError
{
    /// 読み込みエラー
    DXWBAppReadInapplicableDocumentTypeError,

    /// 書き出しエラー
    DXWBAppWriteInapplicableDocumentTypeError
};

例:文字列定数

雛形の挿入をサポートしていない。自前で、コメントをつける必要があるので、以下のコードを参考にする。

/**
 NSString定数の宣言
 */
extern NSString* const DXWBAppConstString;

文字列定数に詳細な説明が必要な場合は設計が間違っている可能性があるので見直したほうが良い。

例:マクロ

雛形の挿入をサポートしていない。自前で、コメントをつける必要があるので、以下のコードを参考にする。

/**
 NSApp.delegateへのアクセッサ
 */
#define AppDelegate ((DXWBAppDelegate *)[NSApp delegate])

文字列定数や列挙型をclassと同じグループにまとめる

Objective-Cでは、同じファイルで定義されている文字列定数や列挙型は、同じファイル中のクラスと強く関連づけられています。

それを明示するために、以下のように、”//@{“と”//@}”でくくるとDoxygenでグループ付けしてくれます。

//@{

// マクロ
 .
 .
// クラス定義
 .
 .
// 文字列定数
 .
 .
//@}

( doxygenWorkBench_2.zip )

]]>
Thu, 20 Apr 2017 00:00:00 +0900
https://www.mindto01s.com/2017/04/14/df7c3597_ccb6_450b_a815_e8887b97fd7a.html https://www.mindto01s.com/2017/04/14/df7c3597_ccb6_450b_a815_e8887b97fd7a.html <![CDATA[uncrustifyもういっぱい]]> uncrustifyもういっぱい

はじめに

以前 uncrustifyおかわり で紹介したように、uncrustifyはObjective-Cに対応したソースコードの整形ツールです。 XCodeからuncrustifyを呼び出すuncrustifierというExtensionが公開されています。

今回は、そのuncrustifierの紹介です。

環境

uncrustifyは、macPortsで構築した。

  • macosx 10.12.4
  • xcode 8.3.1
  • MacPorts 2.4.1
  • uncrustify 0.64
  • uncrustifier 1.2

uncrustifierのインストール

ここから https://github.com/yieldmo/uncrustifier/releases バイナリを持ってきて適当な箇所にコピーするのが一番簡単。

ソースコードからビルドする場合は、https://github.com/yieldmo/uncrustifier でコードを持ってくる。

インストール自体は、ビルド後に出来上がったアプリケーションを立ち上げると自動でインストールされる。

uncrustifierの設定

uncrustifierアプリを立ち上げて、スタイルを選ぶ。

../../../_images/uncrustifyIcon.png

Windowが1つだけのアプリでここでスタイルが設定できる。

../../../_images/uncrustifyPrefarenceWindow.png

“システム環境設定” > “機能拡張” > “Xcode Source Editor” でuncrustifyにチェックを入れて有効にする。

../../../_images/uncrustifySystemPref.png

Note

Resources以下のファイルを書き換えると、デフォルトの設定を変更できるとの公式サイトに載っている。 しかし、uncrustifierの設定の設定画面で、PopupMenuから”Custom File…”を選べばappexのリソースを直接変更するような事をしなくても変更できる。ただし、設定ファイルの内容を変更するたびに読み込ませる必要がある。

XCodeからの使い方

メニューからの呼び出し方は、 “Editor” > “uncrustify” > “Format Current Document” で呼び出せる。 編集中のドキュメントを対象に整形する。

ショートカットキーは、各自が適当につける。設定方法は、”XCode” > “Prefarences…” > “Key Bindings”の画面で行う。

“uncrustify”でフィルターすると対象のメニューが出るのでお好みのショートカットをつけると良い。

../../../_images/uncrustifyKeyBindings.png
]]>
Fri, 14 Apr 2017 00:00:00 +0900
https://www.mindto01s.com/2017/04/13/6a7d8a91_b381_4196_b729_04d38acedfed.html https://www.mindto01s.com/2017/04/13/6a7d8a91_b381_4196_b729_04d38acedfed.html <![CDATA[Doxygenの設定メモの続き]]> Doxygenの設定メモの続き

参考にしたサイトでは自動化にsedを使っていたが、私にはsedの文法が分からない。 diffとpatchで対応することにした。

doxygen出力ディレクトリ

出力ディレクトリは、以下を前提としています。

OUTPUT_DIRECTORY       = "build/doxygen"

patchファイル

以下のzipファイルの”doxygen-latex.patch”にパッチファイルを用意した。

( doxygenWorkBench.zip )

コマンド実行

パッチの適応と、pdfの作成。

$ doxygen
$ patch -p0 < doxygen-latex.patch
$ cd build/doxygen/latex
$ make pdf

あとは、適当にソースのビルド時に上記のスクリプトを追加すれば良さそう。

]]>
Thu, 13 Apr 2017 00:00:00 +0900
https://www.mindto01s.com/2017/04/11/023e9cbe_c712_48d3_9667_0d381a06cfb1.html https://www.mindto01s.com/2017/04/11/023e9cbe_c712_48d3_9667_0d381a06cfb1.html <![CDATA[Doxygenの設定メモ]]> Doxygenの設定メモ

今までpdf出力は文字化けがするので諦めていた。 以下のサイトを参考に色々と試したら、文字化けが治ったしフォントも綺麗になった。

残念ながら、今はもう紙の関数仕様書を要求する仕事は無いし、おそらくこれからも無いので単純に趣味の世界です。

で、作業メモ。

環境

環境は、macPortsで構築した。

  • macosx 10.12.4
  • xcode 8.3.1
  • MacPorts 2.4.1
  • TeX Live 2016/MacPorts 2016_4
  • doxygen 1.8.13

構築したと言っても以下の1行を入力しただけ。

$ sudo port install texlive +full

Doxyfileファイルの設定

最低限、以下の3項目を変更すれば良い。

OUTPUT_LANGUAGE = English
LATEX_CMD_NAME  = latex
USE_PDFLATEX    = YES

これらの項目を以下のように変える。

OUTPUT_LANGUAGE = Japanese
LATEX_CMD_NAME  = uplatex
USE_PDFLATEX    = NO

それぞれの意味は、参考にしたサイトに載っているので特にここでは書かない。

ソースコードの位置や出力ファイルの位置も各自に合わせること。

最初の出力

一度、doxygenコマンドを実行して、latexディレクトリを生成する。

生成されたmakeファイルを実行すると文字化けのするpsファイルやpdfファイルしか作成できない。

その壊れたlatexディレクトリ中のmakeファイルを修正する。

修正箇所は3箇所。

1. pdfの生成にはdvipdfmxを使う

PostScriptファイルから生成している箇所をdvipdfmxから生成するように変更するだけです。

以下の箇所を変更します。

refman.pdf: refman.ps
       ps2pdf refman.ps refman.pdf

変更後は以下のようになります。

refman.pdf: refman.dvi
       dvipdfmx refman.dvi

2. pdfの複数のページを1ページにまとめるにはpdfnupを使う

ps2pdfはうまく日本語が通らないようなので、pdfnupを使います。

以下の箇所を変更します。

refman_2on1.pdf: refman_2on1.ps
       ps2pdf refman_2on1.ps refman_2on1.pdf

変更後は以下のようになります。

refman_2on1.pdf: refman.pdf
       pdfnup refman.pdf
       mv refman-nup.pdf refman_2on1.pdf

3. 複数のpdfをまとめたファイルの後片付けをする

おそらく、doxygenの記載漏れ。clean時にrefman_2on1.pdfも含めるだけ。

以下の箇所を変更します。

clean:
       rm -f *.ps *.dvi *.aux *.toc *.idx *.ind *.ilg *.log *.out *.brf *.blg *.bbl refman.pdf

変更後は以下のようになります。

clean:
       rm -f *.ps *.dvi *.aux *.toc *.idx *.ind *.ilg *.log *.out *.brf *.blg *.bbl refman.pdf refman_2on1.pdf

pdfに目次を作る

ここまでの変更を行うとpdfの文字化けは治ります。ここから先は、大抵の人には関係ありません。

印刷時の目次はあります。ここで作る目次は、pdfのbookmarkと呼ばれる機能です。

リンクの色は黒。bookmarkはdvipdfmxに対応させる。

% Hyperlinks (required, but should be loaded last)
\usepackage{ifpdf}
\ifpdf
  \usepackage[pdftex,pagebackref=true]{hyperref}
\else
  \usepackage[ps2pdf,pagebackref=true]{hyperref}
\fi
\hypersetup{%
  colorlinks=true,%
  linkcolor=blue,%
  citecolor=blue,%
  unicode%
}

上記のコードを以下に置き換え。

% Hyperlinks (required, but should be loaded last)
% hyperrefをdvipdffmx用に決め打ち
\usepackage[dvipdfmx,
            pagebackref=true,
            colorlinks=true,
            linkcolor=darkgray,
            urlcolor=darkgray,
            bookmarks
           ]{hyperref}
\usepackage{pxjahyper} % 日本語で'しおり'したい

これで、目次も出来て、リンクも作成された。

見た目を整える

完全に趣味です。

本文を明朝体にして、章立てやラベルはゴシック体に変更する。

% Custom commandsの末尾にでも以下のコードを追加する。

\usepackage[uplatex,deluxe, expert]{otf}

% sectionの書体を"ゴシック体、サンセリフ体、太字"
\allsectionsfont{
\gtfamily\sffamily
\color{darkgray}
}

% labelの書体を"ゴシック体、サンセリフ体、太字"
\renewcommand{\DoxyLabelFont}{
\gtfamily\sffamily
\color{darkgray}
}

\renewcommand{\kanjifamilydefault}{\mcdefault} % 日本語書体を明朝体
\renewcommand{\familydefault}{\rmdefault} % 欧文書体をローマン体

また、細すぎるので表紙のタイトルはゴシック体のボールドにする。begin{titlepage}の次の行に追加。

\begin{titlepage}
\gtfamily\sffamily\bfseries % <-- これを追加

ヒラギノフォントを使っているかも

macPorts の場合以下のコマンドでヒラギノを有効にした記憶があるが定かでは無い。

$ cd /opt/local/share/texmf-texlive/scripts/cjk-gs-integrate
$ sudo perl cjk-gs-integrate.pl --link-texmf --force
$ sudo mktexlsr
$ sudo kanji-config-updmap-sys hiragino-elcapitan-pron

以下のコマンドではヒラギノが埋め込まれているように見える。

$ pdffonts refman.pdf

参考URL

次は、上記のサイトのように、スクリプトで自動化したい。

]]>
Tue, 11 Apr 2017 00:00:00 +0900
https://www.mindto01s.com/2017/03/27/9f85c2fb_9f90_4e1b_8e5e_8719f57900eb.html https://www.mindto01s.com/2017/03/27/9f85c2fb_9f90_4e1b_8e5e_8719f57900eb.html <![CDATA[Cocoaからのライブ変換(LiveConversion)の変更と監視]]> Cocoaからのライブ変換(LiveConversion)の変更と監視

今の所、専用のAPIは見つかっていない。設定ファイルやDistributedNotificationの監視で見つかった事を書く。

  • 変更の監視
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
   [[NSDistributedNotificationCenter defaultCenter]
           addObserver:self
              selector:@selector(jimPreferencesDidChangeNotification:)
                  name:@"com.apple.inputmethod.JIM.PreferencesDidChangeNotification"
               object:nil
    suspensionBehavior:0];
}

- (void) jimPreferencesDidChangeNotification:(NSNotification*)note
{
    NSLog(@"jimPreferencesDidChangeNotification => %@",
          [note.userInfo valueForKey:@"JIMPrefLiveConversionKey"]);
}
  • 現在の設定の確認
NSUserDefaults* theUD = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.inputmethod.Kotoeri"];

NSLog(@"[theUD valueForKey:JIMPrefLiveConversionKey] => %@",
        [theUD valueForKey:@"JIMPrefLiveConversionKey"]);

Warning

ImputMethodのprefarenceを参照しているだけなので、sandbox環境では正常に動作しない。

  • ライブ変換を有効/無効にする

有効

[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.inputmethod.JIM.PreferencesDidChangeNotification"
                                                                  object:@"com.apple.JIMSession"
                                                                userInfo:/*@{@"JIMPrefLiveConversionKey" : @(1)}*/ // <--コメントアウトを外して使用すること
                                                                 options:0];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.inputmethod.JIM.PreferencesDidChangeNotification"
                                                                  object:@"com.apple.JIMPreferences"
                                                                userInfo:/*@{@"JIMPrefLiveConversionKey" : @(1)}*/ // <--コメントアウトを外して使用すること
                                                                 options:0];

無効

[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.inputmethod.JIM.PreferencesDidChangeNotification"
                                                               object:@"com.apple.JIMSession"
                                                             userInfo:/*@{@"JIMPrefLiveConversionKey" : @(0)}*/ // <--コメントアウトを外して使用すること
                                                              options:0];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.inputmethod.JIM.PreferencesDidChangeNotification"
                                                               object:@"com.apple.JIMPreferences"
                                                             userInfo:/*@{@"JIMPrefLiveConversionKey" : @(0)}*/ // <--コメントアウトを外して使用すること
                                                              options:0];

Warning

sandbox環境では、NSDistributedNotificationはuserInfoにデータを渡すことはできない。sandbox環境では動作しない。

以上のコードでライブ変換の監視/変更が出来るが、sandbox環境では正常動作しない。 sandbox環境で動作するのは変更したタイミングの取得だけ。

“HIToolbox/TextInputSources.h”にあるInputSourceの変更が正規のAPIの可能性があると思い、TISCreateInputSourceListで取得できるリストを見たがそれらしきものはなかった。

]]>
Mon, 27 Mar 2017 00:00:00 +0900
https://www.mindto01s.com/2017/03/25/d2270b22_c3c5_45f8_b3fc_2ccc041ed89c.html https://www.mindto01s.com/2017/03/25/d2270b22_c3c5_45f8_b3fc_2ccc041ed89c.html <![CDATA[最小限アウトラインプロセッサの作り方]]> 最小限アウトラインプロセッサの作り方

Cocoa勉強会松戸用の資料。

NSOutlineViewとNSTreeControllerを使って、”Copy&Paste”,”書類の保存と読み込み”,”Drag&Drop”の実装までの最小限アウトラインプロセッサの作り方の資料です。

なお、ソースコードとPDFはここです。

( TreeOutliner_01.zip ) ( 最小限アウトラインプロセッサの作り方.zip )

解説は当日に行います。

]]>
Sat, 25 Mar 2017 00:00:00 +0900
https://www.mindto01s.com/2017/03/16/ba5bee04_2b62_4a29_be18_ad2ffa4706bd.html https://www.mindto01s.com/2017/03/16/ba5bee04_2b62_4a29_be18_ad2ffa4706bd.html <![CDATA[pragma markによるメソッドの分類]]> pragma markによるメソッドの分類

この記事は、pragma markを最初にコピペする事でクラス実装の抜けや漏れを防ぐ方法の紹介です。

コメントと同じく機械側には無くても良いが、プログラマ側に実装すべきメソッドを思い出しやすくします。

最初にpragma mark のリストをコピペする

クラスを作成すると、.hと.mに以下のコードをコピペします。

#pragma mark - class methods

#pragma mark - init methods

#pragma mark - dealloc

#pragma mark - NSCopying, hash, isEqual:

#pragma mark - NSCoding methods

#pragma mark - restorableState methods

#pragma mark - life cycle methods

#pragma mark - action methods

#pragma mark - event handling methods

#pragma mark - drawing methods

#pragma mark - delegate/datasource methods

#pragma mark - accessor methods (in pairs)

#pragma mark - Utility methods

あとは、メソッドを書いてゆくだけです。

メリットは、以下の2点です。

  1. 次に書くべきメソッドを思い出しやすい。
人間は穴埋め問題の様に場所が決まっていると、何をすれば良いかが思い出しやすい。
  1. コードのブラウズ時に検索や移動がやりやすい。
メソッドの分類毎に、”mark -“の文字列があるので、”⌘F mark -“と”⌘G”で分類の間を移動できる。

Note

今まで私は”ハイフン”行と”分類名”行で2行に分けて記述していました。 しかし、先ほど試した結果1行でも問題なくXCodeが動作しました。 ですので、今後は1行で記述するつもりです。

次は、それぞれの分類に何を書いているかの説明です。

pragma mark - class methods

クラスメソッドを書く場所。

[NSDocument autosavesInPlace]などのクラス特有の性質を示すメソッドやインスタントイニシャライザを書く。

例:

+ (BOOL) autosavesInPlace;
+ (NSURL*) fileURLWithPath:(NSString*) path;

pragma mark - init methods

イニシャライザを書く場所。

引数が少ないメソッドを上の方に、引数の多いメソッドをしたの方に書く様に気をつける事。

書くときはすこし手間が増えるが、コードを読むときに流れが上から下になるので分かりやすくなる。

Objective-Cのイニシャライザでは最も引数の多いメソッドが、指定イニシャライザ になるので、必然的に上記の様になる。

例:

- (instancetype) init;
- (instancetype) initFileURLWithPath:(NSString *)path;

pragma mark - dealloc

deallocを書く場所。

各種プロパティをnilに戻す事や、KVOの停止、Notificationの受信の停止など。

例:

- (void) dealloc;

pragma mark - NSCopying, hash, isEqual:

オブジェクトのコピーや同一性に関わるメソッドの実装を書く場所。

MVCで言うところのViewクラスやControllerクラスでは必要ありませんが、逆にmodelクラスに相当するクラスの場合は実装はほぼ必須です。

例:

- (id) copyWithZone:(NSZone *)zone;
- (id) mutableCopyWithZone:(NSZone *)zone;
- (BOOL) isEqual:(id)object;
- (NSUInteger) hash;

pragma mark - NSCoding methods

シリアライズ化メソッドを書く場所。

Cut&PasteやDrag&Drop、ファイル保存の実装にはNSCodingのメソッドを使うと簡単になる。

例:

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;

pragma mark - restorableState methods

状態保存関係のコードを書く場所。

クラスメソッドであるが[NSResponder (NSRestorableState) restorableStateKeyPaths]もここに書く。

例:

+ (NSArray *)restorableStateKeyPaths;
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder;
- (void) restoreStateWithCoder:(NSCoder *)coder;

pragma mark - life cycle methods

オブジェクトのライフサイクルのコードを書く場所。

ViewControllerやWindowControllerのライフサイクルに関するメソッドを書きます。 場合によっては、delegateやnotificationに分類される様なメソッドも書きます。

例:

- (void)awakeFromNib;

// windowControllerの場合
- (void)windowWillLoad;
- (void)windowDidLoad;

// ViewControllerの場合
- (void)viewDidLoad;

- (void)viewWillAppear;
- (void)viewDidAppear;
- (void)viewWillDisappear;
- (void)viewDidDisappear;

// NSApplicationDelegateの場合
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void)applicationWillTerminate:(NSNotification *)aNotification;

pragma mark - action methods

IBActionのメソッドを書く場所。

ユーザーが実行できるコマンドの単位になる、IBAction系のメソッドを書く。

また、enable/disableを制御するためのcanAction系のメソッドも一緒に書いておくと楽。 ライブラリcontrolEnablerを使用すると良い。 validateuserinterfaceitem(7)

例:

- (IBAction) anAction:(id)sender;        // action
- (BOOL)     canAnAction:(id)sender;     // actionを呼出すボタンの有効無効を制御

pragma mark - event handling methods

イベント処理のメソッドをこの場所に書く。

NSResponderのサブクラスでない場合でも、KVOの監視メソッドや、Notificationの受信メソッドをイベント処理と考えて、この場所に書いておくと楽。

例:

// KVOの監視メソッド
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context;

// なんらかのnotificationのレシーバー
- (void) nanikanoNotificationNoReciver:(NSNotification *)aNotification;

// キーダウンイベントを処理したり
- (void)keyDown:(NSEvent *)event;

pragma mark - drawing methods

描画系のメソッドを書く場所。

NSViewのサブクラスしか関係ないが、当然まとめておくと楽。

例:

- (void)drawRect:(NSRect)dirtyRect;

pragma mark - delegate/datasource methods

他の分類には分けられない、delegateメソッドやdatsourceメソッドを書く場所。

例:

// NSSpliterViewのdelegate
- (void)splitViewWillResizeSubviews:(NSNotification *)notification;
- (void)splitViewDidResizeSubviews:(NSNotification *)notification;

pragma mark - accessor methods (in pairs)

アクセッサとKVCに関連するものを書く。

依存関係の記述の”keyPathsForValuesAffectingプロパティ名”クラスメソッドもここに書く。 インデックス付きアクセッサパターンのメソッドもここにまとめます。

例:

+ (NSSet *)keyPathsForValuesAffectingObject
- (id) object
- (void) setObject:(id)inObject

pragma mark - Utility methods

その他のユーティリティメソッドを書く。例は特になし。

参考文献

元ネタは、ヒレガス本かその前後のはずだがググっても出てこない。

]]>
Thu, 16 Mar 2017 00:00:00 +0900
https://www.mindto01s.com/2017/03/15/89d029ab_21d4_4f54_9d75_ad86e7c478e4.html https://www.mindto01s.com/2017/03/15/89d029ab_21d4_4f54_9d75_ad86e7c478e4.html <![CDATA[CoreDataプロジェクトでDocument.xcdatamodelの名前を変更するには]]> CoreDataプロジェクトでDocument.xcdatamodelの名前を変更するには

Document.xcdatamodelの名前を変更するときは注意が必要。

同じディレクトリにあるファイル”.xccurrentversion”の中に、Document.xcdatamodelのファイル名が書き込まれている。

ファイル名を変更する場合は、キー”_XCCurrentVersionName”の値も変更する必要がある。

$ more .xccurrentversion
<?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>_XCCurrentVersionName</key>
<string>Document.xcdatamodel</string>  <--- ここ
</dict>
</plist>

上記のリストの”Document.xcdatamodel”部分をファイル名と一致させる事。

]]>
Wed, 15 Mar 2017 00:00:00 +0900
https://www.mindto01s.com/2017/03/04/50dd7a7a_8e0c_4f7d_a04a_c462fffbc3e7.html https://www.mindto01s.com/2017/03/04/50dd7a7a_8e0c_4f7d_a04a_c462fffbc3e7.html <![CDATA[Cocoaにおける状態の保存と復元その2]]> Cocoaにおける状態の保存と復元その2

以前に「 Cocoaにおける状態の保存と復元 」にて書いた記述が間違っていた。

splitViewの中のviewをrestoreStateWithCoder:のメソッドの中で生成していたがそれはおそらく間違い。

restore stateの機能は、以下の2点を守れば小細工は必要ない。

  • view階層はrestoreStateWithCoder:が呼ばれる前に構築済みである。
  • restore sateに参加させるobjectはwindow内でユニークなidentifierが設定されている。

この2点を守るのは、通常のアプリケーションでは問題にならない。

Cocoaにおける状態の保存と復元 」で書いた様に、NSTabViewのタブやNSSplitViewのsplitが動的に増える場合にのみ問題になる。

この記事での方法上記の2点を守ることでより簡潔に「状態の保存と復元」を実装する。

実装の概要

nibファイルからのWindowの構築が終わり、メソッドrestoreStateWithCoder:が呼ばれる前のタイミングを得る場所は、以下に示す2つのメソッドになる。

- (void)restoreDocumentWindowWithIdentifier:(NSString *)identifier
                                      state:(NSCoder *)state
                          completionHandler:(void (^)(NSWindow *, NSError *))completionHandler;
- (void) makeWindowControllers;

ただし、makeWindowControllersの方は、引数としてNSCoderが渡されないために、動的にViewを構築するための情報が足らない。

よって、restoreDocumentWindowWithIdentifier:〜をオーバーライドすることになる。

通常、restoreDocumentWindowWithIdentifier:〜をオーバーライドする場合は、以下の様なコードになる。

ググっても”通常”のrestoreDocumentWindowWithIdentifierの使用方法はでてこないので私を信じてほしい。

- (void)restoreDocumentWindowWithIdentifier:(NSString *)identifier
                                      state:(NSCoder *)state
                          completionHandler:(void (^)(NSWindow *, NSError *))completionHandler
{
    NSWindowController* theWindowController;

    if( [identifier isEqualToString:@"window_01"] )
    {
        theWindowController = [self makeWindowController_01];
    }
    else if( [identifier isEqualToString:@"window_02"] )
    {
        theWindowController = [self makeWindowController_02];
    }
    .
    .
    .
    else if( [identifier isEqualToString:@"window_nn"] )
    {
        theWindowController = [self makeWindowController_nn];
    }

    completionHandler([theWindowController window], nil);
}

identifierはクラスではない。同じクラスであっても異なる用途であれば異なるidentifierが設定される。主にnibファイルによって設定され、このなるWindowControllerによって制御される。

このメソッドの中での、completionHandlerによって、restoreStateWithCoder:が呼び出される。

引数identifierとcompletionHandlerは使用されるが、引数stateは使用されていない。

引数stateに保存されている値が保存されているのは、[NSWindowController encodeRestorableStateWithCoder:]で保存できる。

状態の保存時に、動的に生成された部分の情報を[NSWindowController encodeRestorableStateWithCoder:]で保存して、[NSDocument restoreDocumentWindowWithIdentifier]で再生すると実現できる。

サンプルコードの実装

状態の保存に関してみるべきメソッドは以下の3つ。

[TVSDocument restoreDocumentWindowWithIdentifier:state:completionHandler:];
[TVSWindowController prebuildForRestorableStateWithWindowCoder:];
[TVSWindowController encodeRestorableStateWithCoder:];

restoreDocumentWindowWithIdentifierと、prebuildForRestorableStateWithWindowCoderは状態の再生時に使用している。

encodeRestorableStateWithCoderは状態の保存時に使用する。

保存再生する値はsplitViewの分割数で、”numOfSplit”と名前をつけている。

restoreDocumentWindowWithIdentifierの関連箇所は以下の様に、WindowzControllerを作成したら、stateを引数にprebuildForRestorableStateWithWindowCoderを呼び出すだけ。

- (void)restoreDocumentWindowWithIdentifier:(NSString *)identifier
                                      state:(NSCoder *)state
                          completionHandler:(void (^)(NSWindow *, NSError *))completionHandler
{
    TVSWindowController* theWindowController;

    theWindowController = (TVSWindowController*)[self makeWindowController];

    // restore state のコードが走り出す前に、View構造を構築する必要がある。
    // そのため、ここで追加のView構造を構築する機会をwindowに与える。
    [theWindowController prebuildForRestorableStateWithWindowCoder:state];

    completionHandler([theWindowController window], nil);
}

引数stateの中に、”numOfSplit”の値が入っている。ので、prebuildForRestorableStateWithWindowCoderではそれを使ってsplitViewへ分割をお願いする。

- (void) prebuildForRestorableStateWithWindowCoder:(NSCoder*)coder
{
    TVSSplitViewController* theSplitViewController = (TVSSplitViewController*) self.contentViewController;

    // restore state のコードが走り出す前に、View構造を構築する必要がある。
    NSInteger theSplitNumber = [[coder decodeObjectForKey:@"numOfSplit"] integerValue];

    [theSplitViewController createPaneWithNumber:theSplitNumber];
}

状態の保存時に関連するのは、encodeRestorableStateWithCoderだけ。restoreStateWithCoderと対になっていないので気持ち悪いが動的なViewの生成に「状態の保存」が対応していないので仕方がない。 コードとしては以下の様になっている。

- (void) encodeRestorableStateWithCoder:(NSCoder *)coder
{
    TVSSplitViewController* theSplitViewController = (TVSSplitViewController*) self.contentViewController;

    [super encodeRestorableStateWithCoder:coder];

    [coder encodeObject:[NSNumber numberWithInteger:theSplitViewController.splitViewItems.count]
              forKey:@"numOfSplit"];

    [coder encodeObject:self.indexes forKey:@"indexes"];
}

@”indexes”は選択範囲の保存に関係する箇所。画面分割に関係するのは@”numOfSplit”の箇所です。

なお、画面の分割後のsplitViewの中のviewの記述は特に工夫を要する箇所はない。

スクロール位置の保存と再生も”contentView.bounds”を保存して再生時には、reflectScrolledClipView:を使って画面をリフレッシュしているくらいです。

その他

前回のCocoa勉強会で話題になった、「選択位置の情報はどのオブジェクトが保持するべきか?」の問題ですが、WindowControllerに持たせることにしました。

これで、モデルデータは共通だが、選択位置が異なるWindowが実現できています。

なお、ソースコードはここです。

( TableViewSpliter_2.zip )

]]>
Sat, 04 Mar 2017 00:00:00 +0900
https://www.mindto01s.com/2017/02/26/e97ab989_f357_44c7_8810_21ce715d9d2b.html https://www.mindto01s.com/2017/02/26/e97ab989_f357_44c7_8810_21ce715d9d2b.html <![CDATA[storyboardの中のNSWindow initialFirstResponderが効かない問題について (2)]]> storyboardの中のNSWindow initialFirstResponderが効かない問題について (2)

表題について、最も簡単な方法は、有効にしたいviewControllerのawakeFromNibの中で、initialFirstResponderの設定をする事です。

awakeFromNibが複数回呼ばれる事にさえ気をつければ最も簡単な解決方法になります。

@interface ViewController : NSViewController

@property (weak) IBOutlet NSTextField* textField;

@end

@implementation ViewController

- (void) awakeFromNib
{
    [super awakeFromNib];

    // 汎用性はないが、もっと簡単な方法
    self.view.window.initialFirstResponder = self.textField;
}

@end

この解決法の問題点は、汎用性がない事です。「storyboardの中のNSWindow initialFirstResponderが効かない」のはnibファイル間でoutletが接続できない事が原因です。

ここでは、このnibファイル間でoutlet接続をする方法を解説します。

nibファイルの読み込み時に何が起こっているのか?

まずは前提条件となる知識として、nibファイルの読み込み時に起こっていることを説明します。

nibファイルの読み込み時には以下のようなことがおこります。

  1. 非カスタムオブジェクトのアンアーカイブ
フラット化されたストリームからNSKeyedArchiverが作成され、そこから初期化メソッドとしてinitWithCoder:が呼ばれ、それぞれのObjectが生成されます。
  1. カスタムオブジェクトのインスタンス化 (サロゲートObject) cutumViewなど
カスタムViewなどのdecodeされたわけではないviewが、初期化メソッドinitWithFrameが呼ばれて、
  1. オブジェクト間のコネクション関連の設定やRuntimeAttibuteの設定
IBOutletConnectionのサブクラスにestablishConectionメッセージが呼ばれ、コネクションを確立します。
  1. nibから再生された全てのオブジェクトにawakeFromNibを呼び出し
objectの生成もコネクションの確立も完了したのちに、awakeFromNibを呼び出してそれぞれのオブジェクトに追加の初期化処理への機会を与えます。

正確ではないかもしれませんが、以上のような手順でnibから読み込まれます。

Note

なお、ストーリーボードが複数のnibに分割される場合には、3と4の間でまた別のnibを読み込むことがあります。そして、この分割によってoutletが接続できないのです。困ったね。

nibファイル間のoutlet接続の実現のアイデア

outlet接続では、objectを直接outletの変数接続している。 これはアーカイブしたときには、objectのハッシュ値になるわけだが、アンアーカイブされたときにobjectのポインター値へ変換される。

同じ、接続(IBConnectors)プロトコルをサポートしているものでも、bindingの様に、ポインターとパスの組み合わせでobjectを指定するものがある。

今回は、このbinding接続の様にパスを使うことで、目的のobjectへ到達できる様にすることを目標にクラスを作成してた。

ただし、現状のIBOutletConnectionのサブクラスを作成してもXCode場では使用できないので、NSObjectのサブクラスを作成することにした。

実際の接続作業は、nibファイル中のobjectが全てインスタンス化した後のawakeFromNibメソッドの中で行う。

要約すると、

  • 起点とするrootとなるobjectへのポインターを設定
  • 起点から接続先を示せるキーパス文字列を設定
  • Outletが設定されているobjectはポインターで設定
  • Outletの変数名は、キー名で設定
  • 上記の値を元に、awakeFromNibでコネクションを確立する。
  • IBOutletConnectionをサブクラス化しない理由は、XCode場で編集できないから

クラスの実装

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

@interface MTLOutletProxy : NSObject

// オブジェクトoutletObjectのアウトレット名outletNameにポインター値を入れる。
@property (weak) IBOutlet      id        outletObject;
@property (copy) IBInspectable NSString* outletName;

// そのポインター値は、objectRootを起点として、キーパスobjectPathで示されるオブジェクトのポインター
@property (weak) IBOutlet      id        objectRoot;
@property (copy) IBInspectable NSString* objectPath;

@end

実装はこれだけ。

@implementation MTLOutletProxy

- (void) awakeFromNib
{
    [self.outletObject setValue:[self.objectRoot valueForKeyPath:self.objectPath]
                     forKeyPath:self.outletName];
}

@end

以上の様に非常にシンプルなクラスになる。

クラスの使用方法

「storyboardの中のNSWindow initialFirstResponderが効かない」の問題を、このクラスを用いて解決する。

  1. ここにMTLOutletProxyを置く
NSObjectを配置して、class名をMTLOutletProxyに変更する。
../../../_images/MTLOutletProxy_1.png
  1. viewControllerにキーパスでタグれる様にプロパティを作っておき、outlet接続しておく。
コードは以下の通りですが、普通はイニシャルレスポンダーにしたいobjectへはすでにoutletがあるはず。
@interface ViewController : NSViewController

@property (weak) IBOutlet NSTextField* textField_1;
@property (weak) IBOutlet NSTextField* textField_2;
@property (weak) IBOutlet NSTextField* textField_3;
@property (weak) IBOutlet NSTextField* textField_4;//<--今回はこれをイニシャルファーストレスポンダーにする

@end
  1. Windowと接続
MTLOutletProxyのoutletObjectアウトレットに、NSWindowを指定する。
../../../_images/MTLOutletProxy_2.png
  1. 起点としてWindowControllerと接続
MTLOutletProxyのobjectRootアウトレットに、NSWindowControllerを指定する。 キーパスの起点になります。
../../../_images/MTLOutletProxy_3.png
  1. Windowの中のinitialFirstResponderプロパティを指定するために文字列を設定
outlet nameにinitialFirstResponderを入力
../../../_images/MTLOutletProxy_4.png
  1. WindowControllerからキーパス文字列を設定して、最初にフォーカスが当たるObjectを指定
object pathにcontentViewController.textField_4を入力
../../../_images/MTLOutletProxy_5.png

実行時に起こること

MTLOutletProxyの以下のメソッドが呼び出される。

- (void) awakeFromNib
{
    [self.outletObject setValue:[self.objectRoot valueForKeyPath:self.objectPath]
                     forKeyPath:self.outletName];
}

先ほどの各種設定をして入れば、

window.initialFirstResponder = windowController.contentViewController.textField_4;

を実行しているのと同じになる。 このawakeFromNibの実行時点ではwindowは表示される前なのでinitialFirstResponderの値は有効になり、 textField_4がfirstResponderになる。

ただし、その後”Restore state”の機能が実行されて、2回目以降は前回のfirestResponderへフォーカスが移動してしまう。

Note

デバッグ時にRestore state機能をOffにするには、”Edit scheme…” > “Run” > “Options” > “Persistant State”のチェックボックスをONにする。 これを行わないと、最後にWindowを閉じた時のfirstResponderにフォーカスが当たってしまう。

以上で終わりです。じゃあね。

]]>
Sun, 26 Feb 2017 00:00:00 +0900
https://www.mindto01s.com/2017/02/25/86f9c0f7_0b0d_4d3b_b670_5868f583b034.html https://www.mindto01s.com/2017/02/25/86f9c0f7_0b0d_4d3b_b670_5868f583b034.html <![CDATA[storyboardの中のNSWindow initialFirstResponderが効かない問題について (1)]]> storyboardの中のNSWindow initialFirstResponderが効かない問題について (1)

MOSA.swiftでMTLProxyResponderを使えば出来ると嘘をついてしまったのでお詫び。

ソースコードだけ。 ( InitalFirstResponderWB.zip )

サンプルコードの動作は、初回起動時だけ、ViewControllerに結び付けられたtextField_4が最初にアクティブになります。

二回め以降は”restore state”が効くので最後に終了した時のfirstResponderになります。

仕組みの解説や応用は、眠いので明日書きます。

じゃあね。

]]>
Sat, 25 Feb 2017 00:00:00 +0900
https://www.mindto01s.com/2017/02/21/f42c08e5_31e7_462d_95db_a43283dae235.html https://www.mindto01s.com/2017/02/21/f42c08e5_31e7_462d_95db_a43283dae235.html <![CDATA[NSTreeControllerで使用するObjectの変換についてのメモ]]> NSTreeControllerで使用するObjectの変換についてのメモ

NSTreeControllerでnodeを示すのに使用されるObject

NSTreeNode
NSTreeControllerで木構造のノードを管理しやすくするためのクラス。このクラスのインスタンスから、自分自身のNSIndexPathや、NSManagedObjectへの参照を得ることができる。 親ノード、小ノードを手繰ることが出来る。
NSIndexPath
ノードの位置を数字の列で指定するクラス。各ノードは、親のオブジェクトのNSArrayに入っている。 この、NSArrayのindexの値を複数持つことで、fileSystemのパスのように目的のノードの位置を指定することができる。 2/22追記:選択範囲を”状態の保存”で保存する場合は、このindexPathを使用するのが望ましい。NSTreeNodeやNSManagedObjectは保存ファイルやポインターに依存しているので、識別子の変換作業が追加で必要になる。
NSManagedObject
NSTreeControllerが参照するモデルとしてCoraDateを使用する場合に、ノードのデータ本体としてこのクラスのサブクラスが使用される。NSTreeNodeのrepresentedObjectに設定される。
row
NSTreeControllerがNSTableViewやNSOutlineViewに結び付けられている時に、表示中の0から始まる行番号を示す。表示上の値であるため、兄弟ノードの子ノードが表示の為に開閉されると、子ノード分増減する。

上記のObjectの相互変換

NSTreeNodeから各種Objectへの変換

もっとも簡単に変換できる。気をつけることは、rowはviewにしか存在しない概念なので、NSOutlineViewで変換するしかないことぐらい。

方法
NSIndexPath NSTreeNode.indexPath
NSManagedObject NSTreeNode.representedObject
row [NSOutlineView rowForItem:NSTreeNode]

NSIndexPathから各種Objectへの変換

一度NSTreeNodeに変換してからそれぞれの型に変換する。

方法
NSTreeNode [NSTreeController.arrangedObjects descendantNodeAtIndexPath:NSIndexPath]
NSManagedObject [NSTreeController.arrangedObjects descendantNodeAtIndexPath:NSIndexPath].representedObject
row [NSOutlineView rowForItem:[NSTreeController.arrangedObjects descendantNodeAtIndexPath:NSIndexPath]]

NSManagedObjectから各種Objectへの変換

NSTreeController.arrangedObjectsを使って、総なめする以外には方法はない。

コーディングするときは、NSManagedObjectは中間表現としては使ってはならない。NSIndexPathかNSTreeNodeを使用すること。

rowから各種Objectへの変換

rowはviewにしか存在しない概念なので、NSOutlineViewでNSTreeNodeへ変換する。 その後、それぞれの型に変換する。

方法
NSIndexPath [NSOutlineView itemAtRow:row].indexPath
NSTreeNode [NSOutlineView itemAtRow:row]
NSManagedObject [NSOutlineView itemAtRow:row].representedObject

なお、キャストなどは省略した。上記のコードはコピペでは動かない。

]]>
Tue, 21 Feb 2017 00:00:00 +0900
https://www.mindto01s.com/2017/02/06/3b9c5737_9a48_4ba4_99af_367304746ea9.html https://www.mindto01s.com/2017/02/06/3b9c5737_9a48_4ba4_99af_367304746ea9.html <![CDATA[sphinxのadmonition用にcssを書いたの続き]]> sphinxのadmonition用にcssを書いたの続き

前回のアイコンはサイズも適当に作成していた。画像の余白を削り、サイズも統一した。 さらに、CSSを変更して、タイトル部分をboldにして、それぞれの要素のマージンを合わせた。

Caution

caution用のディレクティブはこう。old mac風のアイコンで区別できるようにしてみてます。なかなかにそれっぽくできた。

Danger

危険(?)のディレクティブをかくとこんな感じ。前回は爆弾マークだったのだが、テロ推奨マークのようであまりいい感じがしなかったので、ドクロマークに変更した。

Note

ノート用のディレクティブはこう。この”Note”や”Danger”の文字を消すには、コラムディレクティブ(http://sphinx-users.jp/cookbook/columndirective/index.html)を使う必要があるみたい。CSSのfloat属性とPaddingをうまく使って、”Note”や”Danger”の文字をアイコンの下へつけるようにした。

Tip

tipのディレクティブをかくとこんな感じ。本文の上辺のpaddingもアイコンの上辺と合わせたつもり。

Warning

警告用のディレクティブをかくとこんな感じ。種類によって背景色も変えた方が良いかもと思って色々と試した。が、色をつけると以外に下品になる。色についてはAppleHelpFileを作るときにもう少し考えてから行うことにする。

cssの実装方法は、htmlのソースとcssのソースを見ればすぐにわかると思うので特に記述しない。

]]>
Mon, 06 Feb 2017 00:00:00 +0900
https://www.mindto01s.com/2017/01/31/122c245d_2507_4d2b_a09e_a534e0dd7478.html https://www.mindto01s.com/2017/01/31/122c245d_2507_4d2b_a09e_a534e0dd7478.html <![CDATA[sphinxのadmonition用にcssを書いた]]> sphinxのadmonition用にcssを書いた

適当にアイコンを作り、コラム風に囲ってみた。

../../../_images/cssBeforeAfter.png

cssの実装方法は、htmlのソースとcssのソースを見ればすぐにわかると思うので特に記述しない。

(注意) CSSを変更して、再ビルドしたため上記は、画像ファイルに差し替えた。

]]>
Tue, 31 Jan 2017 00:00:00 +0900
https://www.mindto01s.com/2017/01/25/719795ce_bd54_43b7_945a_f8b9fd1937d5.html https://www.mindto01s.com/2017/01/25/719795ce_bd54_43b7_945a_f8b9fd1937d5.html <![CDATA[NSAtomicStoreサブクラスの作成のメモ]]> NSAtomicStoreサブクラスの作成のメモ

CoreDataがサポートしてい保存形式

CoreDataはデータ保存形式として予め以下の4つの形式をサポートしている。

  指定文字列 description
SQLite NSSQLiteStoreType SQLiteに保存する
バイナリ NSBinaryStoreType バイナリ形式でfileに保存する
XML NSXMLStoreType XML形式でfileに保存する。iOSにはない。
in Mmemory NSInMemoryStoreType メモリだけ使用し保存しない。

これらのStoreTypeを目的に応じて使い分けることができる。そして目的に合わない場合に、自作することも可能である。

クラス階層

ここでは、NSBinaryStoreTypeやNSXMLStoreTypeと同じatomicな読み書きを行い、独自のファイ形式をサポートするStoreタイプの作成方法を説明する。

まずは、既存のStoreTypeがどのクラス階層に属しているかを示す。 残念ながら、既存のStoreTypeのクラスはドキュメント化されていないので指定用文字列をクラス名の代わりに使用している。

../../../_images/class.png

クラスNSPersistentStoreは、CoreDataで使用する永続化方式の抽象クラスです。このクラスのサブクラスを書くことで、CoreDataのデータを任意の媒体に保存できるようになる。 デザインパターンで言うところの、テンプレートパタンやストラテジーパタンで設計されている。

NSIncrementalStoreは、部分的に保存することができるStoreの抽象クラスです。既存のクラスではNSSQLiteStoreTypeがこのクラスを継承している。他のデータベースやNoSQL的なものをサポートしたい場合は、このクラスを継承して作ることになる。

NSAtomicStoreは、データを保存するときに一括で保存する必要があるStoreの抽象クラスです。既存のクラスではNSBinaryStoreTypeとNSXMLStoreTypeがこのクラスを継承している。

このNSAtomicStoreのサブクラスを作成することで、CoreDataの保存形式に独自のファイルフォーマットを導入する。

実行時のオブジェクトグラフとNSAtomicStoreCacheNode

NSAtomicStoreのサブクラスの内部では、NSManagedObjectのデータをNSAtomicStoreCacheNodeに対1で転写して使用する。 NSAtomicStoreCacheNodeはサブクラス化しなくとも問題なく使える。

NSAtomicStoreCacheNodeとNSManagedObjectとNSAtomicStoreの関係は、以下の3点だけ覚えておけば問題ないはず。

  1. NSAtomicStoreCacheNodeはNSAtomicStoreに所有される。
  2. NSAtomicStoreCacheNodeはNSManagedObjectと1:1で関係する。
  3. NSAtomicStoreCacheNodeを区別する識別子を割り当てる。

図で示すと、以下のようになる。

../../../_images/objectGraph1.png

NSAtomicStoreをサブクラス化するには

NSAtomicStoreをサブクラス化するにあたっての、上書きすべきメソッドは大別して2つのグループに分かれる。

メタデータの管理系のメソッド

- (id)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator*)coordinator
                       configurationName:(NSString*)configurationName
                                     URL:(NSURL*)anUrl
                                 options:(NSDictionary *)options

+ (NSDictionary *)metadataForPersistentStoreWithURL:(NSURL*) url
                                              error:(NSError*__autoreleasing*) error

+ (BOOL)        setMetadata:(NSDictionary*) metadata
  forPersistentStoreWithURL:(NSURL*) url
                      error:(NSError*__autoreleasing*) error

- (NSString*)type
- (NSString*)identifier
  • initWithPersistentStoreCoordinator…は、初期化メソッド。ここで、メタデータを読み込むか初期化する必要がある。
  • metadataForPersistentStoreWithURL…は、メタデータの読み込みメソッド。指定されたURLからメタデータを読み込む。
  • setMetadata…は、メタデータの書き込みメソッド。指定されたURLからメタデータを書き込む。
  • typeは、クラスの識別子を返します。この識別子によって、CoreDataは新たに作成したNSAtomicStoreのサブクラスを認識できます。[NSPersistentStoreCoordinator registerStoreClass:forStoreType:]に渡す文字列。NSSQLiteStoreTypeやNSXMLStoreTypeと同じ目的の文字列。
  • identifierはインスタンスの識別子です。保存して再生した場合は、また同じ値を返す必要があります。そーでないと識別子として動作しない。

NSAtomicStoreCacheNodeの管理系のメソッド

- (BOOL)load:(NSError*__autoreleasing*) error
- (BOOL)save:(NSError*__autoreleasing*) error

- (NSAtomicStoreCacheNode*)newCacheNodeForManagedObject:(NSManagedObject*) inManagedObject
- (void)updateCacheNode:(NSAtomicStoreCacheNode*)node fromManagedObject:(NSManagedObject*)managedObject
- (id)newReferenceObjectForManagedObject:(NSManagedObject*) managedObject
  • load…はファイルからデータを読み込ませる。
  • save…はファイルへ保存させる。
  • newCacheNodeForManagedObject..は与えられたNSManagedObjectに対応するNSAtomicStoreCacheNodeを作成する
  • updateCacheNode…はnodeへmanagedObjectの内容を反映させる。
  • newReferenceObjectForManagedObjectはNSManagedObjectの識別子を生成する。保存して再生した場合は、また同じ値を返す必要があります。そーでないと識別子として動作しない。

ファイルフォーマットの設計

NSAtomicStoreのサブクラスを作成して、CoreDataを任意のファイルフォーマットに対応させるにはいくつかの条件がある。

  1. ファイルを区別するためのNSStoreUUIDKeyの値の保存が必要。メソッドidentifierがその値を返す必要がある。
  2. レコードに相当するNSManagedObjectを区別するための、リファレンスの保存が必要。
  3. テーブルに相当する値を何らかの方法で識別する必要がある。

ここで、plist形式やxml形式などの汎用ファイルフォーマット形式であれば対応可能。既存のファイルフォーマットに上記の条件を合わせるには何らかの工夫が必要になる。

全てのCoreDateのファイルを保存するのでなければ、それなりに簡単にできる。

ここでは、以前作成したToDoアプリ元にcsv形式での読み書きをサポートさせてみる。

以下のようにした。

  1. 1行目はメタファイル行。metaDataのUUIDの値
  2. 2行目以降は通常のcsvファイル
  3. ToDoアプリのみサポート。テーブル名は保存しない。
  4. 項目は、done, memo, sortOrder, 作成日, uuid(レコードのuuid値)

具体的にはこんな感じ。

#D35CF030-1EC1-4667-9666-70C8247A1010
1, insertCurrent, 2, 2016-09-17 18:15:00 +0000, E899556C-BE1A-42A3-A634-F8411915C05F
0, insertCurrent, 5, 2016-09-17 18:15:00 +0000, 803054FC-C497-4DD7-A078-CF592B994EB4
1, insertCurrent, 3, 2016-09-17 18:15:00 +0000, 569485BA-A1E2-4E5B-88B5-6D5DB2C8AB8F
0, insertCurrent, 1, 2016-09-17 18:15:00 +0000, 65A95220-A09F-4000-9F3C-EC8448D5DCB7
0, addLast, 0, 2016-09-17 18:15:00 +0000, F7C54A12-8CA6-4A4A-9D45-1A92E23922F9
1, insertCurrent, 4, 2016-09-17 18:15:00 +0000, 5CE1C138-32A3-4CE8-9917-84C941011C09

CoreDataへの登録とinfo.plistへの追加

NSAtomicStoreのサブクラスを使用するには、NSPersistentStoreCoordinatorに登録する必要がある。 適当な場所に以下のコードを書いて、登録する。applicationDidFinishLaunchingが良いと思う。

[NSPersistentStoreCoordinator registerStoreClass:[TDLAtomicStore class]
                                    forStoreType:TDLAtomicStoreType];

同じく、NSPersistentDocumentと一緒に使う場合は、info.plsitにDocument typeを追加する。

<key>CFBundleDocumentTypes</key>
               <dict>
                       <key>CFBundleTypeExtensions</key>
                       <array>
                               <string>csv</string>
                       </array>
                       <key>CFBundleTypeMIMETypes</key>
                       <array>
                               <string>text/csv</string>
                       </array>
                       <key>CFBundleTypeName</key>
                       <string>CSV</string>
                       <key>CFBundleTypeRole</key>
                       <string>Editor</string>
                       <key>LSTypeIsPackage</key>
                       <integer>0</integer>
                       <key>NSDocumentClass</key>
                       <string>TDLDocument</string>
                       <key>NSPersistentStoreTypeKey</key>
                       <string>TDLAtomicStoreType</string>
               </dict>

と登録しておけばOK。これで、.csvの拡張子のファイルを開けるようになるし、”save as..”でTDLAtomicStoreTypeの保存方式が使えるようになる。

ソースコードの解説

めんどくさくなってきたので、Cocoa勉強会Matudoで説明します。https://connpass.com/event/46754/

ファイルTDLCSVAtomicStore.h.mにまとまってます。

ソースコードはここ。( ModernToDoList_atomicStore.zip )

]]>
Wed, 25 Jan 2017 00:00:00 +0900
https://www.mindto01s.com/2017/01/10/5049eefd_26a7_4a64_b99d_53dc9dc54464.html https://www.mindto01s.com/2017/01/10/5049eefd_26a7_4a64_b99d_53dc9dc54464.html <![CDATA[cocoaのモデルデータ関連技術のメモ]]> cocoaのモデルデータ関連技術のメモ

指先を怪我したのでバンドエイドを貼ったら、trackPadが反応してくれない。モチベーションが落ちる。

ローカルへの保存方法

cocoaアプリのファイルの保存形式では大別して以下の4つがある。

  保存形式 大量データ 複雑なデータ その他
手書き 気合い 気合い 気合い  
NSKeyedArchiver 非公開 r/wに時間がかかる 重複処理あり  
CoreData 非公開 一部だけ読み出せる 重複処理あり undo/redoサポート
plist 公開 r/wに時間がかかる 重複処理なし  

この表だけを見ると、お手軽さや機能を考えればCoreData一択。

model-viewの同期方法

Cocoaアプリのモデルとviewの同期方法には主に以下の4つがある。

  依存性 code or nib その他
手書き   コード  
cocoa binding iOSで使えない nib  
fetchResultController CoreData前提 コード coreDataがiCloudStorageと相性が悪い
KVOとaction   コード  

CoreDataを使う場合は、FetchResultControllerとKVOを使って書くのがよさげ。 CoreDataを使わない場合は、ButtonなどはKVOで書いて、TableViewはデリゲートを使った手書きになりそう。

データの保存と違って、手法の混在できるので、適材適所で行うのが良い。

デバイス間のドキュメントの同期方法

調べている途中で気がついたのですが、CoreData Storage 又はCoreData in iCloudと呼ばれる技術はDeprecatedになりました。 CoreDataを使って、iCloud経由でドキュメントを同期できる夢の技術だったのですが、致命的なバグでもあったのでしょう。

で、デバイス間のドキュメント同期技術は以下の2つです。

  1. iCloud document storage

file単位に基づく同期。DropBoxのApple版といった所。PowerPointやWordのようなタイプの書類ベースのアプリとは相性が良いかもしれない。

変更部分だけのマージが出来ないので、巨大なバイナルファイルを丸ごと入れ替えになる。

  1. CloudKit storage

クラウドにデータを置くことが主眼。グラウドにあるデータが主となる。JSからの呼び出しをサポートしているので、webアプリを作る場合は必須。

ネットに繋がっていないと使えない欠点がある。FileMakerやEverNoteのようなパーソナルデータベース型のアプリとは相性が良いかもしれない。

ローカルキャッシュをCoreDataに、ネットに繋がっている時にCloudKitで同期を行うと、上記の欠点を解消できる。

デバイス間の操作状態同期方法

同じユーザーアカウントの別デバイスに操作状態を引き継げる。

NSUserActivityを設定するらしい。使ったことないけど。

https://developer.apple.com/jp/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html

NSOrderedSetの感想

NSOrderedSetが使えそうで使えない。

CoreDataでは、NSOrderedSetをサポートしている。しかし、NSArrayControllerがOrdersSetに対応していない。

bindingでNSTablbeViewの中に順列を使うには、NSSet & NSSortOrderの組み合わせで表現する必要がある。

bindingでなくNSFetchResultControllerを使用しても良い。

CoreData中のNSOrderedSetはCoreDataの思想とあまり相性が良くないのかもしれない。

bindingでのサポートだけでなく、CoreDataInCloudでもサポートが無かった。

地雷を踏み抜かないように、SortOrderを使った昔ながらの手法を使用した方が良い。

]]>
Tue, 10 Jan 2017 00:00:00 +0900
https://www.mindto01s.com/2016/12/05/1c867727_6b64_4dff_91e8_878d8c9840c6.html https://www.mindto01s.com/2016/12/05/1c867727_6b64_4dff_91e8_878d8c9840c6.html <![CDATA[MacOSX用アプリ”CardBook.app”をビルドする]]> MacOSX用アプリ”CardBook.app”をビルドする

ノートテーカー系のアプリを作成しようと思い、参考になりそうなソースコードを漁っていると良さげなアプリをまた見つけた。

CardBook.app http://www.paullynch.org/macosx/cardbook/ https://github.com/pauldlynch/CardBook

細かい実装を丁寧にこなしている印象なメモアプリ。

../../../_images/CardBookSS01.png

修正

Copy&PasteやDrag&Drop時に例外を吐いたの少しだけ修正しました。 原因はNSArchiverの書き出しが現在のMacOSXではサポートされていないため。対応として、NSArchiverをNSKeyedArchiverに、NSUnarchiverをNSKeyedUnarchiverに書き換える以外は何もしていない。

修正箇所はファイルMyDocument.mの以下の2つのメソッドだけ。

[MyDocument copyRows:toPasteboard:]

- (void)copyRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard {
   NSArray *array = [self cardsForRows:rows];
   // NSArchiverをNSKeyedArchiverに置き換え
   NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];// <-- ここ
   NSMutableAttributedString *aString = [[NSMutableAttributedString alloc] initWithString:@""];
   NSEnumerator *e = [array objectEnumerator];
   Card *card;
   while (card = [e nextObject]) {
       [aString appendAttributedString:[card card]];
   }
   [pboard declareTypes:[self pboardTypes] owner:self];
   [pboard setData:data forType:CardPboardType];
   [pboard setString:[aString string] forType:NSStringPboardType];
   [pboard setData:[aString RTFDFromRange:NSMakeRange(0, [aString length]) documentAttributes:nil] forType:NSRTFDPboardType];
   [pboard setData:[aString RTFFromRange:NSMakeRange(0, [aString length]) documentAttributes:nil] forType:NSRTFPboardType];
   [aString release];
}

[MyDocument addCardFromPboard:atRow:]

- (void)addCardFromPboard:(NSPasteboard *)pboard atRow:(NSInteger)row {
   Card *card;
   NSData *data;
   NSString *type;

   type = [pboard availableTypeFromArray:[self pboardTypes]];

   if ([type isEqual:CardPboardType]) {
       NSEnumerator *e;
       NSArray *cardArray;
       data = [pboard dataForType:CardPboardType];
       // NSUnarchiverをNSKeyedUnarchiverに置き換え
       cardArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];//<--ここ
       e = [cardArray objectEnumerator];
       while (card = [e nextObject])
           [self addCard:card atRow:row];
   }
   if ([type isEqual:NSRTFDPboardType]) {
       data = [pboard dataForType:NSRTFDPboardType];
       card = [Card card];
       [card setCardRTFD:data];
       [self addCard:card atRow:row];
   }
   if ([type isEqual:NSRTFPboardType]) {
       data = [pboard dataForType:NSRTFPboardType];
       card = [Card card];
       [card setCardRTF:data];
       [self addCard:card atRow:row];
   }
   if ([type isEqual:NSStringPboardType]) {
       NSString *pboardString = [pboard stringForType:NSStringPboardType];
       card = [Card card];
       [card setCardString:pboardString];
       [self addCard:card atRow:row];
   }
}

修正したソースコードはここ。( CardBook-master.zip )

ソースコードの特徴

作成された年代的に以下の技術は使用されていない。

  • cocoa binding
  • core data
  • restore state
  • プロパティ
  • ARC
  • NSViewController

素直に、コードで書かれている。window位置が再生されるのは、保存ファイルに位置を書き込んでいるから。 データの保存は、伝統的なNSKeyedArchiverを使用している。

印刷時にはCardsViewクラスを使ってA4の紙に複数のカードを纏めて印刷可能にしている。最近印刷を前提としていないアプリばかりなので、これは時代的なものなのかもしれない。

他にも、AppleScript用のリソースやローカライズリソースなども存在しており、丁寧に作られているように見える。

UIの特徴

NSTableViewとNSTextEditViewの組み合わせで特に特徴的なものはない。

しかし、Wiki文法やmarkDownを使うことなく、RTFDな修飾を直接使う方がフツーに便利かもと再考させられた。

カード間リンクはない。 “フォーカス”機能で一部のカードだけをリストアップする機能がある。

標準のメモアプリの使用感が一番近いかもしれない。

]]>
Mon, 05 Dec 2016 00:00:00 +0900
https://www.mindto01s.com/2016/12/03/08ff3e34_982f_4a25_bc6a_7f8e2a912627.html https://www.mindto01s.com/2016/12/03/08ff3e34_982f_4a25_bc6a_7f8e2a912627.html <![CDATA[GNUstep用アプリ”MyWiki.app”をビルドする]]> GNUstep用アプリ”MyWiki.app”をビルドする

ノートテーカー系のアプリを作成しようと思い、参考になりそうなソースコードを漁っていると良さげなアプリを見つけた。

MyWiki.app http://wiki.gnustep.org/index.php/MyWiki.app

Objective-Cで書かれた、サーバーなしのDesktop用のwikiアプリ。

../../../_images/MyWikiSS01.png

残念ながら、十年以上前のコードなのでそのままではビルドが出来ないので、手直ししてビルド。 修正したソースコードはここ。( MyWiki_test.zip )

カレンダーViewとページ作成日を利用した日誌ページの作成機能が面白い。作成日でまとめるために自動で日誌が出来上がる。

../../../_images/MyWikiSS03.png

コードを読んでいて面白いのは、NSTextViewの本文中にコラムを作る手法が良くできている。 NSTextViewにNSTextAttachmentを使って、アイコンと文字列を表示させている。

../../../_images/MyWikiSS02.png

自作ノートテーカーアプリには、文書中に”注意”や”意見”を入れたいと思っていたのでこの機能はパクりたい。

ユーザーとしての使い方は文字列をnoteタグで囲むだけでコラムができるのはお手軽で良い。

問題はDrag&Dropやコピペがうまくゆかない。NSTextAttachmentをコピーするときに、NSTextAttachmentの内容を書き出せば可能かもしれない。

]]>
Sat, 03 Dec 2016 00:00:00 +0900
https://www.mindto01s.com/2016/11/23/eba02d27_e903_4091_953f_f812d6b72148.html https://www.mindto01s.com/2016/11/23/eba02d27_e903_4091_953f_f812d6b72148.html <![CDATA[PlantUMLのsalt言語]]> PlantUMLのsalt言語

以前、オブジェクトグラフを書くのにdot言語を使用した( xibファイルの中のオブジェクト図 )。

この時に「テキストデータからGUI設計用の図を生成するDSLはないものか?」と思っていた。 ググると意外な場所で見つかった。

有名なPlantUMLの中のsalt言語がそれ。(http://plantuml.com/salt)

@startuml
salt
{+
.   |<b>CurrencyConverter|*
--- | ---          | ---
.|{
.   | Doller:  | "Input"    |*
.   | rate:    | "input"    |*
.   | Yen:     |  output    |*
.   | [Reset]  |  [Convert] |*
}|*
}
@enduml

テキストファイル、mainWindow.puに以上のように書いて、

$ java -jar path/to/plantuml.jar -p -tpng < mainWindow.pu > mainWindow.png

と実行すると、

../../../_images/mainWindow.png

こんな画像が得られる。

メニューバーなども作ったり、makefileなども作って可能性を探った。 資料はここ。( plantUML-salt.zip )

結論:webアプリケーション用に設計されたようで、私の用途には表現が弱すぎる。スクリーンショットか手書きの方が良さげ。

]]>
Wed, 23 Nov 2016 00:00:00 +0900
https://www.mindto01s.com/2016/11/19/b458a186_3b0e_4a72_b2bc_fdd3f5fd07d2.html https://www.mindto01s.com/2016/11/19/b458a186_3b0e_4a72_b2bc_fdd3f5fd07d2.html <![CDATA[シェアウェア、やって良い事、悪い事]]> シェアウェア、やって良い事、悪い事

11/19のCocoa勉強会で発表予定の資料。

十年以上前の以下の記事を元に現在のアプリビジネスとプログラム開発を話します。

上記の勝手翻訳資料は会場にてお渡しします。

]]>
Sat, 19 Nov 2016 00:00:00 +0900
https://www.mindto01s.com/2016/11/17/92ee8066_e6b0_43e2_a719_cff157575406.html https://www.mindto01s.com/2016/11/17/92ee8066_e6b0_43e2_a719_cff157575406.html <![CDATA[ProxyResponder nibファイルを超えたTarget/action]]> ProxyResponder nibファイルを超えたTarget/action

11/14のCocoa勉強会で発表予定だった資料。 nibの外にあるObjectにactionを送り込む手法の説明。

代理オブジェクトをnib上に作成して、メソッドforwardInvocation:を利用して外部のobjectへメソッドを転送しています。

資料はここ。( ProxyResponder.zip )

]]>
Thu, 17 Nov 2016 00:00:00 +0900
https://www.mindto01s.com/2016/09/10/817150a8_cae1_4fd0_80b6_f8e3d82337f1.html https://www.mindto01s.com/2016/09/10/817150a8_cae1_4fd0_80b6_f8e3d82337f1.html <![CDATA[NSTableViewのUIを改良する、その2]]> NSTableViewのUIを改良する、その2

一歩後退して、NSTextField周りを調べたのでメモる。

NSTextFieldの描画領域のマージン

NSTextFieldの描画領域boundsと[NSAttributedString boundingRectWithSize:options:context]が返す描画領域は一致しない。 NSAttributedStringの描画領域と一致するのは、[NSCell drawingRectForBounds:]の値の方。

[NSCell drawingRectForBounds:]は、NSControl中のNSCellの描画領域を計算するメソッド。

おそらく、ControlとCellの差は、フォーカスリング分の差と思われる。

テキストの分量に応じて、NSTextFieldの描画サイズを可変にするときに、上記のControlとCellの差を考慮に入れて計算する必要がある。

詳しくはソースコードの[NSTextField interSpacing]と[NSTextField calcBoundingHeightWithFixedCurrentWidth]を参照すること。

NSTextField間の移動をカーソルキーで行う

[NSTextFieldDelegate control:textView: doCommandBySelector:]に記述するのは基本。

左右の矢印キーを押すと、”moveRight:”や”moveLeft:”のセレクタが来る。

この時に、キャレットが文末や文頭にあった場合にnextKeyViewやpreviousKeyViewにフォーカスを移動する。 これだけだと、フォーカスが移動しツァキのNSTextFieldのテキストが、全て選択されてしまう。

以下のように、currentEditorにmoveToEndOfDocument:を投げることで移動先のキャレットを移動すると良い。

NSTextField* theTextFiled = (NSTextField*)control.previousKeyView;

[theTextFiled becomeFirstResponder];
[theTextFiled.currentEditor moveToEndOfDocument:nil];

キャレット位置の判定や移動方法はソースコードを参照のこと。

現状のソースコードの欠点は、キャレットの縦方向移動開始時にX軸の値を保存していないため、NSTextField間の移動時後にキャレットのX軸がズレることがある。

ソースコードはここ。( TextClassTester.zip )

]]>
Sat, 10 Sep 2016 00:00:00 +0900
https://www.mindto01s.com/2016/09/05/1f594d37_521e_42d8_a6ed_5bd95e99df4e.html https://www.mindto01s.com/2016/09/05/1f594d37_521e_42d8_a6ed_5bd95e99df4e.html <![CDATA[ライフゲーム その3]]> ライフゲーム その3

最小限のセル編集機能つきのライフゲームの作成。

方針は、以下の2つ。

  • 今までと同じモデルクラスを使う
  • 表示編集はフレームワーク既存のクラスを組み合わせてでっち上げる。

GUIの見た目はこんな感じになります。

../../../_images/lifeGameGUI.png

GUIパーツの配置と接続

XIBファイルの追加はこんな感じで画面を作ります。

../../../_images/lifeGameIB.png

中央のNSSCrollViewの中身は、NSButtonCellを含んだNSMatrixです。

Nibのoutletの接続は、以下のようになります。

../../../_images/lifeGameObj_outlet.png

Nibのactionの接続は以下のようになります。

../../../_images/lifeGameObj_action.png

ViewControllerだけの図にした方がわかりやすかったかもしれませんが、面倒なので修正はしません。

ViewControllerでモデルクラスと対話させる

各種ボタンが呼び出している、機能は単純にモデルクラスの機能を呼び出しているだけです。

runメソッドを呼び出すと行われる、自動で次の世代へ行くのはNSTimerを使っています。

分かりにくい物は、NSMatrix周りのメソッドです。

nibの読み込み時に、NSMatrix中のCellのprototypeをNSOnOffButtonに変更しています。 これは、見た目はラジオボタンだけと、昨日はOnOffボタンに変更するためです。

- (void)viewDidLoad
{
   [super viewDidLoad];

   NSButtonCell* theCell = self.matrix.prototype;
   [theCell setButtonType:NSOnOffButton];

NSMatrixがクリックされると以下のメソッドが呼ばれます。 マトリクス中のどのNSButtonCellがクリックされたか区別できなかったので(詳しく調べればわかるはず)強引にNSMatrixの値をモデルの値に同期させています。

- (IBAction) toggleCell:(id)sender;

これ以外のメソッドは単純で解説の必要はないと思われます。

ソースコードはここ。( LifeGameOnMac_02.zip )

こんな感じでだいたい終わり。

]]>
Mon, 05 Sep 2016 00:00:00 +0900
https://www.mindto01s.com/2016/09/03/0b5bbf30_6967_4081_a8f5_3bf5cbee2a32.html https://www.mindto01s.com/2016/09/03/0b5bbf30_6967_4081_a8f5_3bf5cbee2a32.html <![CDATA[ライフゲーム その2]]> ライフゲーム その2

先日のコードより後退して、最小限のコードを書いた。

  • 初期状態はコード上で決め打ち。
  • 出力はLog出力の最小限アプリ。

以上の2点しかないのに、テンプレートから作成したので無駄なWindowが出る。

モデルクラスの使い方

モデルクラスは、LGOMModelです。インスタンス化する時に、格子のサイズを指定して作成します。

{
   LGOMSize theSize    = {32, 32};
   self.model = [LGOMModel modelWithSize:theSize];
   .
   .
   .

上記は、32x32の格子サイズを持つライフゲームのモデルを作成します。

初期状態の設定

初期状態の設定用に3つのメソッドを用意しています。

// すべてのセルを非生存状態にします。
- (void) clearAll;

// すべてのセルの生存状態/非生存状態を乱数で設定します
- (void) randomizeAll;

// 文字列でセルの状態を指定します。
- (void) gameBoardSetup:( NSString* _Nonnull )inNewLifeGameBoard;

clearAllとrandomizeAllはコメントの通りです。 gameBoardSetupメソッドに引き渡す文字列は、以下のような形式になります。

 // 8x8の格子でLGOMModelを作成した場合
[model gameBoardSetup:
 @"........\r"
 @"...OOO..\r"
 @"........\r"
 @"........\r"
 @"........\r"
 @"...OOO..\r"
 @"........\r"
 @"........\r"

それぞれの文字の意味を以下の表に示します。

文字 意味
“O” セルが生存状態
“.” セルが非生存状態
“r” 次の行、または終了マーカー

世代の進行

メソッド”next”を呼び出すと、モデルの内容を次の世代に書き換えます。

各セルの値の確認と変更

それぞれのセルの状態を確認変更するメソッドは以下の2つです。

// inPointで示す位置のセルの状態をBOOLで返します。YES:生存 NO:非生存
- (BOOL) aliveAtPoint:(LGOMPoint)inPoint;

// inPointで示す位置のセルの状態をinAliveで指定した値に変更します。
- (void) setAlive:(BOOL)inAlive atPoint:(LGOMPoint)inPoint;

すべてのセルの状態確認

descriptionメソッドで出力するようにしています。

- (NSString*) description

サンプルコードでの使用方法

NSApplicationのdelegateに設定している、LGOMApplicationDelegateの中のawakeFromNibメソッドで使用しています。

- (void) awakeFromNib
{
   LGOMSize theSize    = {32, 32};
   self.model = [LGOMModel modelWithSize:theSize];

   [self.model gameBoardSetup:
    @"................................\r"
    @"...OOO..............O...........\r" // ブリンカー
    @"....................OO..........\r" // ブロック
    @"...........OOOO.................\r" // 蜂の巣
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @".............OOO................\r" // Tテトロミノ
    @"..............O.................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"...O............................\r"// グライダー
    @"....O...........................\r"
    @"..OOO...........................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"
    @"................................\r"];


   NSLog(@"\r%@\r", self.model );
   [self.model  next];

   NSLog(@"\r%@\r", self.model );
   [self.model  next];

   NSLog(@"\r%@\r", self.model );
   [self.model  next];

   NSLog(@"\r%@\r", self.model );
   [self.model  next];

   NSLog(@"\r%@\r", self.model );
   [self.model  next];

   NSLog(@"\r%@\r", self.model );
   [self.model  next];
}

出力結果は以下の通り。

2016-09-04 23:21:11.141 LifeGameOnMac[38776:1497000]
................................
...OOO..............O...........
....................OO..........
...........OOOO.................
................................
................................
................................
................................
................................
.............OOO................
..............O.................
................................
................................
................................
................................
...O............................
....O...........................
..OOO...........................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................

2016-09-04 23:21:11.144 LifeGameOnMac[38776:1497000]
....O...........................
....O...............OO..........
....O.......OO......OO..........
............OO..................
............OO..................
................................
................................
................................
..............O.................
.............OOO................
.............OOO................
................................
................................
................................
................................
................................
..O.O...........................
...OO...........................
...O............................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
以下省略

多分、最も原始的なCocoaアプリでのライフゲイムだと思う。

ソースコードはここ。( LifeGameOnMac_01.zip )

次は、GUIで編集できるコードの解説をします。じゃあね。

]]>
Sat, 03 Sep 2016 00:00:00 +0900
https://www.mindto01s.com/2016/09/02/360fee17_657a_4e8c_86e6_f18ec782c595.html https://www.mindto01s.com/2016/09/02/360fee17_657a_4e8c_86e6_f18ec782c595.html <![CDATA[ライフゲーム その1]]> ライフゲーム その1

Mosa勉強会資料。

眠いのでソースコードだけ。( LifeGameOnMac.zip )

]]>
Fri, 02 Sep 2016 00:00:00 +0900
https://www.mindto01s.com/2016/08/23/08a985d1_360d_4298_9a13_cac5db4501bb.html https://www.mindto01s.com/2016/08/23/08a985d1_360d_4298_9a13_cac5db4501bb.html <![CDATA[NSTableViewのUIを改良する、その1]]> NSTableViewのUIを改良する、その1

Cocoa勉強会資料。入力した文字列の長さに応じて行の高さを調整するコードです。 プロジェクトファイルはここに置いてきます。( TableViewUX.zip )

その2は無いかもしれない。

]]>
Tue, 23 Aug 2016 00:00:00 +0900
https://www.mindto01s.com/2016/08/19/c889785f_70d6_4709_9596_fda46352df40.html https://www.mindto01s.com/2016/08/19/c889785f_70d6_4709_9596_fda46352df40.html <![CDATA[Sphinxで作るApple help book]]> Sphinxで作るApple help book

目的

Sphinxは美しいドキュメントを簡単に作れるようにするマンドラインツールです。 これを転用して、アプリケーションのオンラインマニュアルを作成ができます。

Apple help bookはMacアプリケーションのオンラインマニュアルの形式です。このApple help bookはhtmlで表記する必要があります。

html直書きよりも簡易な表記が可能なドキュメント作成ツールSphinxでオンラインマニュアルを作成すると、ほんのりラクが出来る気がします。

Sphinxの出力形式にApple help book形式が2015年に追加されたましたので、少しだけ調べてみました。

作成手法

最小限の手間で既存のCocoaアプリケーションに、Sphinxで作成したオンラインヘルプを追加する方法を示す。流れは以下の5つの手順。

  1. Sphinx-quick-start
  2. configファイルの変更
  3. XCodeにスクリプトフェーズの追加
  4. apple help book fileをResorceとして追加
  5. info.plistの変更

Sphinx-quick-start

以下のような既存のプロジェクトにSphinxのコードを追加します。

.
├── HalloWorld/
└── HalloWorld.xcodeproj

このディレクトリに、sphinxディレクトリを作成しそこを、カレントディレクトリにします。

.
├── HalloWorld/
├── sphinx/         <--- 今作った。
└── HalloWorld.xcodeproj

sphinx-quickstartコマンドでsphinxのテンプレートファイルを設置します。(私の環境では、2.7系のコマンドしかうまく動かなかったので、2.7系で使用しています)

$ sphinx-quickstart-2.7
Welcome to the Sphinx 1.4.3 quickstart utility.
    .
    .
    .
> Root path for the documentation [.]:
> Separate source and build directories (y/n) [n]: y
> Name prefix for templates and static dir [_]:
> Project name: HalloWorld          <—- 重要
> Author name(s): HalloWorld Author
> Project version: 1.0
> Project release [1.0]: 1.0
> Project language [en]: ja         <—- 重要
> Source file suffix [.rst]:
> Name of your master document (without suffix) [index]:
> Do you want to use the epub builder (y/n) [n]:
> autodoc: automatically insert docstrings from modules (y/n) [n]:
> doctest: automatically test code snippets in doctest blocks (y/n) [n]:
> intersphinx: link between Sphinx documentation of different projects (y/n) [n]:
> todo: write "todo" entries that can be shown or hidden on build (y/n) [n]:
> coverage: checks for documentation coverage (y/n) [n]:
> imgmath: include math, rendered as PNG or SVG images (y/n) [n]:
> mathjax: include math, rendered in the browser by MathJax (y/n) [n]:
> ifconfig: conditional inclusion of content based on config values (y/n) [n]:
> viewcode: include links to the source code of documented Python objects (y/n) [n]:
> githubpages: create .nojekyll file to publish the document on GitHub pages (y/n) [n]:
> Create Makefile? (y/n) [y]:
> Create Windows command file? (y/n) [y]: n   <—- 重要

コマンドを実行すると、上記のように幾つか質問されます。重要点は3点だけです。

重要な項目
設定項目 説明
Project name 生成されるApple help bookのファイル名になります。
Project language ローカライズに使用されます。日本語ならばjaを使用します。
Create Windows command file? 信仰心が試される箇所です。最後まで気を抜かずに、必ずnにしましょう。

コマンドの実行が終了すると、以下のようなディレクトリ構造になります。

.
├── HalloWorld/
├── HalloWorld.xcodeproj
└── sphinx
    ├── Makefile
    ├── build
    └── source

configファイルの変更

sphinx/source/conf.pyを編集します。変更箇所は1箇所です。

# apple help book bundle のCFBundleIdentifierの値の設定。
applehelp_bundle_id = 'com.mindto01s.HalloWorld.help'

上記の1行を適当に追加してください。

この状態で、make applehelpを行うと、apple help fileが作成されます。 ただし、この状態では、アプリケーションバンドルにはコピーされませんし、ヘルプメニューから呼び出すこともできません。

XCodeにスクリプトフェーズの追加

XCodeでビルドした時に、自動でsphinxのmakeファイルを呼び出すために追加します。

../../../_images/appleHelpBook_script.png

追加したコードを以下に示します。

cd "./sphinx/"
PATH=${PATH}:/opt/local/bin
make applehelp

PATHに/opt/local/binを追加しているのは、私がMacPortsを使用しているため。他の方法でsphinxをインストールしてるならば別の場所のPATHを書いてください。

この時点で、XCode上でsphinxもビルド可能になります。

apple help book fileをResorceとして追加

XCode上で一度ビルドすると、”./sphinx/build/applehelp/”に、”HalloWorld.help”というapple help bookバンドルができます。 これを”Copy Bundle Resources”に追加します。

../../../_images/appleHelpBook_copy.png

これで、XCode上でビルドすると、アプリケーションバンドルに含まれるようになります。

info.plistの変更

最後に、info.plistに以下の2項目を追加して、アプリケーションにバンドル内のApple help bookを認識させます。

info.plistに追加する項目
キー 説明
CFBundleHelpBookName com.mindto01s.HalloWorld.help conf.pyで設定したapplehelp_bundle_idの値
CFBundleHelpBookFolder HalloWorld.help AppleHelpBookのフォルダ名
../../../_images/appleHelpBook_infoPlist.png

これで、アプリ実行後に、Helpメニューから”HalloWorld Help”を選ぶと以下のページが表示される。

../../../_images/appleHelpBook_HelpViewer.png

Apple help book作成に関連するSphixの設定

Sphinxのoption項目で、Apple help bookに関連する項目の一覧です。

apple help book bundle用info.plsitの設定

applehelp_bundle_name
Apple Help Bookのベース名です。デフォルトでproject名が使用される。
applehelp_bundle_id
Apple Help BookのバンドルID(必須)。CFBundleIdentifierに反映される。
applehelp_dev_region
Apple Help BookのCFBundleDevelopmentRegionに反映される。デフォルトで’en-us’
applehelp_bundle_version
Apple Help Bookのバージョン文字列。デフォルトで1。CFBundleVersionに反映される。

多分、applehelp_bundle_id以外は設定しないで良いハズ。

その他の設定

applehelp_disable_external_tools
この値がTrueだと、indexerやcodesignなどのコマンドラインツールを呼び出しません。つまり、MacOSX以外でビルドのテストを使用するときに使います。デフォルトで、false。
applehelp_locale
バンドルのResourceフォルダーの中のロケールフォルダー(*.lproj)の生成に使われます。ここが、enならば、en.lprojを生成し、jpnならば、jp.lprojを生成します。デフォルトでlanguageの値。
applehelp_icon
Apple Help Book用の16x16ピクセルのアイコンのファイル名。Apple Helpのリスト表示などの使われます。

必須でないけと設定してiconを作成しないとみっともないので、applehelp_iconは設定した方が良い。その他は設定は不要と思います。

出力されるhtmlファイルの設定

applehelp_title
ヘルプファイルのタイトルの設定。デフォルトでは ‘<project> Help’です。 info.plistのHPDBookTitleの変更と、html中の<title>タグの変更。
apple help bookの出力は、既存のhtml出力のビルダーから継承されているので、html関連のoptionを変更すると変更できます。
このapplehelp_title項目は設定しなくても良いと思います。

indexer用の設定

ヘルプファイルのindexを作成するためのコマンドラインツール用のoptionです。多分使わない。

applehelp_indexer_path
hiutilコマンドへのパスの変更。変更する必要なし。
applehelp_index_anchors
コード上からAppleHelpBookのページを呼び出すための、アンカー用indexを作成するか否か。Trueにする。アンカーの表記方法は知らない。
applehelp_min_term_length
hiutilに渡すminmum term lengthの引数。an とかtheとかをindexに含めないようにするためのもの。変更の必要はないだろう。
applehelp_stopwords
hiutilに渡すstop wordの引数。’about’や’then’などの単語をindexに含めないように、あらかじめstopWordとして登録しておいた単語を指定するためのもの。普通は変更する必要なし。デフォルトの登録内容でOK.

codesignの設定

apple help book バンドルに署名する必要があるときに使用するらしい。使うことがあるとは思えない。

applehelp_codesign_identity
設定されている場合は、この値に設定されているidentityでコードサインされる。デフォルトでは、環境変数CODE_SIGN_IDENTITYに使われている値を使用する。
applehelp_codesign_flags
設定されている場合は、この値のオプションをcodesignコマンドのオプションとして使用する。デフォルトでは環境変数OTHER_CODE_SIGN_FLAGSに使われている値を使用する。
applehelp_codesign_path
コマンドcodesignへのパスを変更するためにあるらしいが、使うことはないハズ。

リモートアクセス系の出力のオプション

Apple Help Bookはネット経由でダウンロード出来たり、知識ベース(サポート掲示板を格好良くいう言葉らしい)へアクセスできるらしいが、使ったことはない。

applehelp_kb_product
ナレッジベースへのURLの基本部分。デフォルトでは、’<project>-<release>’.
applehelp_kb_url
知識ベースへのURL
applehelp_remote_url
リモートコンテンツへのURL

多分、これからも使わないと思います。

参考資料とソースコード

プロジェクトファイルはここに置いてきます。( HalloWorld.zip )

使っている人はここの人ぐらいかも。
https://alastairs-place.net/blog/2015/01/14/apple-help-in-2015/
Apple Help Book自体の解説はこちらを参考にしてください。
https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/ProvidingUserAssitAppleHelp/user_help_concepts/apple_help_concepts.html
Apple Help Bookの構造自体はこちらの解説の方がわかりやすかったです。
http://devzone.touchdude.com/step-step-create-apple-help-your-cocoa-xcode-application
Sphinxの設定ファイルの書き方は本家が一番わかりやすいです。
http://www.sphinx-doc.org/ja/stable/config.html

じゃあね。

]]>
Fri, 19 Aug 2016 00:00:00 +0900
https://www.mindto01s.com/2016/08/05/86e2ae30_dbf3_49cf_afa4_5ee028b5ec84.html https://www.mindto01s.com/2016/08/05/86e2ae30_dbf3_49cf_afa4_5ee028b5ec84.html <![CDATA[画面分割その2]]> 画面分割その2

第2回 MOSA自習室(http://mosa.connpass.com/event/36300/)の発表用資料です。

説明が足らない部分は当日補足します。

画面分割の種類

画面分割(Splitting a window)には2種類あります。

  1. 一つ目は、同一の書類の異なる部分を同時に見るための機能。プレインストールアプリではターミナルアプリのWindowが典型的な画面になります。
../../../_images/terminal_app.png
  1. 二つ目は、iTunesやFinderのサイドバー等のMaster Detail構造のアプリです。
../../../_images/finder_app.png

ここでは、1の物を作ってみる。なお、NSSpliterViewは、おそらく2の物を作るために設計されたようです。2の型の分割を行うには素直にコーディングを行えば問題ありません。

分割の追加&削除ボタンの作成

スクロールバーにあるボタンの作り方だが、以前の「 画面分割その1 」 でやったので説明はパス。

分割の追加の方法

分割自体は簡単です。NSSplitViewControllerの以下のメソッドを呼んでいるだけです。

- (void)insertSplitViewItem:(NSSplitViewItem *)splitViewItem atIndex:(NSInteger)index;

問題は以下の2点です。

  • 分割の追加後に分割されたviewが再レイアウトされて、分割線の位置がずれる。
  • 新規に分割後のスクロール位置が原点を指している。

分割線の追加後に分割位置がずれる問題は、理想的な分割位置を計算して、以下のメソッドで再設定することで回避しています。

- (void)setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex;

理想的な分割位置は、「分割ボタンを押したNSScrollViewを2分割する位置」を理想と考えて計算しています。 このようにすることで、ユーザーは「今このScrollViewが分割された」と認識しやすくなります。

同じく、スクロール位置の調整は、分割されたNSScrollViewのコンテンツが連続する位置に移動しています。

何もしないと、以下のようにスクロール位置が原点を示します。

../../../_images/split_scrollpoint_1.png

調整することで、以下のようにコンテンツが連続する位置に調整しています。

../../../_images/split_scrollpoint_2.png

これも、ユーザーが分割を認識しやすくするために行っています。

分割の削除の方法

分割の削除も簡単です。NSSplitViewControllerの以下のメソッドを呼んでいるだけです。

- (void)removeSplitViewItem:(NSSplitViewItem *)splitViewItem;

問題は以下の2点です。

  • 分割の削除後にNSSplitViewが再レイアウトされて、分割線の位置がずれる。
  • フォーカスがある(firstResponder)の分割部分が削除された場合に、フォーカスが未設定になる。

分割線がずれる問題は、分割の追加と同じ手法で解決しています。 ユーザーが分割したことを認識しやすいように、削除した分割部の1つ上の部分だけが広がったように見せています。

フォーカスの未設定への対処は、削除された分割部の1個上の分割部分へフォーカスを移動しています。

コードでは、[TVSSplitViewController makeFirstResponderAtIndex]を参照してください。

分割線の移動方法

NSSplitViewのサブクラスなので何もしないでも分割線( split bar )は移動できます。

が、デフォルトのままだと、分割線の移動時にコンテンツの中身と一緒に移動してしまいます。

これの何処が問題なのかは説明が難しいので、当日現場で説明します。

理想と現実

ここまで作ってみましたが、より良いユーザーインターフェイスとして以下のようなものがあります。

../../../_images/better_splitView.png

Inside Machintoshで紹介されている例です。 ターミナルアプリにあるような分割の追加&削除ボタンは存在しません。 スクロールバーに分轄用の黒い split bar が付いているだけです。 この split bar をドラッグすると、スクロールバー上を split bar が移動し、マウスを離すとその位置で新たに画面が分轄されます。 分轄部分を削除するには、 split bar をスクロールバーの端っこまで移動すると消えます。

この Inside Machintosh で紹介されている方式の方が、以下の2点で優れている。

  • 画面上のコントロールが少ない。「ボタン2個 + split bar1個」から「split bar1個」に減っている。
  • ユーザの作業が少ない。「分割後にsplit barの移動」から「split barの移動」だけに減っている。

この方式が劣っているのは、Appleの標準のアプリで採用している例が無いので、初見では操作方法が解りにくい事くらいと思われる。

あと、ソースコードはここ TableViewSpliter.zip に置いときます。

次回は、こちらの方式を実装してみたいです。じゃあね。

]]>
Fri, 05 Aug 2016 00:00:00 +0900
https://www.mindto01s.com/2016/07/30/9d0df618_e189_4ff1_b2ac_8fbbc02012fd.html https://www.mindto01s.com/2016/07/30/9d0df618_e189_4ff1_b2ac_8fbbc02012fd.html <![CDATA[Cocoaにおける状態の保存と復元]]> Cocoaにおける状態の保存と復元

単純な例では、カテゴリNSRestorableStateのrestorableStateKeyPathsで保存する変数のパスを返すだけ。 NSViewControllerのサブクラスにでも以下のようなコードを書けば良い。

+ (NSArray *)restorableStateKeyPaths
{
    return [@"color"];// 実際には[]の前に@がつく。コードパーサーがバグってるので@を外した
}

これで、復帰時にプロパティcolorの内容が復元できる。

同じ動作をするものを、少し複雑に書くと。以下のようなコードになる。

@property (atomic, copy) NSColor* color;
.
.
.
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super encodeRestorableStateWithCoder:coder];

    NSData* theData=[NSArchiver archivedDataWithRootObject:self.color];

    [coder encodeObject:theData forKey:@"color"];

}

- (void) restoreStateWithCoder:(NSCoder *)coder
{
    [super restoreStateWithCoder:coder];

    NSData* theData = [coder decodeObjectForKey:@"color"];

    if (theData != nil)
    {
        self.color =(NSColor *)[NSUnarchiver unarchiveObjectWithData:theData];
    }
}

@synthesize color = _color;

- (void) setColor:(NSColor*) inColor
{
    @synchronized(self)
    {
        _color = [inColor copy];
    }

    [self invalidateRestorableState];
}

- (NSColor*) color
{
    @synchronized(self)
    {
        return _color;
    }
}

初期設定Windowやパレットの復帰は、https://www.bignerdranch.com/blog/cocoa-ui-preservation-yall/ のコードが参考になる。

Document-Baseアプリケーションで、1個のドキュメントに複数のウインドウが開けるPhotoShopのようなアプリケーションの場合は以下のようになる。少し違うが、http://mikeabdullah.net/lion-restore-multi-window-document.html も参考になる。

- (void)restoreDocumentWindowWithIdentifier:(NSString *)identifier
                                      state:(NSCoder *)state
                          completionHandler:(void (^)(NSWindow *, NSError *))completionHandler
{
    NSWindowController* theWindowController;

    theWindowController = [self makeWindowController];

    completionHandler([theWindowController window], nil);
}


- (NSWindowController*) makeWindowController
{
    NSStoryboard*       theStoryboard;
    NSWindowController* theWindowController;

    theStoryboard       = [NSStoryboard storyboardWithName:@"Document"
                                                    bundle:nil];

    theWindowController = [theStoryboard instantiateInitialController];

    [self addWindowController:theWindowController];

    return theWindowController;
}

ここまでは、アップルのドキュメントなどの通り。

Windowは動的に増える事を前提にしているので、restoreDocumentWindowWithIdentifier:〜なメソッドが用意されていた。 NSTabViewのタブやNSSplitViewのsplitが動的に増える場合にはどうするか?

これは、基本に戻り、encodeRestorableStateWithCoder:やrestoreStateWithCoder:のメソッドをオーバーライドする事になる。

- (void) encodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super encodeRestorableStateWithCoder:coder];

    NSMutableArray* theArray = [NSMutableArray array];

    for(NSInteger i = 0; i <= [self countOfDivider]; i++)
    {
        CGFloat thePosition = [self positionOfDividerAtIndex:i];

        [theArray addObject:@(thePosition)];
    }

    [coder encodeObject:theArray
                 forKey:@"splitSizes"];
}


- (void) restoreStateWithCoder:(NSCoder *)coder
{
    [super restoreStateWithCoder:coder];

    NSArray* theSplitSizes = [coder decodeObjectForKey:@"splitSizes"];

    // 個数分分割して
    NSInteger theSplitCount = theSplitSizes.count;

    while(theSplitCount)
    {
        [self addLastSplitItem:nil];
        theSplitCount--;
    }

    // 幅を揃える
    for(NSInteger i = 0; i < [self countOfDivider]; i++)
    {
        CGFloat thePosition = [[theSplitSizes objectAtIndex:i] floatValue];

        [self.splitView setPosition:thePosition
                   ofDividerAtIndex:i];
    }
}

- (IBAction) addLastSplitItem:(id)sender
{
    NSStoryboard*     theStoryboard;
    NSViewController* theViewController;
    NSSplitViewItem*  theItem;

    theStoryboard     = [NSStoryboard storyboardWithName:@"MainViewController" bundle:nil];
    theViewController = [theStoryboard instantiateInitialController];
    theItem           = [NSSplitViewItem splitViewItemWithViewController:theViewController];

    [self addSplitViewItem:theItem];

    [self invalidateRestorableState];
}

- (NSInteger) countOfDivider
{
    NSInteger theCount = self.splitViewItems.count;

    return (theCount == 0 ) ? 0 : theCount - 1;
}

- (void)setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex
{
    NSAssert( dividerIndex >= 0, nil);
    NSAssert( dividerIndex < [self countOfDivider], nil);

    [self.splitView setPosition:position
               ofDividerAtIndex:dividerIndex];
}

- (CGFloat)positionOfDividerAtIndex:(NSInteger)dividerIndex
{
    NSAssert( dividerIndex >= 0, nil);
    NSAssert( dividerIndex <= [self countOfDivider], nil);

    CGFloat thePosition = 0;

    for(NSInteger i = 0; i <= dividerIndex; i++)
    {
        NSSplitViewItem* theItem = [self.splitViewItems objectAtIndex:i];
        NSRect theFrame = theItem.viewController.view.frame;

        thePosition += theFrame.size.height;

        // 2個目のitem以降にはdividerThicknessを含める
        if( i != 0)
        {
            thePosition += self.splitView.dividerThickness;
        }
    }

    return thePosition;
}

じゃあね。

]]>
Sat, 30 Jul 2016 00:00:00 +0900
https://www.mindto01s.com/2016/07/24/ca207650_0470_44f4_a03b_ff1eddd87c0e.html https://www.mindto01s.com/2016/07/24/ca207650_0470_44f4_a03b_ff1eddd87c0e.html <![CDATA[キーボードショートカットキーの拡張]]> キーボードショートカットキーの拡張

MacOSの⌘キーを使ったショートカットシステムに、擬似モードの概念を取り入れる事でショートカットキーの機能を拡張する方法を考えてみる。

擬似モードとは、モディファイアキー(⌘とか)を押している時と離している時をそれぞれ別の動作として考えるUIの手法です。詳細はジェフラスキンのswyftyの記事を参考にしてください。

MacOSに以下の条件で擬似モードを追加する方法を考えました。

  1. 追加のハードウエアが不要
  2. 既存のUIの動作を妨害しない
  3. GUIとして学習が容易。

で、実装として以下のような機能にしました。

  1. ⌘キー+英字キーの組み合わせて、擬似モードに入る。
  2. ⌘キーを離したら、擬似モードを抜ける。
  3. 擬似モード中に、英数キーを押すことで、firstResponderへキーに結び付けられたアクションを発行する。
  4. 擬似モードに入ると、Panelを表示する。Panelには使用可能なアクションの一覧が表示される。

リッチテキストの編集機能を試しに実装してみました。

⌘Tを押すと、以下のようなPanelが表示される。

../../../_images/cmdKeyMode.png

⌘キーを押したまま、L,R,C,Jのアルファベットキーを押すと、選択された行のアライメントが変更される。

⌘を離すと、擬似モードを抜けるのでPanelの表示は消える。

../../../_images/cmdKeyMode_2.png

ムービーで動作確認したほうが早いかも( cmdkeymode.mov )

で、これを実装すると以下のような利点と欠点がある。

  1. ショートカットが多段階になるので使えるキーが事実上増える
  2. emacsスタイルのショートカットと比べてGUIの表示がある事と⌘を使うことで学習コストが低い
  3. Panelの陰にアクションを投げる対象物が隠れてしまう。
  4. 擬似モード中だけに使えるショートカットキーというのは理解しづらいかもしれない

今の所、こんな感じかな。プロジェクトファイルはここに置いてきます。( commandKeyMode.zip )

じゃあね。

]]>
Sun, 24 Jul 2016 00:00:00 +0900
https://www.mindto01s.com/2016/07/11/71f480a2_e7a8_471d_b82c_6f6d921f7b0c.html https://www.mindto01s.com/2016/07/11/71f480a2_e7a8_471d_b82c_6f6d921f7b0c.html <![CDATA[xibファイルの中のオブジェクト図]]> xibファイルの中のオブジェクト図

xibファイルの中にある、それぞれのオブジェクトの関係を図示したい。 xibから自動生成させるのが理想だが、実力がないので出来なかった。 テキストから画像を生成させるところまでをメモとしてまとめた。

このようなテキストから

digraph window {

// 定義ファイルをインクルード
include(objectGraph.inc)

// オブジェクトの設定
GenericObject(WinController)
WindowObject(Window)


// コネクションの設定
IBOutlet(Window, WinController, "delegate")
IBOutlet(WinController, Window, "window")

// 表示上の調整
{rank=same; WinController; Window;}

}

以下のような図を生成する。

../../../_images/window.png

材料は、

  • graphviz
  • icon画像
  • m4

の3つ。dot言語をm4マクロで包んで、簡単な表記でオブジェクト図を描画できるようにした。

手順は、

  1. ディレクトリ構造を整え、icon画像を配置。

zipファイルの中身を以下の様に配置する。objectGraph.zip

$ tree .
.
├── icon
│   ├── FirstResponder.png
│   ├── NSApplication.png
│   ├── NSCell.png
│   ├── NSControl.png
│   ├── NSObject.png
│   ├── NSObjectController.png
│   ├── NSView.png
│   ├── NSViewController.png
│   ├── NSWindow.png
│   ├── NSWindowController.png
│   └── Placeholder.png
└── objectGraph.inc
  1. 描画するオブジェクトのdot.m4ファイルを書く。

上記のファイルを展開したディレクトリに、テキストファイルを書く。

  1. コマンド実行。
$ m4 -DshowIBOutlet window.dot.m4 | dot -T png -o window.png

サンプル1 IBOutlet

以下の様なファイルを

digraph objectGraph {

// 定義ファイルをインクルード
include(objectGraph.inc)

// オブジェクトの設定
ApplicationObject(NSApplication)
GenericObject(CCAppDelegate)
WindowObject(NSWindow, , viewGroup)

// View階層の設定
BeginContainerView(contentsView, NSView, , viewGroup)
 ControlObject(NSButton1)
 ControlObject(NSTextField1)
 ControlObject(NSTextField2)
EndContainerView

// コネクションの設定
IBOutlet(NSApplication, CCAppDelegate, " delegate")
IBOutlet(CCAppDelegate, NSWindow, "window")
IBOutlet(CCAppDelegate, NSTextField1, "yen")
IBOutlet(CCAppDelegate, NSTextField2, "doller")
IBOutlet(NSWindow, contentsView, " contentsView")
IBAction(NSButton1, CCAppDelegate, "target/convert:")

// 表示上の調整
{rank=same; CCAppDelegate; NSWindow;}
}

以下のコマンドで画像を生成すると、

$ m4 -DshowIBOutlet CurrencyConverter.dot.m4 | dot -T png -o CurrencyConverter_outlet.png

outletを表示します。

../../../_images/CurrencyConverter_outlet.png

m4に”showIBOutlet”を渡すと、outletを表示します。

サンプル2 IBAction

サンプル1と同じテキストファイルを以下のコマンドで画像生成すると、

以下のコマンドで画像を生成すると、

$ m4 -DshowIBAction CurrencyConverter.dot.m4 | dot -T png -o CurrencyConverter_action.png

actionを表示します。

../../../_images/CurrencyConverter_action.png

m4に”showIBAction”を渡すと、outletを表示します。

IBActionのラベルは”target/セレクタ名”にしています。

サンプル3 View階層

View階層を表現するのには、矢印などのコネクションの表現だけでは無理だと思われるので、”内包”の表現を使用する様にした。

digraph objectGraph {

// 定義ファイルをインクルード
include(objectGraph.inc)

// View階層の設定
BeginContainerView(contentsView, NSView, , viewGroup)
 ControlObject(NSButton1)
 ControlObject(NSTextField1)
 ControlObject(NSTextField2)
EndContainerView

}

は、contentsViewに2つのテキストフィールドと1つのボタンを配置されていることを示しています。

以下のコマンドで画像を生成すると、

$ m4 CurrencyConverter_view.dot.m4 | dot -T png -o CurrencyConverter_view.png

こんな感じになります。グレーの枠で複数のコントロールがcontentsViewに内包されていることを示しています。

../../../_images/CurrencyConverter_view.png

サンプル4 少し大きな画像

ある程度オブジェクトの数が増えると、実用的ではないかもしれない。

ここ( objectGraph.dot.m4 )にあるようなファイルを出力するだけで、

../../../_images/objectGraph.png

こんな煩雑な画像が出てくる。

初心者に説明するための、Object図ならば簡単に書けるけど、実用的なアプリケーションのオブジェクト図は、煩雑すぎて実用にはならないかもしれない。

試作してみての感想

利点としては、

  • 元データがテキストなので各種コマンドラインツールと相性が良い。
  • outletとactionを1つのデータから出力できる様にしたので、画像が煩雑になるのを防いでいる。

欠点は、 - m4のマクロだけでは、テキスト入力の手間はあまり変わらない。 - ある程度大きなオブジェクト図になるとかなり煩雑な画面になってしまう。

今後の展開として

  • xibから出力できる様にしたい。
  • bindingの表記も必要
  • autoLayoutの表記も必要
  • 注目するObjectをフィルタリングして出力可能にすると良いかもしれない
]]>
Mon, 11 Jul 2016 00:00:00 +0900
https://www.mindto01s.com/2016/01/11/394afc56_cdfe_4024_866e_5c3f301d6beb.html https://www.mindto01s.com/2016/01/11/394afc56_cdfe_4024_866e_5c3f301d6beb.html <![CDATA[Swyftのユーザーインターフェイス その3]]> Swyftのユーザーインターフェイス その3

クラスメソッドさんの会議室をお借りして、第76回Cocoa勉強会関東 を開催しました。

そこで、「Swyftのユーザーインターフェイス」の発表を行いました。

内容は、

  • SwyftUIとは何かの説明
  • MacOSXでSwiftyUI(のサブセット)を実現するアプリケーションの解説
  • SwyftUIで使用する、キーボードの自作

なお、かな英数キーをLeapキーの代わりに使用しているので、JISキーボードであれば自作しなくともソフトウエアは使用できます。

プロジェクトファイルは、 TestTextEdit.zip に、pdfファイルは、 SwyftUI.pdf に置いておきます。

]]>
Mon, 11 Jan 2016 00:00:00 +0900
https://www.mindto01s.com/2016/01/03/0d66751e_4a46_42f4_bba7_789f4b2e374a.html https://www.mindto01s.com/2016/01/03/0d66751e_4a46_42f4_bba7_789f4b2e374a.html <![CDATA[Swyftのユーザーインターフェイス その2]]> Swyftのユーザーインターフェイス その2

コードを書く前に、LEAPキーの状態遷移を書き出してみる。

まずはイベントの種類

イベントの種類
イベント名 説明
LF下 LEAP Forward keyDown event
LF上 LEAP Forward keyUp event
LB下 LEAP Back keyDown event
LB上 LEAP Back keyUp event
Tab下 Tab keyDown event
Tab上 Tab keyUp event
その他 その他のキーイベント

SwyftではLeapキーを押している間だけ検索する。そのため、keyDownとKeyUpを区別する必要があり、keyDownイベントとkeyUpの2つのイベントを分けている。 TabキーはLeapキーとの組み合わせで再検索を行うためにイベントを追加した。

次に状態の種類

状態の種類
状態名 説明
normal 通常状態。各種キー入力はそのままTextViewへ渡される
findF 前方検索状態。各種キー入力された文字列が検索文字列として前方逐次検索される
findB 後方検索状態。各種キー入力された文字列が検索文字列として後方逐次検索される
select 選択状態。現在のカーソル位置とマークされた位置を選択状態にする。

状態遷移図を書くとこんな感じ。

digraph foo {  {rank = same; normal} {rank = same; findF; findB} {rank = same; select}  "normal" -> "findF" [ label = "LF下" ]; "normal" -> "findB" [ label = "LB下" ]; "normal" -> "normal" [ label = "その他" ];   "findF" -> "select" [ label = "LB下" ]; "findF" -> "normal" [ label = "LF上" ]; "findF" -> "findF" [ label = "その他" ];    "findB" -> "select" [ label = "LF下" ]; "findB" -> "normal" [ label = "LB上" ]; "findB" -> "findB" [ label = "その他" ];   "select" -> "findF" [ label = "LB上" ]; "select" -> "findB" [ label = "LF上" ]; "select" -> "select" [ label = "その他" ];  }

眠いから、状態遷移表はまた次に書く。

]]>
Sun, 03 Jan 2016 00:00:00 +0900
https://www.mindto01s.com/2016/01/02/addf2f1f_daa0_48c9_920f_54774f88b123.html https://www.mindto01s.com/2016/01/02/addf2f1f_daa0_48c9_920f_54774f88b123.html <![CDATA[Swyftのユーザーインターフェイス その1]]> Swyftのユーザーインターフェイス その1

Swyftとはなんぞや?

Swiftではない。

Swyftは、ジェフ・ラスキンによって開発されたテキスト編集用のユーザーインターフェイスです。 Leapキーと呼ばれる2つのキーを使用した、逐次検索を主体とした操作体系が特徴です。

../../../_images/Canon_Cat.jpg

こんな画像のようなキーボードで操作します。(ウィキペディアのCanonCatより) もちろん私は使ったことはない。以下の文献を元に使用感を想像するだけ。

以下の文献をふんわりと心の目で見て妄想する。

ジェフ・ラスキン本人がSwyftについて解説している。第5章「統一化」で、Swyft特有のキーボード操作について解説している。

AppleII用のソフトウェアSwyftWareの使用レポート。日本語でSwyftの雰囲気がわかる貴重な文献です。 私にとっては、「ヒューメイン・インタフェース」より解りやすかった。

SwyftのUIを採用している、CanonCatの雰囲気がわかる。キー操作と画面の関係はこの動画を見ると腑に落ちる感じ。

そして、冬休みの工作の時間が始まる。

LEAPキーがないから作る

まずは、Leapキーがあるキーボードをデッチ上がる必要がある。

で、コレ。冬休みの工作としては良くできた。

../../../_images/IMG_1997.jpg

HHKと 英数/かなキーボード を2個位置しただけ。 英数/かなキーボードは、基盤だけ自作の傾斜台に釘で打ち付けた。

スペースキーの前にキーを置くのは想像以上に打ちやすい。ただし、キーボードの配置そのものよりもキーの高さが打ちやすさに関係していると思われる。

スペースキーを押したときの高さよりも、Leapキーの高さが低い必要がある。 そうしないと、スペースキーを押したときにLeapキーを誤って押してしまうことがあった。

LEAPキーの検索を日本語対応させる

逐次検索を主体としたUIなのに、日本語ではかな漢字変換が間に入るために、操作性が低下してしまう。 この問題の解決はすでに先人が解決していて、

逐次検索を行うには、 migemo を使用する。で解決。

以前、OSX内蔵の辞書を使ったmigemoもどきを作ったが、非公開APIを使った実装になってしまったので、素直に C/migemo を使うことにした。

が、なんか上手く行かない。

LEAPキーを押したときだけ、検索をするようにする

Leapキーを使った操作系なのだが、これもまだ上手く行かない。

../../../_images/ss1.png

右上のパレットは、Leapキーと関連するキーの状態を表している。

[NSEvent addLocalMonitorForEventsMatchingMask: handler:]

を使って、キーの変更を捉えている。

今日は、ここまで。

]]>
Sat, 02 Jan 2016 00:00:00 +0900
https://www.mindto01s.com/2015/05/24/60eb87a8_b417_45b2_806a_0efbcb5592ff.html https://www.mindto01s.com/2015/05/24/60eb87a8_b417_45b2_806a_0efbcb5592ff.html <![CDATA[BDRuleEngine]]> BDRuleEngine

BDRuleEngineは以下のサイトで配布しているルールエンジン。 http://eschatologist.net/bDistributed.com/

10年以上前にObjective-Cで書かれた。WebObjectsのDirectToWebのルールエンジンを再実装したものらしい。 前々回のCocoa勉強会で手直ししたものを発表したので、zipでまとめておく。

手直しの内容は、

  • BDQualifier was replaced by NSPredicate.
  • BDSortOrdering was replaced by NSSortDescriptor.
  • BDControl was replaced by cocoa binding.
  • Adds BDExpressionAssignment class.

プロジェクトファイルは以下からダウンロードできる。

BDRuleEngine.zip BDRuleEditor.zip

]]>
Sun, 24 May 2015 00:00:00 +0900
https://www.mindto01s.com/2015/03/06/ede6a597_7158_4aaa_ad95_1c34061afd9b.html https://www.mindto01s.com/2015/03/06/ede6a597_7158_4aaa_ad95_1c34061afd9b.html <![CDATA[初期設定Windowの作り方]]> 初期設定Windowの作り方

勉強会資料。初期設定Windowの作り方。

../../../_images/PrefWindow.png

プロジェクトファイルは、 PrefWindow.zip に、pdfファイルは、 PrefWindow.pdf に置いておきます。

]]>
Fri, 06 Mar 2015 00:00:00 +0900
https://www.mindto01s.com/2014/12/13/d300fcdf_19b9_4f5d_ac51_73deb21ba5a6.html https://www.mindto01s.com/2014/12/13/d300fcdf_19b9_4f5d_ac51_73deb21ba5a6.html <![CDATA[インスペクタパネルの設計と実装]]> インスペクタパネルの設計と実装

勉強会資料。インスペクターパネルを複数同時に表示できるようにした。

../../../_images/inspector.png

プロジェクトファイルは、 inspector.zip に置いておきます。

]]>
Sat, 13 Dec 2014 00:00:00 +0900
https://www.mindto01s.com/2014/11/03/b4ac3066_c9ed_467b_8613_9152e0a0678e.html https://www.mindto01s.com/2014/11/03/b4ac3066_c9ed_467b_8613_9152e0a0678e.html <![CDATA[画面分割その1]]> 画面分割その1

昔から作ろうとしていて、挫折していた画面分割のUIを作ることにした。

以前は、汎用性を考慮した設計にしようと努力して失敗したので、今回は努力しない方針とする。

まずは、NSScrollerにボタンをつける。

../../../_images/split1.png

垂直スクロールバーの上の方にある。2つのアイコンがそれ。

コードはこんな感じ。

TDScrollView.h

@interface TDScrollView : NSScrollView
@end

TDScrollView.m

#import "TDScrollView.h"

@interface TDScrollView ()

@property (strong) NSButton* vSplitOpenButton;
@property (strong) NSButton* vSplitCloseButton;

@end


@implementation TDScrollView


- (IBAction) responseOpenSplit:(id)sender
{
    id theObject = [NSApp targetForAction:@selector(openSplit:)
                                       to:self
                                     from:self];
    if(theObject && theObject != self)
    {
        [NSApp sendAction:@selector(openSplit:) to:theObject from:self];
    }
}

- (IBAction) responseCloseSplit:(id)sender
{
    id theObject = [NSApp targetForAction:@selector(closeSplit:)
                                       to:self
                                     from:self];
    if(theObject && theObject != self)
    {
        [NSApp sendAction:@selector(closeSplit:) to:theObject from:self];
    }
}

- (void)tile
{
    [super tile];

    if(  self.hasVerticalScroller)
    {
        NSScroller* vScroller        = self.verticalScroller;
        NSRect      vScrollerFrame   = vScroller.frame;

        // 分割ボタン
        if( !self.vSplitOpenButton )
        {
            self.vSplitOpenButton = [[NSButton alloc] initWithFrame: NSZeroRect];
            self.vSplitOpenButton.buttonType = NSMomentaryLightButton;
            self.vSplitOpenButton.bezelStyle = NSThickSquareBezelStyle;
            self.vSplitOpenButton.bordered = NO;
            self.vSplitOpenButton.image = [NSImage imageNamed: @"splitopen"];
            self.vSplitOpenButton.imagePosition = NSImageOnly;
            [self.vSplitOpenButton.cell setImageScaling:NSImageScaleProportionallyDown];

            self.vSplitOpenButton.target = self;
            self.vSplitOpenButton.action = @selector(responseOpenSplit:);

            [self addSubview:self.vSplitOpenButton];
        }

        NSButton*   vOpenButton      = self.vSplitOpenButton;
        NSRect      vOpenButtonFrame;

        // 分割ボタンの位置を設定
        vOpenButtonFrame.origin.x = vScrollerFrame.origin.x;
        vOpenButtonFrame.origin.y = vScrollerFrame.origin.y;
        vOpenButtonFrame.size.height = vScrollerFrame.size.width;
        vOpenButtonFrame.size.width  = vScrollerFrame.size.width;
        vOpenButton.frame = vOpenButtonFrame;

        // スクローラーの表示非表示を同期する
        vOpenButton.hidden = vScroller.hidden;

        // 結合ボタン
        if( !self.vSplitCloseButton )
        {
            self.vSplitCloseButton = [[NSButton alloc] initWithFrame: NSZeroRect];
            self.vSplitCloseButton.buttonType = NSMomentaryLightButton;
            self.vSplitCloseButton.bezelStyle = NSThickSquareBezelStyle;
            self.vSplitCloseButton.bordered = NO;
            self.vSplitCloseButton.image = [NSImage imageNamed: @"splitclose"];
            self.vSplitCloseButton.imagePosition = NSImageOnly;
            [self.vSplitCloseButton.cell setImageScaling:NSImageScaleProportionallyDown];

            self.vSplitCloseButton.target = self;
            self.vSplitCloseButton.action = @selector(responseCloseSplit:);

            [self addSubview:self.vSplitCloseButton];
        }

        NSButton*   vCloseButton      = self.vSplitCloseButton;
        NSRect      vCloseButtonFrame;

        // 結合ボタンの位置を設定
        vCloseButtonFrame.origin.x = vScrollerFrame.origin.x;
        vCloseButtonFrame.origin.y = vScrollerFrame.origin.y + vOpenButtonFrame.size.height;
        vCloseButtonFrame.size.height = vScrollerFrame.size.width;
        vCloseButtonFrame.size.width  = vScrollerFrame.size.width;
        vCloseButton.frame = vCloseButtonFrame;

        // スクローラーの表示非表示を同期する
        vCloseButton.hidden = vScroller.hidden;

        // スクロールバーの大きさをボタンに合わせて調整
        vScrollerFrame.origin.y += vOpenButtonFrame.size.height + vCloseButtonFrame.size.height;
        vScrollerFrame.size.height -= vOpenButtonFrame.size.height + vCloseButtonFrame.size.height;
        [vScroller setFrame:vScrollerFrame];
    }

}

@end

tileメソッドをオーバーライドしないと実装できない。 IBでなんとかしようとしたり、サブクラスしないでなんんとかできると考えてはいけない。

]]>
Mon, 03 Nov 2014 00:00:00 +0900
https://www.mindto01s.com/2014/08/27/53b1678e_ffa9_41ad_b5e4_03e8e1258b5a.html https://www.mindto01s.com/2014/08/27/53b1678e_ffa9_41ad_b5e4_03e8e1258b5a.html <![CDATA[第68回 Cocoa勉強会 関東 - Mac/iOS開発勉強会の告知]]> 第68回 Cocoa勉強会 関東 - Mac/iOS開発勉強会の告知

Cocoa勉強会 関東( http://cocoa-study.com )の68回目の勉強会です。

見学の方は以下のページで参加表明してください。

http://connpass.com/event/8287/

※勉強会のメンバーの方は こちらに登録してもかまいませんが、 いつものところでも出席登録してください。

日時:20014/9/27(土) 13:00-17:00

集合:現地

会場:千葉県松戸市本町20-10 ルシーナビル7F “FAN CLUB”

JR常磐線/新京成線松戸駅から徒歩2分

http://madcity.jp/fanclub/

参加費:500円(当日集めます)

発表:

Cocoa勉強会 関東について

Mac OS X, iOSでのプログラミングについての勉強会です。 4-6本程度の発表と質疑応答、その他雑談という構成です。 Mac/iOSの比重はその日によって変わります。Macばかりの日もあります。

最近の内容については、 http://wp.cocoa-study.com を参照ください。

勉強会を見学された方で入会を希望される方は、http://www.cocoa-study.com/mail/ から、ご連絡ください。

じゃあね。

]]>
Wed, 27 Aug 2014 00:00:00 +0900
https://www.mindto01s.com/2014/06/15/b250d000_19e3_4a64_b440_d1e1af11d815.html https://www.mindto01s.com/2014/06/15/b250d000_19e3_4a64_b440_d1e1af11d815.html <![CDATA[swiftのローカルスコープ]]> swiftのローカルスコープ

色々と試して出来ないのかと、諦めていた。 が、ググると stackOverFlowのサイトに答が有った。

http://stackoverflow.com/questions/24011271/how-to-create-local-scopes-in-swift

こーやって、locallyを定義すると。

func locally(work: () -> ()) {
    work()
}

こんな風に、使える。

locally {
    let g = 42
    println(g)
}

swiftでは引数の最後の引数は括弧の外にだしても良いようだ。

色々と、試してみるとこんな感じ。

func local(test:Int, inWork:()->())
{
    inWork();
}

// OK
local(12,
    {

    })

// OK
local(12){

}

// Error
local(12)
{

}

3番目の例が文法的に許さないのは、単純なローカルスコープと引数の区別が出来ないのかも知れない。

swiftでは@synchronizedはサポートが無いそうなので、このローカルスコープを作る方法を利用出来そうだと思った。 が、これもまた、ググると stackOverFlowのサイトに答が有った。

http://stackoverflow.com/questions/24045895/what-is-the-swift-equivalent-to-objective-cs-synchronized

func synced(lock: AnyObject, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

synced(self) {
    println("This is a synchronized closure")
}

この、クロージャーを受け取って、その前後で定型処理を行うのは結構つかえるテクニックに見える。 例えば、

func cgLoclaContext(closure: () -> ()) {
    var theContext: CGContext;
    CGContextSaveGState(theContext)
    closure()
    CGContextRestoreGState(theContext)
}

cgLoclaContext() {
    // ココで、描画色を変えたり、クリッピングリージョンを変えたりする。
}
// ここまでくると、CGContextの状態が自動で戻る。

ローカルスコープと、CoreGraphixの状態を一致させる事で状態の戻し忘れ等のミスを防げそう。

]]>
Sun, 15 Jun 2014 00:00:00 +0900
https://www.mindto01s.com/2014/06/08/7077a060_72af_42bc_8e11_829bb823c780.html https://www.mindto01s.com/2014/06/08/7077a060_72af_42bc_8e11_829bb823c780.html <![CDATA[IB_DESIGNABLEとIBInspectable]]> IB_DESIGNABLEとIBInspectable

XCodeでInterfaceBuilderようの新たなキーワードが定義された。 定義されたのはIB_DESIGNABLEとIBInspectableの2つ。

IB_DESIGNABLE 編集可能にしたいクラスに付ける
IBInspectable 編集可能にしたいプロパティに付ける

IB_DESIGNABLEは以下のように@interfaceの前に付ける。

IB_DESIGNABLE

@interface CustomView : NSView
.
.
.

IBにこのクラスが編集可能である事を知らせる為だけのキーワードらしい。

一方、IBInspectableは少し複雑。

@interface CustomView : NSView

@property (nonatomic) IBInspectable NSColor* fillColor;
@property (nonatomic) IBInspectable NSPoint point;
@property (nonatomic) IBInspectable NSRect rect;
@property (nonatomic) IBInspectable NSString* string;
@property (nonatomic) IBInspectable BOOL thisIsBool;
@property (nonatomic) IBInspectable NSImage* thisIsImage;
@property (nonatomic) IBInspectable NSInteger intgerValue;
@property (nonatomic) IBInspectable float floatValue;
@property (nonatomic) IBInspectable double doubleValue;
.
.
.

上記のように型名の前にIBOutletのように付ける。そうするとIB側が適当にインスペクターに表示してくれる。 NSImageやNSColorはiOSではUIImageやUIColorになると思われる。

../../../_images/IBInspectable.png

使える、型は決まっているようで物は使えなかった。

// Number型は表示出来ない
//@property (nonatomic) IBInspectable NSNumber* number;

// NSRangeは表示出来ない
//@property (nonatomic) IBInspectable NSRange range;

// NSValueは表示出来ない
//@property (nonatomic) IBInspectable NSValue* thisIsNSValue;

// 列挙は表示出来ない
//@property (nonatomic) IBInspectable TEST_ENUM updateType;

NSRangeが出来ないのは意外だが、IB上ではあまり使い道が無いのかもしれない。 贅沢を言えば、角度を表すUIに対応する型や、列挙型をサポートして欲しかった。

多分、”User defined Runtime Attributes”の機能をソースコードをパースして自動化出来るようにした物なんだろう。 “User defined Runtime Attributes”を表示するとリアルタイムで値が変わっている。

../../../_images/UserDefinedRuntimeAttributes.png

昔のIBPluginとは違った方向で作っているので、NSCoderとか無関係に実装出来ている。

IBでプレビューするには、framework化した上に、同じプロジェクトでコンパイルする必要がある等の面倒な面が有った。

]]>
Sun, 08 Jun 2014 00:00:00 +0900
https://www.mindto01s.com/2014/04/11/38acf113_34e0_4d88_b822_bcbe10c1bf00.html https://www.mindto01s.com/2014/04/11/38acf113_34e0_4d88_b822_bcbe10c1bf00.html <![CDATA[日本語のmanは便利だ]]> 日本語のmanは便利だ

ここ jmanを使わずにMacのmanを日本語化する方法 ( http://tukaikta.blog135.fc2.com/blog-entry-224.html ) を参考に環境整備した。

コチラの環境は MacPorts の為か、man がうまく jman を探してくれない。

仕方ないので .bash_profileに以下の行を追加した。

alias jman='env LANG=ja_JP.UTF-8 man -M /usr/local/share/man/: '

今までやせ我慢してたんだな。

]]>
Fri, 11 Apr 2014 00:00:00 +0900
https://www.mindto01s.com/2014/01/11/98167c88_a71e_407f_9339_88c82ba95885.html https://www.mindto01s.com/2014/01/11/98167c88_a71e_407f_9339_88c82ba95885.html <![CDATA[NSSplitViewの同期]]> NSSplitViewの同期

2つのNSSplitViewでSpliterPaneの位置を同期させたい。splitViewDidResizeSubviews:を使えば出来そうだと思いコードを書いてみた。

こんな感じ。

- (void)splitViewDidResizeSubviews:(NSNotification *)notification
{
    NSSplitView* theCurrentSplitView = (NSSplitView *)notification.object;
    NSView*      theParentView       = [theCurrentSplitView superview];
    NSArray*     theParentSubviews   = [theParentView subviews];

    NSArray* theSrcArray;
    theSrcArray = theCurrentSplitView.subviews;

    for(NSView* theDstSplitView in theParentSubviews)
    {
        if( theDstSplitView != theCurrentSplitView && [theDstSplitView isKindOfClass:[NSSplitView class]] )
        {
            NSArray* theDstArray;

            theDstArray = theDstSplitView.subviews;

            if( theSrcArray.count == theDstArray.count )
            {
                NSInteger theCount = theDstArray.count;

                for(NSInteger i = 0; i < theCount; i++)
                {
                    ((NSView*)theDstArray[i]).frame = ((NSView*)theSrcArray[i]).frame;
                }
            }
        }
    }
}

リサイズするとうまく動いてくれない。さて、どうした物か。

プロジェクトファイルは、 SplitViewTest1.zip に置いておきます。

]]>
Sat, 11 Jan 2014 00:00:00 +0900
https://www.mindto01s.com/2013/12/27/e0296e4a_f060_4739_811f_c72101735b9e.html https://www.mindto01s.com/2013/12/27/e0296e4a_f060_4739_811f_c72101735b9e.html <![CDATA[ヒレガス本 20章 課題]]> ヒレガス本 20章 課題

20章の課題は以下の2つ。

  1. BigLetterViewの文字にNSShadowを使って影を付ける。
  2. BigLetterViewのの文字にboldやitalicの効果を付けるチェックボックスをつける。

最初の課題は簡単です。メソッドprepareAttributesの末尾にNSShadowに関するコードを追加するだけ。

- (void) prepareAttributes
{
    .
    .
    .

    NSShadow* theShadow = [[NSShadow alloc] init];
    theShadow.shadowOffset = NSMakeSize(3.0, -3.0);

    self.attributes[NSShadowAttributeName] = theShadow;
}

二番目の課題も簡単。以下の手順で実装する。

  • BigLetterViewにboldやitalicのプロパティを追加
  • アクセッサメソッドで、attributesのNSFontAttributeNameキーの値を更新
  • UIはbindingで作成

ヘッダファイルの追加は2行だけ。

@interface BigLetterView : NSView
    .
    .
    .
@property (getter = isBold)   BOOL bold;
@property (getter = isItalic) BOOL italic;
    .
    .
@end

次に実装ファイルに、以下のようなアクセッサを追加。NSFontManagerの説明は、p373を見ればだいたい判る。

@synthesize bold = _bold;

- (void) setBold:(BOOL)inBold
{
    NSFont*        theFont = self.attributes[NSFontAttributeName];
    NSFontManager* theFontManager = [NSFontManager sharedFontManager];

    if( inBold )
    {
        theFont = [theFontManager convertFont:theFont
                                  toHaveTrait:NSBoldFontMask];
    }
    else
    {
        theFont = [theFontManager convertFont:theFont
                               toNotHaveTrait:NSBoldFontMask];
    }

    self.attributes[NSFontAttributeName]            = theFont;

    [self setNeedsDisplay:YES];
}

- (BOOL) isBold
{
    NSFontManager* theFontManager = [NSFontManager sharedFontManager];

    if( [theFontManager traitsOfFont:self.attributes[NSFontAttributeName]] & NSBoldFontMask )
    {
        return YES;
    }
    else
    {
        return NO;
    }
}

italicのアクセッサの説明とコードは省略。

最後のUIの処理は文章では判りにくいので以下の図を参考にしてください。

NSObjectControllerを追加してcontentsにBigLetterViewを指定する。

../../../_images/controller.png

チェックボックスBoldとチェックボックスitalicをObjectController経由でBigLetterViewのboldとitalicにbindingする。

../../../_images/binding1.png

これで、チェックボックスに連動してbigLetterViewの表示が変わるようになります。

プロジェクトファイルは、 Typing_2.zip に置いておきます。

]]>
Fri, 27 Dec 2013 00:00:00 +0900
https://www.mindto01s.com/2013/12/26/9d174648_b2aa_4208_8551_7d58cb73793b.html https://www.mindto01s.com/2013/12/26/9d174648_b2aa_4208_8551_7d58cb73793b.html <![CDATA[ヒレガス本 19章 課題]]> ヒレガス本 19章 課題

19章には課題は無い。無いので、自分で作る事にする。

作成した、アプリケーションTypingTutorに以下の2つの機能を追加する。

  1. BigLetterViewの矩形に入ったら、カーソルをIビームの形にする。
  2. BigLetterViewの矩形に入ったら、バックグラウンド色を変更する。

最初の課題は簡単です。NSViewにはカーソルを変更する時にオーバーライドするメソッドが用意されている。 以下のメソッドを追加すれば良い。

- (void) resetCursorRects
{
    [self addCursorRect:[self bounds]
                 cursor:[NSCursor IBeamCursor]];
}

二番目の課題もp339からのロールオーバーのコードをそのまま入れるだけ。

プロジェクトファイルは、 TypingTutorWithMouse.zip に置いておきます。

]]>
Thu, 26 Dec 2013 00:00:00 +0900
https://www.mindto01s.com/2013/12/23/e6896f6d_3c40_4403_9926_ed7bcedb830f.html https://www.mindto01s.com/2013/12/23/e6896f6d_3c40_4403_9926_ed7bcedb830f.html <![CDATA[ターミナルでちょっとした文字列の暗号化と複号化]]> ターミナルでちょっとした文字列の暗号化と複号化

MacOSXでは暗号化と複号化の定番はopnesslコマンドです。

ターミナルで以下のようにする事で文字列の暗号化が出来る。暗号化する為のパスワードを聞いてくるので、適当に打ち込む事。

$ echo '暗号化したい文字列' | openssl enc -e -aes128 | base64

base64が必要な理由は、opnesslの出力がバイナリだから。base64を使うと暗号化後のデータが文字列になるので扱いやすい。

使い方は簡単。例えば、文字列「秘密にした大切な事」を暗号化するには、

$ echo '秘密にしたい大切な事' | openssl enc -e -aes128 | base64
enter aes-128-cbc encryption password: (パスワードを入力)
Verifying - enter aes-128-cbc encryption password: (もう一度パスワードを入力)
U2FsdGVkX18W0ERyPHnFu1rLkHnaytOC61ZDX2j+SIsYEXp4tuD5QTg0M5VvBVQS

と行えば良い。最後に出て来た”U2FsdGVk…….”が暗号化後の文字列。

複号化するには、ターミナルで以下のように打ち込めば良い。複号化の為のパスワードを聞いてくるので打ち込む事。

$ echo '暗号化した文字列' | base64 -D | openssl enc -d -aes128

先ほどの暗号化した文字列を複号化するには、以下のようにする。

$ echo 'U2FsdGVkX18W0ERyPHnFu1rLkHnaytOC61ZDX2j+SIsYEXp4tuD5QTg0M5VvBVQS' | base64 -D | openssl enc -d -aes128
enter aes-128-cbc decryption password: (パスワードを入力)
秘密にしたい大切な事

もちろん、暗号化した時に入力したパスワードを忘れたら元に戻せないので気をつける事。

]]>
Mon, 23 Dec 2013 00:00:00 +0900
https://www.mindto01s.com/2013/10/29/4b3a79c9_a875_464d_8700_4d0c4cecec9c.html https://www.mindto01s.com/2013/10/29/4b3a79c9_a875_464d_8700_4d0c4cecec9c.html <![CDATA[mavericksにアップデートしたらgit-completionが行方不明になった]]> mavericksにアップデートしたらgit-completionが行方不明になった

mavericksにアップデートしたら、ターミナルを立ち上げるたびに

-bash: /usr/share/git-core/git-completion.bash: No such file or directory

とエラーを吐かれる。

git-completion.bashの位置が変わったようだ。findで調べると、

/Applications/Xcode.app/Contents/Developer/usr/share/git-core/git-completion.bash

/Library/Developer/CommandLineTools/usr/share/git-core/git-completion.bash

の2カ所に見つかった。

.bash_profilの

# git setting
source /usr/share/git-core/git-completion.bash

# git setting
source /Library/Developer/CommandLineTools/usr/share/git-core/git-completion.bash

と書き換える。

エラーも止まり、補完も効くようになり解決。

]]>
Tue, 29 Oct 2013 00:00:00 +0900
https://www.mindto01s.com/2013/10/18/2634b9c1_d0d4_496d_876f_5b8c53ef624e.html https://www.mindto01s.com/2013/10/18/2634b9c1_d0d4_496d_876f_5b8c53ef624e.html <![CDATA[関東62回Cocoa勉強会の資料(10/19)]]> 関東62回Cocoa勉強会の資料(10/19)

19日に行うCocoa勉強会の資料です。

multiWindow.pdf.

SampleCodes.zip.

内容は、Cocoaでマルチウインドウアプリケーションを作る時のコツみたいな事や考え方をダラダラ説明。

]]>
Fri, 18 Oct 2013 00:00:00 +0900
https://www.mindto01s.com/2013/10/10/93fdefe5_19a8_484a_9ce6_fb3b5afc47a4.html https://www.mindto01s.com/2013/10/10/93fdefe5_19a8_484a_9ce6_fb3b5afc47a4.html <![CDATA[トーマス的な何か]]> トーマス的な何か
../../../_images/IMG_0876.png

薄暗い中に、突然コレを見て久しぶりに動揺した。

]]>
Thu, 10 Oct 2013 00:00:00 +0900
https://www.mindto01s.com/2013/10/09/678a1481_78c8_4600_bf75_124dcf27a37a.html https://www.mindto01s.com/2013/10/09/678a1481_78c8_4600_bf75_124dcf27a37a.html <![CDATA[ヒレガス本 18章 課題 続き]]> ヒレガス本 18章 課題 続き

bindigのやり方が間違っている気がして、色々とやってみた。

カスタムViewをbinding対応にするやり方は、Appleのサンプルコードや詳解Objective-C2.0第三版で説明されていますが、個人的にはこれは何か違う気がしたのでパス。

まず、先日の「NSControllerが仲介して逆方向への伝播の設定を行っている」は私の間違い。NSControllerは逆方向への伝播は行っていない。

次に、[NSObject infoForBinding:]を使ってやってみた。 コードは、以下のような感じでovalが変更される部分で逆方向にKVCしてるだけ。

- (void) setOval:(NSRect)inOval
{
    if( !NSEqualRects(_oval, inOval) )
    {
        _oval = inOval;

        // binding元への伝播
        NSDictionary* theInfo = [self infoForBinding:@"oval"];

        if( theInfo )
        {
            NSString* thePath   = [theInfo valueForKey:NSObservedKeyPathKey];
            NSString* theObject = [theInfo valueForKey:NSObservedObjectKey];

            [theObject setValue:[NSValue valueWithRect:_oval]
                     forKeyPath:thePath];
        }


        [self setNeedsDisplay:YES];
    }
}

2重にbindingするよりも、「正しい」感じがする。

次に、先日も取り上げた Implementing Your Own Cocoa Bindings を参考にしてコードを書くと、

- (void) setOval:(NSRect)inOval
{
    if( !NSEqualRects(_oval, inOval) )
    {
        _oval = inOval;

        // binding元への伝播
        [self propagateValue:[NSValue valueWithRect:_oval]
                  forBinding:@"oval"];

        [self setNeedsDisplay:YES];
    }
}

ここまで、キレイに書ける。

プロジェクトファイルは、 OvalDoc_2.zipOvalDoc_3.zip に置いておきます。

]]>
Wed, 09 Oct 2013 00:00:00 +0900
https://www.mindto01s.com/2013/10/05/713b8fcf_547d_46c2_ad34_fdfaf0c527a4.html https://www.mindto01s.com/2013/10/05/713b8fcf_547d_46c2_ad34_fdfaf0c527a4.html <![CDATA[ヒレガス本 18章 課題]]> ヒレガス本 18章 課題

18章で作成したアプリケーションを元に以下の3つの機能を入れるとの事。

  • ドキュメントベースにする
  • ファイルの保存と再生に対応する
  • Undo/Redoに対応する

上記3つは簡単だった。 ハマったのは、viewとdocumentのデータを同期するのにbindingを使った所。

IBを使って操作していると、bind:toObject:withKeyPath:options:1行呼出すだけで双方向にデータを同期してくれると勘違いしていた。

双方向にデータをやり取りするには、それぞれにbind:toObject:withKeyPath:options:を呼出す必要があった。

IB上ではおそらくNSControllerが仲介して逆方向への伝播の設定を行っているのだと思われる。

コード上で行うには、今後は Implementing Your Own Cocoa Bindings を参考にして

[NSObject propagateValue:(id)value forBinding:(NSString*)binding];

を使うとよいかもです。

プロジェクトファイルは、 OvalDoc.zip に置いておきます。

]]>
Sat, 05 Oct 2013 00:00:00 +0900
https://www.mindto01s.com/2013/10/04/c9fc9288_e1b0_4e6b_917b_626d9108ce7a.html https://www.mindto01s.com/2013/10/04/c9fc9288_e1b0_4e6b_917b_626d9108ce7a.html <![CDATA[ヒレガス本 17章 課題]]> ヒレガス本 17章 課題

ランダムな直線の描画部分を曲線に変えろとの事。

これは簡単。変更部分は1カ所だけ。

thePoint = [self randomPoint];
[path lineToPoint:thePoint];

[path curveToPoint:[self randomPoint]
     controlPoint1:[self randomPoint]
     controlPoint2:[self randomPoint]];

に変えただけ。

プロジェクトファイルは、 DrawingFun.zip に置いておきます。

注意点

P302の図が説明文とAutosizingの設定が違っている。

../../../_images/image1.png

上記の設定だとNSScrollViewがリサイズされると、StrectViewも同じようにリサイズされてしまう。 P301の説明のように左下隅に固定してリサイズされないようにするには以下のように設定する。

../../../_images/image2.png

違いはAutosizingの中の十字の部分だけ。

]]>
Fri, 04 Oct 2013 00:00:00 +0900
https://www.mindto01s.com/2013/10/01/1fef5661_b9c6_45ce_acc6_38229a78de43.html https://www.mindto01s.com/2013/10/01/1fef5661_b9c6_45ce_acc6_38229a78de43.html <![CDATA[ヒレガス本 16章 課題 その2]]> ヒレガス本 16章 課題 その2

前回のアップデートスクリプトに加えて、ビルド時のチェックツールも作ってみた。

機能は、

  • 既に存在しないキーで指定された、翻訳文が有れば警告
  • ソースコード中にキーが有るが、*.stringsに存在しないキーがあれば警告
  • マージがコンクリフトしている場所をエラー

とするツールを作った。

#!/bin/sh
#
# *.lproj/Localizable.stringsの内容のチェックを行う

# stringsの拡張子
theStringsExt=".strings"

# ローカライズディレクトリの拡張子
theLocalizeExt=".lproj"

# Localizable.stringのファイル名
theLocalizableStrings="Localizable"$theStringsExt


##########################################################################################
#
# missingなキーワードの警告
#
##########################################################################################
# warningMessage フルパス 行番号 警告メッセージ 警告行のテキスト
function warningMissingKeyWordMessage() {

    local FULL_PATH_FILE_NAME=$1
    local KEYWORD_STRING=$2

    echo $FULL_PATH_FILE_NAME"::: warning:" $KEYWORD_STRING is a missing key word.":" $KEYWORD_STRING
}


##########################################################################################
#
# danglingなキーワードの警告
#
##########################################################################################

function warningDanglingKeyWordMessage() {

    local FULL_PATH_FILE_NAME=$1
    local KEYWORD_STRING=$2

    echo $FULL_PATH_FILE_NAME":"`grep -n $KEYWORD_STRING $FULL_PATH_FILE_NAME | cut -d":" -f1`":0: warning:" $KEYWORD_STRING is a dangling key word.":" `grep $KEYWORD_STRING $FULL_PATH_FILE_NAME`
}





##########################################################################################
#
# エラーメッセージを出力
#
##########################################################################################
# errorMessage フルパス 行番号 エラーメッセージ エラー行のテキスト
function errorConflictMessage() {

    local FULL_PATH_FILE_NAME=$1
    local LINE_NUMBER=$2
    local TEXT_STRING=$3

    echo $FULL_PATH_FILE_NAME":"$LINE_NUMBER":0: error: Merge conflict :" $TEXT_STRING
}

# Base.lprojのフルパス
theBaseLocalizableStrings=`pwd`/Base$theLocalizeExt/$theLocalizableStrings


# キーだけのリストにしてtmpへ保存する
# 一時的な作業フォルダーを作成する
theTempDir="tmp"
if ! [ -d $theTempDir ]; then
    mkdir $theTempDir
fi

# コメントと空白行を削除
cat $theBaseLocalizableStrings | sed -e 's:/\*.*\*/::' | sed -e '/^[<space><tab>]*$/d' | cut -d' ' -f1 > $theTempDir/tmplist.txt

# forループの区切りを改行だけにする
IFS=$'\n'


# Base.lproj/以下以外のLocalizable.stringsを全部ループ
for theIterator in `find \`pwd\` -regex ".*/Base.lproj/.*" -prune -o -regex ".*lproj/Localizable\.strings" -print`
do

    # 右側にだけある項目
    for theWord in `cat $theIterator | sed -e '/^<<<<<<<.*$/d' | sed -e '/^|||||||.*$/d' | sed -e '/^=======.*$/d' | sed -e '/^>>>>>>>.*$/d' |  sed -e 's:/\*.*\*/::' | sed -e '/^[<space><tab>]*$/d' | cut -d' ' -f1 | sort | uniq | comm -13 $theTempDir/tmplist.txt -`
    do
        warningDanglingKeyWordMessage $theIterator $theWord
    done

    # 左側にだけある項目
    for theWord in `cat $theIterator | sed -e 's/^<<<<<<<.*$//' | sed -e 's/^|||||||.*$//' | sed -e 's/^=======.*$//' | sed -e 's/^>>>>>>>.*$//' |  sed -e 's:/\*.*\*/::' | sed -e '/^[<space><tab>]*$/d' | cut -d' ' -f1 | sort | uniq | comm -23 $theTempDir/tmplist.txt -`
    do
        warningMissingKeyWordMessage $theIterator $theWord
    done

    # コンフリクトな行(下の四行など)をコンクリフトだと報告する
    # <<<<<<<
    # |||||||
    # =======
    # >>>>>>>
    for theConflictLine in `cat $theIterator | grep -n '^<<<<<<<\|^=======\|^>>>>>>>\|^|||||||'`
    do
        errorConflictMessage $theIterator `echo $theConflictLine | cut -d':' -f1` `echo $theConflictLine | cut -d':' -f2`
    done
done


# 後片付け
rm -f $theTempDir/tmplist.txt

XCodeに組み込んで使います。

ここに置いて、

../../../_images/filePath.png

こんな風に設定すると。

../../../_images/setting.png

ビルド時に、こんな風に警告が出たりエラーが出ます。

../../../_images/error.png

実用的になるかどうかは不明。 16章はここら辺で見切りを付けて明日からは次の章を行うつもり。

]]>
Tue, 01 Oct 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/25/abe4e0fb_fbcb_4966_8627_ad4a2c1ea8d2.html https://www.mindto01s.com/2013/09/25/abe4e0fb_fbcb_4966_8627_ad4a2c1ea8d2.html <![CDATA[ヒレガス本 16章 課題]]> ヒレガス本 16章 課題

16章のローカライズの章には課題は無い。無いので自分で課題を作ってみた。

genstringsを使った文字列のローカライズを支援するツールを作成する。

genstringsが自動でLocalizable.stringを作れるのはよい。楽だ。 開発の最後の段階でローカライズを行い、1回の翻訳で全ての文字列を翻訳できるのならば、問題も無い。

しかし、実際には開発を行いながらローカライズも随時行うのが私のやり方です。面倒だと思ってもユーザーインターフェイスを追加したり削除したりします。 ソースコードもリファクタリングしてアラートの数を減らしたりします。

genstringsはLocalizable.stringを上書きしてしまうので、そのまま使うと翻訳済みのデータが消えてします。

そこで、genstringsとdiff3を組み合わせて、翻訳済みのデータを上書きしないようにシェルスクリプトを書いてみました。 練習でつくってみただけですので、実開発では使っていません。

#!/bin/sh
#
# updateLocalizableStrings
#
#  マージツール

# stringsの拡張子
theStringsExt=".strings"

# ローカライズディレクトリの拡張子
theLocalizeExt=".lproj"

# Localizable.stringのファイル名
theLocalizableStrings="Localizable"$theStringsExt

theTempDir="tmp"
theNewBaseLocalizableStrings=$theTempDir/$theLocalizableStrings


##########################################################################################
#
# 新たに生成されたLocalizable.stringsを"tmp/Localizable8.strings"に用意する
#
##########################################################################################

# 一時的な作業フォルダーを作成する
if ! [ -d $theTempDir ]; then
    mkdir $theTempDir
fi

# ソースコードをなめて、置換えテーブルを作成。作成場所は"./tmp/Localizable.strings"
genstrings -o $theTempDir `find . -name '*.[hcm]'`

# UTF-8に変換
iconv -f UTF-16 -t UTF-8 < $theNewBaseLocalizableStrings > $theNewBaseLocalizableStrings"_tmp"

# 後片付け
mv $theNewBaseLocalizableStrings"_tmp" $theNewBaseLocalizableStrings

##########################################################################################
#
# 古いLocalizable.stringsは"Base.lproj/Localizable.strings"にあると仮定する。なかったら作る
#
##########################################################################################

theOldBaseLocalizableStrings="Base"$theLocalizeExt/$theLocalizableStrings

if ! [ -f $theOldBaseLocalizableStrings ]; then
     cp $theNewBaseLocalizableStrings $theOldBaseLocalizableStrings
fi

##########################################################################################
#
# "Base"ディレクトリ以外の"*.lproj/Localizable.strings"をdiff3でアップデートする
#
##########################################################################################

for theLocaleStringsDir in `find . -name "*.lproj" -print`
do
    if [ $theLocaleStringsDir != "./Base.lproj" ]; then
        theLocaleStringsFile=$theLocaleStringsDir"/Localizable.strings"

        # 無ければ作り、あればdiff3でアップデート
        if ! [ -f $theLocaleStringsFile ]; then
            cp $theNewBaseLocalizableStrings $theLocaleStringsFile
        else
            diff3 -m $theLocaleStringsFile $theOldBaseLocalizableStrings $theNewBaseLocalizableStrings > $theLocaleStringsFile"_tmp"
            mv $theLocaleStringsFile"_tmp" $theLocaleStringsFile
        fi
    fi
done


# 最後に"Base.lproj/Localizable.strings"を更新する
cp $theNewBaseLocalizableStrings $theOldBaseLocalizableStrings

使い方は、 “*.m” や “*.lproj” があるディレクトリで呼出すだけです。 必要に応じて “*.lproj/Localizable.strings” のデータが更新されます。 コンクリフトが発生した場合は、gitやsvnでおなじみの”<<<<<<<<”の文字列が書き込まれますので随時対処してください。

このスクリプトの課題

  1. diffのように行毎に比較して判断するのではなく、”*.strings”をパースするコードを書けばもう少し知的な振る舞いが出来そう。
  2. ibtoolの出力に対してもアップデート出来るコマンドがあると便利かも。
  3. プログラマだけで開発するのならよいけど、翻訳担当にコマンドラインインターフェイスはキツいかも。せめてExcelで編集csvで読込みが出来るようにするべきでは?
]]>
Wed, 25 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/21/0025a080_82a8_4d55_a9ff_3b3ec117f1da.html https://www.mindto01s.com/2013/09/21/0025a080_82a8_4d55_a9ff_3b3ec117f1da.html <![CDATA[ヒレガス本 15章 課題]]> ヒレガス本 15章 課題

削除しないが昇給なし(Keep, but no raise)ボタンを追加しろとの事。

これも簡単、KVCで行うと、NSArrayに含まれる全てのオブジェクトのプロパティーにアクセス出来る。 Controller経由でArrayを取得する事で、Undo/Redoにも対応してる。

- (IBAction) KeepButNoRaise:(id)sender
{
    NSArray* theSelectedPeople = [employeeController selectedObjects];

    [theSelectedPeople setValue:[NSNumber numberWithFloat:0.0]
                     forKeyPath:@"expectedRaise"];
}

後は、ボタンのenable/disableはbindingで選択された行数で判定。

../../../_images/binding.png

プロジェクトファイルは、 RaiseMan_AlertPanel に置いておきます。

]]>
Sat, 21 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/21/4d1857a5_bcce_4003_89bb_21829cb1c40e.html https://www.mindto01s.com/2013/09/21/4d1857a5_bcce_4003_89bb_21829cb1c40e.html <![CDATA[ヒレガス本 14章 課題]]> ヒレガス本 14章 課題

アプリケーションがアクティブになった時にBeep音をならせとの事。

既にapplication delegateが設定されてあるので、すごい簡単。

- (void)applicationDidBecomeActive:(NSNotification *)notification
{
    NSBeep();
}

課題の問題中の、Notification文字列が違うのはわざとなのだろうか?

プロジェクトファイルは、 RaiseMan_Notification に置いておきます。

]]>
Sat, 21 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/21/7cbe9f51_a913_47f9_956c_ab6fa2047fc0.html https://www.mindto01s.com/2013/09/21/7cbe9f51_a913_47f9_956c_ab6fa2047fc0.html <![CDATA[ARCって楽だと思ったら、メモリリークしてる]]> ARCって楽だと思ったら、メモリリークしてる

deallocが呼ばれないので調べてみると、巡回参照が生じているらしい。IBOutletを指定したらretainしないと思ってたら、間違いだったようだ。

以下のように、__weak指定して、巡回参照を押さえてdeallocを呼ばれるようにした。

@interface RMDocument : NSDocument
{
    NSMutableArray* employees;

    IBOutlet __weak NSTableView*       tableView;
    IBOutlet __weak NSArrayController* employeeController;
}

ARCの導入でコーディング量は減ったけど、考え方は変わらない。 手続き的な表記から宣言的な表記に変わったと理解して@interfaceを書く時にメモリ管理を考えるようにするとよいのかもしれない。

もう一度、@synthesizeとか@propetyの書き方を調べ直さないとダメだな。

]]>
Sat, 21 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/20/1580621c_a848_4870_a3a1_ac29bd3f5b45.html https://www.mindto01s.com/2013/09/20/1580621c_a848_4870_a3a1_ac29bd3f5b45.html <![CDATA[ヒレガス本 13章 課題]]> ヒレガス本 13章 課題

UserDefaultの値を初期値にするボタンを設置せよとの事。

PreferenceController.mに以下のようなコードを追加すれば可能。

- (IBAction) resetPreferences:(id)sender
{
    NSUserDefaults* theDefault = [NSUserDefaults standardUserDefaults];

    [theDefault removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];

    [colorWell setColor:[PreferenceController preferenceTableBgColor]];
    [checkBox setState:[PreferenceController preferenceEmptyDoc]];
}

しかし、バインディングを使って実装し[NSUserDefaultsController revertToInitialValues]を使えばもっと簡単に実装出来る。 ググってもそちらの方はあまりでないのはiOSにNSUserDefaultsControllerが無いからなのかもしれない。

プロジェクトファイルは、 RaiseMan_UserDefault に置いておきます。

]]>
Fri, 20 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/20/7c03d7e5_cf87_4bcf_88ed_af3c6be6eecb.html https://www.mindto01s.com/2013/09/20/7c03d7e5_cf87_4bcf_88ed_af3c6be6eecb.html <![CDATA[デバッグ時に復元機能をOffにする]]> デバッグ時に復元機能をOffにする

MacOSX10.7以降では、アプリケーションにドキュメントの復元機能が追加された。このため、初期状態から起動した時のデバッグが困難になった。

色々と面倒だなと思っていたら、XCodeにその復元機能をOffにするOptionが追加されていた。

スキーム編集ダイアログで、”Run XXX.app” -> “Options” -> “Persistant state”をON/OFFするだけで切り替えられる。

../../../_images/PersistantState.png

便利だ。

参考文献

  • MacOSX Cocoa プログラミング P259
]]>
Fri, 20 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/20/d56ae453_393a_457a_be71_644fd2929ebf.html https://www.mindto01s.com/2013/09/20/d56ae453_393a_457a_be71_644fd2929ebf.html <![CDATA[objective-cの文字列キーの定義]]> objective-cの文字列キーの定義

ヒレガス本第四版の第13章を読んでの感想。 一昔前は、プリプロセッサマクロの#defineを使用していた。最近はグローバル変数を使うのが定石らしい。

XXXXXX_KEYを定義したい場合には、

*.hに

extern NSString* const XXXXXX_KEY

*.mに

extern NSString* const XXXXXX_KEY = @"XXXXXX_KEY";

と書くのがお作法のようです。

自分は#defineのやり方とコチラのやり方を行ったり来たりしていたが、今後はグローバル変数のやり方に統一するつもり。

]]>
Fri, 20 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/20/5f3dffb1_f13c_4f7d_8e8f_66746f2c4699.html https://www.mindto01s.com/2013/09/20/5f3dffb1_f13c_4f7d_8e8f_66746f2c4699.html <![CDATA[ヒレガス本 12章 課題]]> ヒレガス本 12章 課題

課題は、NSBundleを利用してnibをロードし独自のAboutPanelを表示しろとの事。

早速NSBundleを使ってNibをロードしようと、

[NSBundle loadNibNamed: owner:]

を使おうとしたらDeprecatedとの事。

仕方が無いので、NSNibを使用して以下のように書いた。

- (IBAction) showAbouPanel:(id)sender
{
    if( !aboutPane )
    {
        NSNib*    theAboutPanelNib;

        theAboutPanelNib = [[NSNib alloc] initWithNibNamed:@"aboutPanel.nib"
                                                    bundle:nil];

        NSArray* theTopLevelObject;

        if( [theAboutPanelNib instantiateWithOwner:self
                                      topLevelObjects:&theTopLevelObject] )
        {
            NSLog(@"aboutPanel loaded from nib. %@", theTopLevelObject);

        }
    }

    [aboutPane makeKeyAndOrderFront:sender];
}

プロジェクトファイルは、 RaiseMan_NibAndWindowController に置いておきます。

]]>
Fri, 20 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/19/3591f73f_829c_4b62_a0a9_31ad075be3d1.html https://www.mindto01s.com/2013/09/19/3591f73f_829c_4b62_a0a9_31ad075be3d1.html <![CDATA[ヒレガス本 11章 課題]]> ヒレガス本 11章 課題

課題を行ってみてミスを2つした。

  • TableViewに何も表示されない

理由は、NSArrayControllerのManaged Object Contextを設定をし忘れていた。

Managed Object ContextをFile’s OwnerのManagedObjectContextにbindingしする必要があったのに設定を忘れていた。

  • Make/ModelとPriceの編集が出来なかった

理由は、それぞれのTextViewで、Binding PropatyのConditionally Sets EditableをONにしていなかった事が原因だった。

課題その物の、新しいレコードを追加してすぐに編集を開始するには以下のコードを追加した。

//
//  CarArrayController.h
//  CarLot

#import <Cocoa/Cocoa.h>

@interface CarArrayController : NSArrayController
{
    IBOutlet NSTableView* tableView;
}

@end
//
//  CarArrayController.m
//  CarLot

#import "CarArrayController.h"

@implementation CarArrayController

// managedObjectContextに変化があれば managedObjectContextObjectsDidChange:を呼び出し
// tableを編集状態にする

- (void) awakeFromNib
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(managedObjectContextObjectsDidChange:)
                                                 name:NSManagedObjectContextObjectsDidChangeNotification
                                               object:[self managedObjectContext]];
}

// 終了時にobservingを停止する
- (void) dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void) managedObjectContextObjectsDidChange:(NSNotification*)inNotification
{
    NSManagedObject* theObject = [[[inNotification userInfo] valueForKey:NSInsertedObjectsKey] anyObject];

    if( theObject )
    {
        NSUInteger theRow = [[self arrangedObjects] indexOfObject:theObject];

        [tableView editColumn:0
                          row:theRow
                    withEvent:nil
                       select:YES];
    }
}

// これはヒレガス本そのまま
- (id) newObject
{
    id theNewObject = [ super newObject];

    NSDate* theNow = [NSDate date];

    [theNewObject setValue:theNow
                    forKey:@"datePurchased"];

    return theNewObject;
}

@end

しかし、Undo/Redoがうまく働いていないように見える。Undo/Redoで内部データとTableViewのレコードがうまく連動していない。

CoreDataだとUndo/Redoを自動で行うので手を入れなくて良いと思っていたが違うようだ。

前々回の、Cocoa勉強会でCoreDataのUndoManagerについて平井さんが発表していた気がしたのでハンドセットを読み返した。 しかし、デバッグのやり方しか書いていない。

CoreDataの基礎の章なので深くは追求しない事にした。

]]>
Thu, 19 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/18/352cfdd1_2644_424c_a9af_9ef147cd2598.html https://www.mindto01s.com/2013/09/18/352cfdd1_2644_424c_a9af_9ef147cd2598.html <![CDATA[NSArchiverはdeprecated]]> NSArchiverはdeprecated

ヒレガス本でアーカイブ化の章を読んで、NSArchiverの事が一言も出ていなかった。 不思議に思い、ググると Cocoaでのアーカイブとシリアライズ機能 アーカイブ編 が見つかる。

10.3以降はNSArchiverはdeprecatedなのでNSKeyedArchiverを使うべきとの事。

要点は、

  • 新規プリケーションにはNSKeyedArchiverを使え。NSArchiverは後方互換性の為だけに残されている。
  • setVersionも不要。NSKeyedArchiverを使えば必要ない。

との事。歳はとりたくないなぁ。

]]>
Wed, 18 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/09/17/d18683db_bf5e_4ffb_8954_8188a2817402.html https://www.mindto01s.com/2013/09/17/d18683db_bf5e_4ffb_8954_8188a2817402.html <![CDATA[ヒレガス本第四版第9章 NSUndoManagerの覚え書き]]> ヒレガス本第四版第9章 NSUndoManagerの覚え書き

NSInvocationについて

たった半ページでNSInvocationを説明をしている。予備知識が無いと理解出来ない。 NSInvocationを理解するには「詳細Objective-C 2.0第3版」 p385を参照する事。

[NSUndoManager prepareWithInvocationTarget:self]について

[NSUndoManager prepareWithInvocationTarget:self]の返り値はid型です。

この返り値がid型である為に、どのようなメッセージを投げられてもコンパイル時にはエラーにはなりません。

そのため、ヒレガス本の例にあるように簡潔にコードが書けます。

- (void) insertObject:(Person*)inPerson
   inEmployeesAtIndex:(NSUInteger)inIndex
{
    NSUndoManager* theUndoMgr  = [self undoManager];

    // この行はid型に対してメッセージを送っている
    [[theUndoMgr prepareWithInvocationTarget:self]
        removeObjectFromEmployeesAtIndex:inIndex];

    if( ![theUndoMgr isUndoing] )
    {
        [theUndoMgr setActionName:@"Add Person"];
    }

    [employees insertObject:inPerson atIndex:inIndex];
}

より防衛的に書く場合は、型チェックをすり抜ける上記のような書き方はよくない。 以下のように型を指定した書き方をした方がよい。

- (void) insertObject:(Person*)inPerson
   inEmployeesAtIndex:(NSUInteger)inIndex
{
    NSUndoManager* theUndoMgr  = [self undoManager];
    RMDocument*    theInstance = [theUndoMgr prepareWithInvocationTarget:self];

    // RMDocumentの型にする事で凡ミスが減る
    [theInstance removeObjectFromEmployeesAtIndex:inIndex];

    if( ![theUndoMgr isUndoing] )
    {
        [theUndoMgr setActionName:@"Add Person"];
    }

    [employees insertObject:inPerson atIndex:inIndex];
}
]]>
Tue, 17 Sep 2013 00:00:00 +0900
https://www.mindto01s.com/2013/08/30/87822cc0_279e_4efc_bb67_6c053a87fdc8.html https://www.mindto01s.com/2013/08/30/87822cc0_279e_4efc_bb67_6c053a87fdc8.html <![CDATA[CodeSignが”check your system clock”とエラーを吐いたら時間を再設定する]]> CodeSignが”check your system clock”とエラーを吐いたら時間を再設定する

xcodebuildをVMWareで作った仮想環境で実行したらcodeSignがまたエラーを吐いてうまく行かない。

エラーメッセージを見ると、時刻が合ってないとの事。

仕方が無いので、GUI経由でログインしてシステム環境設定>日付と時刻を開いた。自動で再設定されたよ。

次はうまくビルドされたが、GUIでログインしなければならないのはメンドクサイ。

VMWareを立ち上げっぱなしにしてれば良いのだろうか?

]]>
Fri, 30 Aug 2013 00:00:00 +0900
https://www.mindto01s.com/2013/08/28/d9fd1536_c671_443a_98e6_f406e904e7ab.html https://www.mindto01s.com/2013/08/28/d9fd1536_c671_443a_98e6_f406e904e7ab.html <![CDATA[CodeSignが”User interaction is not allowed.”とエラーを吐いたら”security unlock-keychain”を実行する]]> CodeSignが”User interaction is not allowed.”とエラーを吐いたら”security unlock-keychain”を実行する

xcodebuildをVMWareで作った仮想環境で実行したらcodeSignがエラーを吐いてうまく行かない。

ググると、keychainをアンロックする必要があるとの事だった。

GUI経由で行うと問題が無かったのはkeyChainのダイアログが出て来たのでそこで解決が出来ていたのだろう。

ssh経由でkeyChainのアンロックは

security unlock-keychain

を行えば良い。

]]>
Wed, 28 Aug 2013 00:00:00 +0900
https://www.mindto01s.com/2013/08/28/c7320971_34eb_47d5_92ed_b59603c1826e.html https://www.mindto01s.com/2013/08/28/c7320971_34eb_47d5_92ed_b59603c1826e.html <![CDATA[git-ignoreの設定]]> git-ignoreの設定

ディレクトリを掘るたびに、色々と設定していたが、サブディレクトリのファイルを一括で無視する方法がはじめか有った。

「**/」で、サブディレクトリにたいして再帰的に適応してくれるそうです。

# .gitignore
**/build/
**/.DS_Store
**/*.xcodeproj/*
!**/*.xcodeproj/project.pbxproj

ディレクトリを掘るのが楽になる。

]]>
Wed, 28 Aug 2013 00:00:00 +0900
https://www.mindto01s.com/2013/08/23/66ee0d42_ecd8_42fd_a179_36099448a121.html https://www.mindto01s.com/2013/08/23/66ee0d42_ecd8_42fd_a179_36099448a121.html <![CDATA[shellでMacOSXのバージョンを調べる]]> shellでMacOSXのバージョンを調べる

sw_versを使う。unameよりも便利。

バージョン番号の取得

$ sw_vers -productVersion
10.8.4

ビルド番号の取得

$ sw_vers -buildVersion
12E55

さらにバージョン番号を分解して扱いやすくするには、以下のようにcutを使えば良い。

  • メジャー
$ sw_vers -productVersion | cut -f1 -d "."
10
  • マイナー
$ sw_vers -productVersion | cut -f2 -d "."
8
  • リビジョン
$ sw_vers -productVersion | cut -f3 -d "."
4

実際に使うのはマイナー番号だろうな。

]]>
Fri, 23 Aug 2013 00:00:00 +0900
https://www.mindto01s.com/2013/08/22/6f63c8d2_b345_4789_8ff9_f9d93bd03ef0.html https://www.mindto01s.com/2013/08/22/6f63c8d2_b345_4789_8ff9_f9d93bd03ef0.html <![CDATA[xcode-select]]> xcode-select

2つのバージョンのxcodeをインストールしてハマった。

makeファイルからxcodebuildを呼出しているのだが古いxcodeの設定で実行されていてコマンドラインからうまくコンパイル出来なかった。

xcodebuildのオプションを使うのかもしれないと、少ししらべるとxcode-selectなるコマンドを発見。

xcode-select: Report or change the path to the active
              Xcode installation for this machine.

Usage: xcode-select --print-path
           Prints the path of the active Xcode folder
   or: xcode-select --switch <xcode_path>
           Sets the path for the active Xcode folder
   or: xcode-select --version
           Prints the version of xcode-select

DP版のxcodeを使うには

xcode-select --switch /Applications/Xcode5-DP4.app/Contents/Developer

を入れれば使える。元に戻すには、

xcode-select --switch /Applications/Xcode.app/Contents/Developer

を入れれば良い。

]]>
Thu, 22 Aug 2013 00:00:00 +0900
https://www.mindto01s.com/2013/08/04/3021fddb_75e6_45ff_8036_cd397c4d2c09.html https://www.mindto01s.com/2013/08/04/3021fddb_75e6_45ff_8036_cd397c4d2c09.html <![CDATA[Cocoaの添字演算子”[]”のオーバーロード]]> Cocoaの添字演算子”[]”のオーバーロード
  • 数値による添字演算子のオーバーロード

以下のメソッドを実装しているとObj[nnn]が使えるようになる。

- (id)objectAtIndexedSubscript:(NSUInteger)idx;
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
  • 文字列による添字演算子のオーバーロド

以下のメソッドを実装しているとobj[@”string”]が使えるようになる。

- (id)objectAtKeyedSubscript:(id <NSCopying>)key;
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;

NSArrayとNSDictonary以外でどのように使うべきかは不明。C++のように超絶技巧で使うときも有るのかもしれない。

]]>
Sun, 04 Aug 2013 00:00:00 +0900
https://www.mindto01s.com/2013/07/31/40a06225_8c7b_4904_85bc_0bfd06cfe4be.html https://www.mindto01s.com/2013/07/31/40a06225_8c7b_4904_85bc_0bfd06cfe4be.html <![CDATA[KVCでObjectからまとめて値の参照と設定が出来るメソッド]]> KVCでObjectからまとめて値の参照と設定が出来るメソッド

KVCでObjectからまとめて値の参照と設定が出来るメソッドが何処かの本に書いてあった記憶が有ったので、本棚をひっくり返す。

翔泳社の”入門 Objective-C 2.0” p290からキーバリューコーディングのバッチ処理として説明が有った。

  • ObjectへNSDictonaryにあるキーと名前が一致するプロパティに値を入れるメソッド
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
  • ObjectからNSArrayにあるキーに一致する名前のプロパティーの値でNSDictonaryを作るメソッド
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;

リフトアンドスタンンプツール等の作成に使えるとの事でした。多分インスペクタパネルのような物にも使えると思う。

参考文献

翔泳社の”入門 Objective-C 2.0” p290

]]>
Wed, 31 Jul 2013 00:00:00 +0900
https://www.mindto01s.com/2013/07/30/0897e4da_e24f_4691_b8d7_fc4cecb85115.html https://www.mindto01s.com/2013/07/30/0897e4da_e24f_4691_b8d7_fc4cecb85115.html <![CDATA[運転免許の書き換えに行く]]> 運転免許の書き換えに行く

平日に行ったらすごい空いてた。五年後も平日に行こう。

]]>
Tue, 30 Jul 2013 00:00:00 +0900
https://www.mindto01s.com/2013/07/29/a086e9e2_a629_435f_a226_4473d81c91c0.html https://www.mindto01s.com/2013/07/29/a086e9e2_a629_435f_a226_4473d81c91c0.html <![CDATA[ペーパードライバー教習に行った]]> ペーパードライバー教習に行った

10年近くハンドルを握っていなかったのだが、以外と運転出来て驚いた。

しかし、最初に教習用のコースを運転出来て良かった。ブレーキとアクセルの位置を忘れていた。

いきなり、自分で運転していたら暴走させていたかも。:-)

]]>
Mon, 29 Jul 2013 00:00:00 +0900
https://www.mindto01s.com/2013/05/23/8d7ff396_bc0d_4b42_9004_8ea4e063b7d5.html https://www.mindto01s.com/2013/05/23/8d7ff396_bc0d_4b42_9004_8ea4e063b7d5.html <![CDATA[キューとタイマーをくっつけたクラス]]> キューとタイマーをくっつけたクラス

よくある疑似イベントループもどきのクラスを書いた。

テスト用に書いたので実用になるかどうかは不明。

使い方は、こんな感じ。

{
        .
        .
        .
    timerQueue = [[MTLTimerQueue timerQueueWithTarget:self
                                               action:@selector(logObject:)
                                        firstInterval:1.0
                                     continueInterval:0.1] retain];

    [timerQueue enqueue:@"+1"];
    [timerQueue enqueue:@"+2"];
    [timerQueue enqueue:@"+3"];
    [timerQueue enqueue:@"+4"];
    [timerQueue enqueue:@"+5"];
        .
        .
        .
}



- (IBAction) logObject:(id)sender
{
    NSLog(@"theObject => %@", sender);
}

この場合は、キューに、値を積んで1.0秒後に、0.1秒毎に積んだ値を取り出して出力する。

firstIntervalとcontinueIntervalで時間を調整する。

continueIntervalがマイナスの値の場合は、イベントループを回さないで一気に取り出す。

MTLTimerQueue.zip.

]]>
Thu, 23 May 2013 00:00:00 +0900
https://www.mindto01s.com/2013/05/18/b34796d2_2252_4857_9e5f_2b40d5cd4d14.html https://www.mindto01s.com/2013/05/18/b34796d2_2252_4857_9e5f_2b40d5cd4d14.html <![CDATA[Cocoa勉強会資料(5/18用)追加]]> Cocoa勉強会資料(5/18用)追加

18日に行うCocoa勉強会の資料の追加です。

aNoKeyboard.zip.

内容は、kextまわりの作り方みたいな。

]]>
Sat, 18 May 2013 00:00:00 +0900
https://www.mindto01s.com/2013/05/16/47883dc9_b117_4a47_b2b5_65d7ef723ce8.html https://www.mindto01s.com/2013/05/16/47883dc9_b117_4a47_b2b5_65d7ef723ce8.html <![CDATA[Cocoa勉強会資料(5/18用)]]> Cocoa勉強会資料(5/18用)

18日に行うCocoa勉強会の資料です。

CocoaOfCoC.zip.

内容は、Cocoaで設定より規約をやってみたみたいな。

]]>
Thu, 16 May 2013 00:00:00 +0900
https://www.mindto01s.com/2013/04/20/2c5d8558_665e_4966_ba19_dfb7bd782743.html https://www.mindto01s.com/2013/04/20/2c5d8558_665e_4966_ba19_dfb7bd782743.html <![CDATA[OSBundleLibraries]]> OSBundleLibraries

kextを作る時、info.plsitに手作業で依存するライブラリを記述する必要がある。

OSBundleLibrariesにライブラリの識別子と互換性がある最低バージョン版番号を記述する。

例えばこんな感じ。

<key>OSBundleLibraries</key>
<dict>
        <key>com.apple.kpi.iokit</key>
        <string>12.3</string>
</dict>

この時、どのライブラリのどのバージョンに依存しているかは、”kextlibs”コマンドで確認する事が出来る。

例えばこんな感じ。

$ kextlibs PATH/FILE.kext
        For all architectures:
        com.apple.kpi.iokit = 12.3

いちいち手作業でライブラリの依存関係を調べるのは面倒くさいので、一度ビルドした後に”kextlibs”コマンドで、依存関係を出力させて、plsitに転記するのが楽。

また、”kextload”時に

/PATH/FILE.kext - no compatible dependency found for com.apple.kpi.iokit.

とか

Check library declarations for your kext with kextlibs(8).

等のエラーが出た場合も、悩まずに”kextlibs”ですぐにライブラリのidとバージョンを出力させて、info.plsitを確認すると良い。

]]>
Sat, 20 Apr 2013 00:00:00 +0900
https://www.mindto01s.com/2013/04/15/5b5ba02a_d02a_4508_bf64_46de99238f49.html https://www.mindto01s.com/2013/04/15/5b5ba02a_d02a_4508_bf64_46de99238f49.html <![CDATA[2ボタンキーボード]]> 2ボタンキーボード

週間アスキー で紹介されていた、英数/かなキーボード for Macintosh をパクってArduinoで作ってみた。

ハードウェア

ハードウェアは2つのボタンを”Arduino Leonardo”のPin13とPin12につないだだけ。

プルアップ抵抗もソフトウェアでONにするので、ハードウェア側には無し。

作図がメンドクサイので図は無し。

ソフトウェア

Arduino IDEに付属のKeyboardライブラリだと「英数/かな」キーコードが送れない仕様だった。

生のキーコードが送れるソースコード が公開されていたので、そのコードを利用して以下のようなコードを書いた。

#include "USBrawkeyboard.h"

USBrawkeyboard rawkybd;

#define RIGHT_KEY_CODE 0x90
#define LEFT_KEY_CODE  0x91

const int kRightSwitch = 12;
const int kLeftSwitch  = 13;

int gPreviousRightSwitchState = 0;
int gPreviousLeftSwitchState  = 0;


void setup()
{
  pinMode(kRightSwitch, INPUT_PULLUP);
  pinMode(kLeftSwitch, INPUT_PULLUP);

  delay(1000);

  gPreviousRightSwitchState = digitalRead(kRightSwitch);
  gPreviousLeftSwitchState  = digitalRead(kLeftSwitch);

  Keyboard.begin();
  rawkybd.begin() ;

  delay(1000);
}

void loop()
{
  int theRightSwitchState = digitalRead(kRightSwitch);
  int theLeftSwitchState  = digitalRead(kLeftSwitch);

  if( theRightSwitchState != gPreviousRightSwitchState )
  {
    if( theRightSwitchState == LOW )
    {
      rawkybd.press(RIGHT_KEY_CODE);
    }
    else
    {
      rawkybd.release(RIGHT_KEY_CODE);
    }
    gPreviousRightSwitchState = theRightSwitchState;
  }


  if( theLeftSwitchState != gPreviousLeftSwitchState )
  {
    if( theLeftSwitchState == LOW )
    {
      rawkybd.press(LEFT_KEY_CODE);
    }
    else
    {
      rawkybd.release(LEFT_KEY_CODE);
    }

    gPreviousLeftSwitchState =      theLeftSwitchState;
  }

  delay(100);
}

コチラからダウンロード出来ます。 sketch_apr14a.zip

今のところ入力モードの切り替えしか出来ないのでホンノリ便利に使えるMacOSX側のユーティリティーが出来ないか妄想中。

参考文献

Arduino IDEに付属のKeyboardライブラリだと生のUSBキーコードが送れない仕様だったので、このサイトにあるソースコードを流用させていただきました。
ここ以外に「英数/かな」のキーコードが説明されているサイトは見つけられなかった。
]]>
Mon, 15 Apr 2013 00:00:00 +0900
https://www.mindto01s.com/2013/04/10/963cc363_efc2_4cad_8eb9_6a9a34c9dc64.html https://www.mindto01s.com/2013/04/10/963cc363_efc2_4cad_8eb9_6a9a34c9dc64.html <![CDATA[tinker運用ノート(3)]]> tinker運用ノート(3)

tinkerを運用するにあたっての覚え書き

tinker運用ノート(1)tinker運用ノート(2) の続き

makeファイルを作って、よく使うコマンドをまとめる

アップロードしたり、プレビューしたりするのに毎回長いコマンドを打つのは面倒なのでMakefileにまとめる。こんな感じ。

PN := $(shell uuidgen)

all : clean build

clean :
    rm -rf ./blog/

cat :
    find . -name "*.rst" | xargs grep -e '^.. categories::' | cut -d':' -f4 | sort | uniq | tr -d ' '

tag :
    find . -name "*.rst" | xargs grep '^.. tags::' | cut -d':' -f4 | tr ',' '\n' | sort | uniq | tr -d ' '

build :
    tinker -b

preview : build
    open index.html

newPost :
    open `tinker -f -p "$(PN)"`


upload : clean build
    rsync -av --exclude=.DS_Store --exclude=.buildinfo blog/html/ USER@EXAMPLE.COM:/HTML/DIR/PATH/

先日の MacPortsでbash-completion と組み合わせると、そのディレクトリだけで有効なコマンドが出来たみたいで快適。

uploadはサーバーへアップロードするコマンド。

newPostはblogに新しい記事を追加するコマンド。私の場合は、内容を書いてからタイトルを決める事が多いので、デフォルトではページ名はuuidで生成された文字列にしました。 任意の名前にしたい場合は”PN=TitleString”を引数に付けると良い。日本語は不可。

]]>
Wed, 10 Apr 2013 00:00:00 +0900
https://www.mindto01s.com/2013/04/09/cb30e421_d9c5_41ec_8e80_401f006f6227.html https://www.mindto01s.com/2013/04/09/cb30e421_d9c5_41ec_8e80_401f006f6227.html <![CDATA[tinker運用ノート(2)]]> tinker運用ノート(2)

tinkerを運用するにあたっての覚え書き

tinker運用ノート(1) の続き

リンクの張り方

内部リンクの方法

拡張子なしでファイル名を書く。

適当な文 :doc:`sample/index` と書くと、sample/index.rstへジャンプするリンクを書ける。

と書く。リンク文の前後に空白を入れる事を忘れると認識してくれない。

絶対パスの例

:doc:`/2013/01/01/doubutunomori`

相対パスの例

:doc:`../../01/01/doubutunomori`

外部リンクの方法

`Sphinxを知りたい方はこちらをクリック <http://sphinx-users.jp>`_

と書く。URLの直書きもOK。

リンク文の前後に空白を入れる事を忘れると認識してくれない。

詳しくは Sphinxのサイト を参照する事。

]]>
Tue, 09 Apr 2013 00:00:00 +0900
https://www.mindto01s.com/2013/04/09/e07883ff_504a_417b_922b_dfb4bce4c4f1.html https://www.mindto01s.com/2013/04/09/e07883ff_504a_417b_922b_dfb4bce4c4f1.html <![CDATA[MacPortsでbash-completion]]> MacPortsでbash-completion

makeファイルのターゲッットをコマンドライン補完したくなった。

調べるとbash-completionと呼ばれるスクリプトをインストールすると良いらしい事まで判った。

MacPortsでインストールしようと考えたら、デフォルトで入っているbashのバージョンが古いのでMacPotsで新しいbashに切り替える必要があるとの事。

面倒だなと思いつつインストールして設定する。

結果。大変快適。自動補完が無い環境にはもう戻れない。

  • 参考URL

How to use bash-completion

]]>
Tue, 09 Apr 2013 00:00:00 +0900
https://www.mindto01s.com/2013/04/08/3bd7f359_46bc_4420_ae45_db36b8386fc4.html https://www.mindto01s.com/2013/04/08/3bd7f359_46bc_4420_ae45_db36b8386fc4.html <![CDATA[ヒレガス本 課題8-2]]> ヒレガス本 課題8-2

ヒレガス本第四版の課題8-2を読んで、この課題は10分も掛からないだろうと見積もりコーディングを始めるたが、デバッグに1時間もかかった。

ミスをした箇所は以下の通り。

  • NSButtonからtarget/actionの設定を忘れていた
凡ミスだが、何度もボタンを押してしまった。最初に接続忘れを疑うべきだった。
  • プロトコルのスペルミス

NSTableViewDataSourceプロトコルの一部をスペルミスしていた。実装がoptionalだと警告が発生しないのでミスを発見しにくい。

対策は、NSTableView.hから該当するプロトコルメソッドをCopy&Pasteするのが一番だと思う。

  • [NSTableView reloadData]の呼出し忘れ
arrayへの追加削除の後には、reloadDataしないと画面がアップデートしない。
  • IB上でidentifierの設定忘れ
tableCulumのidentifierを見て表示する内容を選んでいるのに、identifierが空だった。 identifierが空の時にassertを発生させた方が良いかも。
  • tableCulumのidentifierをデータのKVCの名前に一致させるべきだったのを別の名前を作ってしまった。
わざわざ変換ようのディクショナリを作ってから気がついた。CoreDataが出てくるまでは定型文だったのだがすっかり忘れていた。
]]>
Mon, 08 Apr 2013 00:00:00 +0900
https://www.mindto01s.com/2013/04/08/keyvaluecoding.html https://www.mindto01s.com/2013/04/08/keyvaluecoding.html <![CDATA[KeyValueCodingのメモ]]> KeyValueCodingのメモ

ヒレガス本の復習をして自分の間違いに気がついた。

今までアクセッサメソッドを手書きする時に、必ず[NSObject willChangeValueForKey:]と[NSObject didChangeValueForKey:]を呼出していた。

KVOでそのプロパティが自動変更通知を使用している場合には不要だった。

プロパティーをKVCやドット表記でアクセスした場合だけでなく、アクセッサメソッドにも自動で変更通知をする。

おそらく内部で、メソッドの置換えを行っていて、アクセッサメソッドの前後に[NSObject willChangeValueForKey:]と[NSObject didChangeValueForKey:]の呼び出しを追加しているようだ。

なお、パフォーマンスの改善等の理由でより細かく通知を制御したい場合は手動変更通知と呼ばれる方法が存在する。

+ (BOOL) [NSObject automaticallyNotifiesObserversForKey:(NSString *)inKey]

をオーバーライドして、手動変更通知したいプロパティ名でNOを返すようにすると、そのプロパティ名は手動変更通知と見なされる。

手動変更通知にしたプロパティ名でKVOに対応させるには、アクセッサメソッドでwiiChangeValueForKeyとdidChangeValueForKeyの対を呼ばなければならない。

すいません。いままで知りませんでした。 今までかなりパフォーマンスが悪いプログラムを各地で書いていたようです。 本当にすいません。

参考文献
ADCのKeyValueObserving.pdfとKeyValueCoding.pdfとヒレガス本
]]>
Mon, 08 Apr 2013 00:00:00 +0900
https://www.mindto01s.com/2013/03/07/20_years_old.html https://www.mindto01s.com/2013/03/07/20_years_old.html <![CDATA[20年物]]> 20年物

病院帰りに図書館へ寄った。書庫に保管されている20年程前の本を借りて家に帰った。

家でおもむろにページを捲ると、随所に鼻毛が挟まっていた。汚い。そう思ったが、気を取り直して読み進める。

しかし、その量が多過ぎた。

最初は、見開きに1本程の量で有ったが、読み進めて行くと1ページに4本程の量が挟まっているページが有る。

もの凄く汚い、読む気も失せた。気持ち悪いので、本をポリ袋に入れて部屋の片隅に置いた。

土日に返却しに行くつもり。何だか悲しい。

]]>
Thu, 07 Mar 2013 00:00:00 +0900
https://www.mindto01s.com/2013/03/02/tinker_note_01.html https://www.mindto01s.com/2013/03/02/tinker_note_01.html <![CDATA[tinker運用ノート(1)]]> tinker運用ノート(1)

tinkerを運用するにあたっての覚え書き

restの書き方

sphinxのページより引用し、自分が使いそうな物だけを列挙する。

表の書き方

空の行
.. csv-table:: 表のタイトル
    :header: "ヘッダの", "タイトル"
    :widths: 25, 75
空の行
    "セルA1", "せるB1"
    "セルA2", "せるB2"

と書くと

表のタイトル
ヘッダの タイトル
セルA1 せるB1
セルA2 せるB2

の様に表示される。

コードの書き方

空の行
.. code-block:: rest
空の行
   コード1
   コード2
   コード3
空の行

と書くと

コード1
コード2
コード3

の様に表示される。

ダウンロード可能なファイルの指定

:download:`EnablerTest.zip <EnablerTest.zip>`

と書くとダウンロード用のリンクが出来る。インラインで書ける。多分前後に空白が必要。 指定したファイルが無いと、build時にエラーが出る。

画像の置き方

空の行
   .. image:: ControlEnabler.png
       :scale: 50%
空の行

と書くと画像を置ける。 指定したファイルが無いと、build時にエラーが出る。

書いた文書から情報を引き出す

カテゴリの種類

カテゴリ一覧を取得するには。()の中はメモです、出力されないです。

$ find . -name "*.rst" | xargs grep 'categories::' | cut -d':' -f4 | sort | uniq | tr -d ' '

discovery  <-- (発見)
reference  <-- (引用)
study      <-- (学習)
note       <-- (雑記)
article    <-- (記事)

タグの種類(複数可能)

タグ一覧を取得を取得するには

$ find . -name "*.rst" | xargs grep 'tags::' | cut -d':' -f4 | tr ',' '\n' | sort | uniq | tr -d ' '

xcode
cocoa
ditzX
graphics
etc
]]>
Sat, 02 Mar 2013 00:00:00 +0900
https://www.mindto01s.com/2013/02/16/basic_design_01.html https://www.mindto01s.com/2013/02/16/basic_design_01.html <![CDATA[グラフィックデザインの勉強 その1]]> グラフィックデザインの勉強 その1

ソフトウェア開発の分野では、デザインというと設計の事だが、世間一般ではグラフィックデザインの事を言うらしい。

設計は仕事でやった事はあるが、グラフィックデザインはやった事も無いし苦手。

素人なりに基本だけでも覚えておこうと本を買って来た。署名は「デザインの教室 手を動かして学ぶデザイントレーニング」。

副題の”手を動かす”との事だが、最初の1章は座学だった。

まとめノート

私の知識で似た物を探すと、グラフィックデザインの基本はInterfaceBuilderの各種レイアウト機能に似てる。 しかし対比と余白の項目はIB上に対応する機能はない。

  • 特徴点
    オブジェクトの上下左右の端や中点。(どのように決めるかは言及していない)
  • 補助線
    特徴点を通るX軸またはY軸に平行な直線。
  • 整列
    異なるオブジェクトの特徴点同士をX軸やY軸方向に合わせる事。
  • グリッドシステム
    グリッドに従いオブジェクトを配置する。変化をしつつ全体の統一感を出せる。
  • 繰返し
    異なるオブジェクトの特徴点の間隔を等間隔にする事。繰返しを知覚させるには3回以上の繰返しが必要。
  • 対比
    画像と本文や黒と白等とページをエリア分轄し、それぞれの部分に明確な役割を与える。メリハリが効いて良いらしい。
  • 余白
    強調する場所のまわりに余白をとる。強調する箇所を目立つ色や大きさにするのも良いが限界がある。余白によって強調効果が出せる。
  • ルールを作る
    上記の要素を組み合わせて、局所的な俺様ルールを作るようにすると良いらしい。

疑問

  • 特徴点はどうやって決めるのか?
不連続な点や極値の点を特徴点として良いのだろうか?
  • グリッドシステムは構造化出来そう
オブジェクト毎の特徴点の摘出とグリッド配置を再帰的に適応すると、もっと汎用性が高まると思う。

感想とか

これを1週間に2〜3時間程行い半年くらいすれば基本は身に付くのだろうか?道のりが遠過ぎて途中で挫折しそう。

プログラミングの学習と違いデザインの学習はループが閉じていない気がする。

]]>
Sat, 16 Feb 2013 00:00:00 +0900
https://www.mindto01s.com/2013/02/13/source_command.html https://www.mindto01s.com/2013/02/13/source_command.html <![CDATA[sourceコマンド]]> sourceコマンド

unixコマンドをもう一度勉強してみようと思い、本を広げた。 十数ページも読み進まないうちに、初めて知る事が書いてあり、未熟を悟る。

  1. bashのsourceコマンド

    “socrce file”と行うと、そのfileをスクリプトとして実行される。 “bash file”との違いは、新規にプロセスを生成するか否かの違い。

    socrceは同じプロセスでシェルスクリプトを実行する。 bashは子プロセスでシェルスクリプトを実行する。

    この違いにより、socrceは環境変数の上書き等に利用される。

    bashは返り値しか元のプロセスに返さないので環境変数を書き散らかしても問題ない。

  2. bashのシングルクオーテーション(‘)とダブルクオーテーション(“)

    bashではコマンドライン上に特定の文字が有ると、コマンドの評価に先立ってその文字列を展開する。展開される文字は、”*”, “?”, “`”等。

    シングルクオーテーション(‘)とダブルクオーテーション(“)で囲む事は、それらの特殊な文字を展開させずにコマンドに渡す為の手段。これをクォーティングと言う。

    2つのクォーティングの違いは、展開を許す文字列の違い。

    シングルクオーテーションは展開を許さない。

    ダブルクオーテーションは、変数展開”${VAR}”やコマンド展開”`”の展開を許す。

]]>
Wed, 13 Feb 2013 00:00:00 +0900
https://www.mindto01s.com/2013/02/05/compilerflags.html https://www.mindto01s.com/2013/02/05/compilerflags.html <![CDATA[コンパイラフラグ]]> コンパイラフラグ

XCode4でファイル毎のコンパイルフラグを設定するには以下の場所で設定する。

../../../_images/complierFlag.png

実際に使うのは以下の2つくらい。

  • arcをファイル毎にON/OFFする時
“-fobjc-arc” / “-fno-objc-arc”
  • 最適化をファイル毎に変更する時
“-O0”(なし)から”-o3”(速い)、あるいは”-Os”(速くて小さい)。

プロジェクト全体を”-Os”が設定されている時、デバッグがうまく出来ない事がある。 そんな時に、その特定のファイルだけ”-O0”にすると良いかもしれない。

]]>
Tue, 05 Feb 2013 00:00:00 +0900
https://www.mindto01s.com/2013/02/04/singleton.html https://www.mindto01s.com/2013/02/04/singleton.html <![CDATA[Objective-Cのsingleton]]> Objective-Cのsingleton

「Objective‐Cフレーズブック」のSingletonの部分の記述が面白い。

p75より

Appleのドキュメントでは、シングルトンを作成する為に、+sharedInstanceメソッドの中で@synchronized(sefl)を使用する事を推奨しています。 そのアプローチは著しく遅く、このセクションで提案したアプローチよりも多くのコードが必要なため、あまりお勧めしません。

との事。

Appleのやり方、「シングルトンインスタンスの作成」 を見ると

static MyGizmoClass *sharedGizmoManager = nil;

+ (MyGizmoClass*)sharedManager
{
    @synchronized(self) {
        if (sharedGizmoManager == nil) {
            [[self alloc] init]; // ここでは代入していない
        }
    }
    return sharedGizmoManager;
}

+ (id)allocWithZone:(NSZone *)zone {
{
    @synchronized(self) {
        if (sharedGizmoManager == nil) {
            sharedGizmoManager = [super allocWithZone:zone];
            return sharedGizmoManager;  // 最初の割り当てで代入し、返す
        }
    }
    return nil; // 以降の割り当てではnilを返すようにする
}
.
.
.

となっており、確かに+ (MyGizmoClass*)sharedManagerで@synchronized(self)を使用している。

一方、「Objective‐Cフレーズブック」のやり方は、

static MyGizmoClass *sharedGizmoManager = nil;

+ (void) initialize
{
    if( [MyGizmoClass class] == self )
    {
        sharedGizmoManager = [self new];
    }
}

+ (MyGizmoClass*)sharedManager
{
    return sharedGizmoManager;
}

+ (id)allocWithZone:(NSZone *)zone {
{
    if( sharedGizmoManager && [MyGizmoClass class] == self )
    {
        [NSException raise:NSGenericException
                    format:@"May not create more than one instance of singleton."];
    }

    return [super allocWithZone:zone];
}
.
.
.

となっており、@synchronizedがなく、全体的にすっきりしたコードだ。

しかし、脚注にGCCのランタイムにはバグがあって「Objective‐Cフレーズブック」のやり方は動作しないような事が書いてある。

結論としては、gccを使う場合はAppleの推奨する方法を使うのが良いとの事だろうか。

]]>
Mon, 04 Feb 2013 00:00:00 +0900
https://www.mindto01s.com/2013/02/02/method_name_synthesis2.html https://www.mindto01s.com/2013/02/02/method_name_synthesis2.html <![CDATA[メソッド名合成を使ったプログラミング(1)]]> メソッド名合成を使ったプログラミング(1)

「メソッド名合成を使ったプログラミング(1)」の続き

メソッド名合成プログラミングその2

Cocoaライブラリの例以外には、Cocoa勉強会 関東57回目で発表した「五年後のControlEnabler」でもメソッド名合成を使ってプログラミングしている。

このControlEnablerでは、target-actionパラダイムを利用して、NSControlのサブクラスの挙動を変更する仕組みです。

target-actionパラダイムでは、変数targetによって示されるオブジェクトへ変数actionによって示されるメソッドを呼出します。

関係は以下の図のように、ボタンからコントローラへの一方通行。

../../../_images/target-action.png

ここで、ControlEnablerを導入しすると、windowのupdateのタイミングでボタンはコントローラのデータを読み取り、ボタンの挙動を変更する。

../../../_images/ControlEnabler.png

target-actionパラダイムでtargetとして指定されたオブジェクトは、actionを実行出来るのだから”おそらく”actionに関する他の事柄も知っているだろう、言う前提の仕組みです。

ここで、コントローラにactionメソッドとして “anAction:”が有るとすると、以下のメソッドを定義する事でbuttonの挙動を変更出来ます。

- (IBAction) anAction:(id)sender;        // action
- (BOOL)     canAnAction:(id)sender;     // actionを呼出すボタンの有効無効を制御
- (NSString*)titleOfAnAction:(id)sender; // actionを呼出すボタンのタイトルを制御

ここで、上記のように”anAction:”を共通の文字列として、can<action名>、titleOf<action名>といったメソッド名を合成して、update毎に呼出してその値をボタン自身に適応する。

ControlEnablerでは、このようなメソッド名合成を使ったプログラミングを使用している。

メソッド名合成プログラミングその3

その他にも状態遷移クラス等の設計等で使えると思うけど、作ってません。

“<状態名>Enter”や”<状態名>Exit”等のメソッド名を合成してデリゲートを呼出せば、switch分が減る。

]]>
Sat, 02 Feb 2013 00:00:00 +0900
https://www.mindto01s.com/2013/02/01/heavy_novel.html https://www.mindto01s.com/2013/02/01/heavy_novel.html <![CDATA[ヘビーノベル]]> ヘビーノベル

ライトノベルがあるならヘビーノベルもあるのかもしれない。

大江健三郎がQJをシャウトする、そんなイメージ。

]]>
Fri, 01 Feb 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/31/nsinvocation_addition2.html https://www.mindto01s.com/2013/01/31/nsinvocation_addition2.html <![CDATA[「NSInvocationをカテゴリで拡張した」の修正]]> 「NSInvocationをカテゴリで拡張した」の修正

以前の、「NSInvocationをカテゴリで拡張した」の修正。

詳解Objective-C 2.0 第三版 P537より、KVCで自動変換出来る構造体は、

自動的に受け渡し出来るのはCocoaで標準的に使われている四種類の構造体 NSPoint, NSRange, NSRect, NSSizeに限られています。

との事なので、NSPoint, NSRange, NSRect, NSSizeの四つの構造体をサポートするように変更する。

if文の固まりの末尾に、以下を追加した。

else if( 0 == strcmp(theReturnType, @encode(NSPoint)) )
{
    theResult = [NSValue valueWithPoint:*(NSPoint*)thePtr];
}
else if( 0 == strcmp(theReturnType, @encode(NSSize)) )
{
    theResult = [NSValue valueWithSize:*(NSSize*)thePtr];
}
else if( 0 == strcmp(theReturnType, @encode(NSRect)) )
{
    theResult = [NSValue valueWithRect:*(NSRect*)thePtr];
}
else if( 0 == strcmp(theReturnType, @encode(NSRange)) )
{
    theResult = [NSValue valueWithRange:*(NSRange*)thePtr];
}
else
{
    theResult =  [NSValue valueWithBytes:thePtr
                                objCType:theReturnType];
}

以上のサポートでKVCで標準でサポートしている型の自動変換をサポートしたハズ。

]]>
Thu, 31 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/30/method_name_synthesis.html https://www.mindto01s.com/2013/01/30/method_name_synthesis.html <![CDATA[メソッド名合成を使ったプログラミング(1)]]> メソッド名合成を使ったプログラミング(1)

Objective-Cでは文字列型NSStringとセレクタ型SELを相互に変換出来る。この変換機能で実行時に生成した文字列からメソッドを呼び出す事が可能になる。

スクリプト言語では当たり前の機能だが、コンパイル言語でこのような機能があるのは少ない。

ここで、文字列からメソッド名を合成する事、その合成したメソッドを積極的に使ったコーディングをそれぞれ、「メソッド名合成」及び「メソッド名合成プログラミング」と命名する事にする。

メソッド名合成

文字列型NSStringとセレクタ型SELの相互変換関数として、以下の2つがある。

NSString *NSStringFromSelector(SEL aSelector);
SEL NSSelectorFromString(NSString *aSelectorName);

文字列からSEL型を作るのはもちろん、SEL型も文字列型に変換後は編集可能になる。編集した文字列型を再びSEL型に戻せば、SEL型の編集も可能と見なせる。

単純な例として、プロパティ名からsetterメソッドを合成する関数を示す。

SEL setterSelectorFromName(NSString* inName)
{
    NSString* theResultString;

    theResultString = [NSString stringWithFormat:@"set%@:",
                    [inName stringByReplacingCharactersInRange:NSMakeRange(0,1)
                                                    withString:[[inName substringToIndex:1]
                                                                uppercaseString]]];

    return NSSelectorFromString(theResultString);
}

この関数は、@”test”を渡すと@selector(setTest:)を返す動作をする。

合成したメソッドの呼び出し

プロトコルや非公式プロトコルのメソッドと同じように、合成したメソッドは対象となるオブジェクトにメソッドの実装があるとは保証されていない。

よって、セレクタが呼び出し可能かを確認後呼び出しをしなければならない。

コードとしては、以下のようになる。

if( [self respondsToSelector:theSelector] )
{
    [self performSelector:theSelector
               withObject:theObject];
}

これはデリゲートやプロトコルを使ったプログラミングと同じようになる。

すこしだけ異なるのは、プロトコルと違いメソッド名は動的に決まるので、[NSObject performSelector:]やNSInvocationを使ったメソッド呼び出しを行う事になる。

メソッド名合成プログラミング

メソッド名合成プログラミングの応用例がCocoaのフレームワークの中にある。 ここでは、判り易い例としてkey値コーディングのインディックス付きアクセッサを示す。

インディックス付きアクセッサは以下の2つのメソッドをサポートする必要がある。

- (NSInteger) countOf_KEY_;
- (id) objectIn_KEY_AtIndex:(NSInteger)index;

ここで、”_KEY_”はkey値コーディングでいうキー文字列が入る。

この2つのメソッドを定義すると、そのオブジェクトはkey値コーディング上ではプロパティ_KEY_を持っているように振る舞う事が出来る。

プロトコルと異なり、_KEY_は任意の文字列に出来る。実行時に対で実装されていれば実行出来る。

同じような例として、プロパティ値の検証用メソッドがある。

- (BOOL) validate_KEY_:(id*)ioValue
                 error:(NSError**)outError;

ここでも、”_KEY_”はkey値コーディングでいうキー文字列が入る。

これらの例のように、Cocoaでは「メソッド名合成を使ったプログラミング」は既に使われている。

つづく。

]]>
Wed, 30 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/29/validateuserinterfaceitem_8.html https://www.mindto01s.com/2013/01/29/validateuserinterfaceitem_8.html <![CDATA[validateuserinterfaceitem(8)]]> validateuserinterfaceitem(8)

NSUserInterfaceValidationsプロトコルは、CocoaのUIの状態を更新する仕組みを提供するプロトコルだ。

UIにはNSToolbar、NSMenu、NSView、NSControlのサブクラス群が含まれる。

メニューのenable/disableやチェックマークを付ける等の状態を更新する、OpenStep時代のNSMenuActionResponder非公式プロトコルを発展させた物だと思われる。

NSUserInterfaceValidationsプロトコルは、NSToolbar用のNSToolbarItemValidationプロトコルや、NSMenu用のNSMenuValidationプロトコルを統一的に扱う為の物なのだろう。

ここで問題なのは、NSToolbarやNSMenuにはautovalidates等のプロパティがあり、自動で更新処理を行う事が出来る。

しかし、NSViewやNSControlには自動で更新を行う仕組みは無い。

当初、私は個々のNSControlのサブクラスで、”NSWindowDidUpdateNotification”をとらえて更新させるコードを書いていた。

この方式の欠点はNibエディター上でUIを配置する毎に、Class名を書き換える必要があり煩雑である事だった。

この欠点を解消する為に、Notificationを個々のcontrolでとらえるのではなく、NSWindowのupdateメソッドをオーバーライドして、個々のcontrolの状態を更新する方法に変更した。

NSWindowとNSPanelのサブクラスをそれぞれ作る事で実現している。

コードは以下の通り、

MTLUserInterfaceValidationUpdate.zip

]]>
Tue, 29 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/28/nswindow_update.html https://www.mindto01s.com/2013/01/28/nswindow_update.html <![CDATA[NSWindowのupdateメソッドについてのメモ]]> NSWindowのupdateメソッドについてのメモ
  • NSWindowのupdateメソッドは、NSWindowDidUpdateNotificationnを通知センターにポストする以外の処理は行わない。
  • NSApplicationのupdateWindowsメソッドを呼び出す事で、アプリケーション内の全てのNSWindowに対してupdateメソッドを呼び出す事が出来る。
  • イベントループが一回転するたびに、NSWindowのupdateメソッドは呼ばれる。
  • updateメソッドが呼ばれるタイミングはNSWindowが再描画される前。
  • NSWindowのサブクラスでは、updateメソッドをオーバーライドして、モデルデータの内容を各種UIに反映する事が出来る。

これらの事を考えると、各種コントロールをアップデートするには、

  • NSWindowのupdateメソッドをオーバーライドする。
  • windowに乗っている全てのview(control)に対して、NSUserInterfaceValidationsプロトコルに基づく更新作業を行う。

と言うのがCocoa的に正しいUI更新の方法かもしれない。

MenuやToolbarはデフォルトでこのような更新メカニズムが実装されている。 NSControlにそのような仕組みが無いのは、歴史的理由とCPUパワーの問題と思われる。

現状ではCPUパワーは有り余っている。よってイベントループ毎にcontrolを更新しても問題ないと、私は判断し実装する事にする。

参考文献

OpenStepリファレンス

]]>
Mon, 28 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/27/css_overwrite.html https://www.mindto01s.com/2013/01/27/css_overwrite.html <![CDATA[CSS設定の上書き]]> CSS設定の上書き

このサイトの構築に、sphinxだとかtinkerを使用している。 見出し文字の行間を調整したいのだが、何処の値を変更すれば良いのか判らなかった。

ビルド結果のファイル”_static/modern5.css”中の以下の記述を変更すれば良い事までは判った。

この記述を

article.document h1 { font-size: 2.2em; }
article.document h2 { font-size: 1.8em; }
article.document h3 { font-size: 1.2em; }

こうして、line-heightを足せば

article.document h1 { font-size: 2.2em; line-height: 1.2;}
article.document h2 { font-size: 1.8em; line-height: 1.2;}
article.document h3 { font-size: 1.2em; line-height: 1.2;}

みっともない、行間を修正出来た。

しかし、ビルド毎にファイル”_static/modern5.css”は上書きされてしまう。

仕方ないので、modern5を継承した独自テーマのcssの末尾に上記のCSSの修正したコードを貼付けた。

結果的にmodern5の値はcss的には上書きされたようで、レイアウトは変更されたがコレがsphinx/tinker的に正しい解決方法なのかは不明。

]]>
Sun, 27 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/26/cocoa_study.html https://www.mindto01s.com/2013/01/26/cocoa_study.html <![CDATA[Cocoa勉強会 関東57回目]]> Cocoa勉強会 関東57回目

validateuserinterfaceitemの内容をまとめて、「五年後のControlEnabler」をCocoa勉強会で発表してきました。

内容が練り込まれていない事もあり、あまり評判はよろしく無いようでした。

CocoaSQLMapperの発表を聞きたかったのですが後半は事情により欠席。

]]>
Sat, 26 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/25/validateuserinterfaceitem_7.html https://www.mindto01s.com/2013/01/25/validateuserinterfaceitem_7.html <![CDATA[validateuserinterfaceitem(7)]]> validateuserinterfaceitem(7)

Cocoa勉強会用の資料として、「五年後のControlEnabler」をまとめた。

validateuserinterfaceitemをうまく使って、NSControlの挙動を変かえるコードです。

サンプルコードは、

narita_20130125.zip

PDFは、

Controlenabler.pdf

]]>
Fri, 25 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/24/recipe_uncrustify.html https://www.mindto01s.com/2013/01/24/recipe_uncrustify.html <![CDATA[uncrustifyおかわり]]> uncrustifyおかわり

はじめに

uncrustifyはObjective-Cに対応したソースコードの整形ツールです。

以前にCocoa勉強会での発表時から時間がたち、uncrustifyのObjective-C対応が進みました。 また、XCodeのバージョンも進みuncrustifyを呼び出す方法が変わりました。

現在のuncrustifyとXCodeのバージョンにあった設定と手法を紹介します。

開発環境

開発環境は以下の通りです

MacOSX:10.8.2
XCode:4.5.2
uncrustify:0.59

インストール

MacPortsにパッケージがありますので、これを使います。インストール方法は以下の通りです。

$ sudo port install uncrustify

主な使用方法

uncrustifyは単純なコマンドラインのプログラムです。 以下の様にして、整形したいファイルを指定するだけです。

$ uncrustify [options] [files ...]

主に使うオプションは以下の通りです。

-c CFG 設定ファイルCFGを使う
-f FILE FILEを入力ファイルとして扱う
-o FILE 出力をFILEに書き出す。
-F FILE FILEに1行毎にファイルパスを書いておくと一括で処理。
--replace 入力ファイルを出力結果で置換える。(バックアップは取る)
--no-backup 入力ファイルを置換えてバックアップは取らない。
-l OC 言語の指定。Objective-CはOC、Objective-C++はOC+。

整形フォーマットは設定ファイルで指定します。

設定ファイルの設置

MacPortsでインストールした場合は、”/opt/local/share/uncrustify/”に設定ファイルのサンプルがおいてあります。 これらの設定をコピペして変更しながら使うとお好みの形式に近づけられます。

設定ファイルの項目数は400個くらいあります。全体を眺めるのに必要な略語を以下に示します。

略語一覧

略語 意味
indent 行の最初のインデント関連
sp 要素間の空白関連
align 桁揃え関連。変数宣言等で型と変数名を縦方向にそろえるとか
nl 改行関連
mod 順序をsortしたり、{}を追加したり等の コードを変更をする
cmd コメントの中身の改変
pp プリプロセッサ関連
oc Objective-C関連

Objective-Cに関連する項目を次の表に示します。 sp_xxxx系の項目はお好みで。

項目 設定値
sp_before_oc_colon メッセージ式の’:’の前に空白を挿入/削除する
sp_after_oc_colon -(int) f:(int) x;と-(int) f: (int) x;の様に:と型名の間にスペースを入れるか否か
sp_after_oc_scope -(void)foo;や+(int)bar;の+-等の記号の後にスペースを入れるか否か
sp_after_oc_type メッセージ式の引数の型名の後に空白を挿入/削除する
sp_after_oc_return_type -(void) foo;等の型と関数名の間にスペースを入れるか否か
sp_before_send_oc_colon ‘[object setValue:1];’ vs ‘[object setValue :1];’これremoveじゃないとダメだろ
sp_after_send_oc_colon ‘[object setValue:1];’ vs ‘[object setValue: 1];’普通は左側
sp_after_oc_at_sel ‘@selector(msgName)’ vs @selector (msgName)’‘普通は左側
sp_before_oc_block_caret ‘^int (int arg){…}’ vs. ‘ ^int (int arg){…}’
sp_after_oc_block_caret ‘^int (int arg){…}’ vs. ‘^ int (int arg){…}’
align_oc_msg_colon_span # Obj-C message on the ‘:’ but stop after this many lines (0=don’t align)
align_oc_msg_spec_span # the span for aligning ObjC msg spec (0=don’t align)
align_oc_decl_colon # Aligning parameters in an Obj-C ‘+’ or ‘-‘ declaration on the ‘:’

コマンドラインからの使い方

MacPortsでインストールした場合は、”/opt/local/share/uncrustify/”に設定ファイルのサンプルがおいてあります。 以下のような感じで”/opt/local/share/uncrustify/”以下の設定ファイルを色々と試して、好みに近い物を探すと最初から設定ファイルを手書きするより楽です。

$ uncrustify -l OC -c /opt/local/share/uncrustify/kr-indent.cfg testClass.m

実行するとtestClass.m.uncrustifyが出来たと思いますので、diffをとって変更された箇所を確認してください。

さらに、以下のようにopeddiffすればビジュアルに変更点を確認出来きて便利です。

$ uncrustify -l OC -c /opt/local/share/uncrustify/kr-indent.cfg testClass.m
$ opendiff testClass.m testClass.m.uncrustify

FileMerge.appが開いてこんな感じになります。

../../../_images/opediff.tiff

XCode4からの使い方

XCode3の時に使えたScriptMenuが使えなくなったので、以下の2種類の方法があるようです。

残念ながら私には能力が無いのでどちらも試していません。 XCodeのビヘイビアもAppleScriptもAutometrも全部使った事が無いのです。

そのうち試して追記します。

]]>
Thu, 24 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/23/target_action_in_xcode4.html https://www.mindto01s.com/2013/01/23/target_action_in_xcode4.html <![CDATA[XCode4でのnib編集時のTarget/Actionの扱い]]> XCode4でのnib編集時のTarget/Actionの扱い

以前のInterfaceBuilderで使っていたテクニックが使えなくなっていた。

InterfaceBuilderではtarget/actionパラダイムで接続出来るクラスは、以下の4つのメソッドがヘッダに定義されていれば問題なく使えた。

- (id)target;
- (void)setTarget:(id)anObject;
- (SEL)action;
- (void)setAction:(SEL)aSelector;

上記の4つのメソッドの有無で、IBActionConnectionでコネクションを張れるかどうかを判断していたらしい。

XCode4でこのテクニックは使えない。代わりに以下のような手法を使う。

  1. actionセレクタを文字列で設定するメソッドを追加する

    - (void)setActionName:(NSString*)inActionName
    {
        self.action = NSSelectorFromString(inActionName);
    }
    
    - (NSString*)actionName
    {
        return NSStringFromSelector(_action);
    }
    
  2. ヘッダにtargetをIBOutletを付ける

    @property (retain) IBOutlet id target;
    
XCode4上のUIエディターで通常のconnectionとして接続する事でtargetの設定が出来きる。
  1. XCode4上でセレクタ名を設定する

“Identify Inspector”の”User Defined Runtime Attributes”に

key Path actionName
Type String
Value <selectorNameString>

と設定する。これで、nibの読込み完了時にactionが設定される。

]]>
Wed, 23 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/22/nsmenu_update.html https://www.mindto01s.com/2013/01/22/nsmenu_update.html <![CDATA[メニューのアップデート]]> メニューのアップデート

先日の問題の続き。

Application delegateで

- (void)applicationDidUpdate:(NSNotification *)notification
{
    [[NSApp mainMenu] update];
}

と行えば、メニューの状態をメニューバーのクリック前に随時更新する。らしい。

NSLogで挙動を見る限り、更新作業をオンタイムで行っている。

が、ここで更に問題が有った。と言うよりも、私自身の問題認識が間違っている。

NSMenuは所有するNSMenuItemが全てDisableでもNSMenu自身はDisableになる訳ではない。

そもそも、OpenStepでは、MacOSの流儀と違ってSubMenuが選択出来ないからといって、Menuを選択出来ないようにする文化が無いのかもしれない。

]]>
Tue, 22 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/21/nsmenuitem_update.html https://www.mindto01s.com/2013/01/21/nsmenuitem_update.html <![CDATA[NSMenuItemの更新のタイミング]]> NSMenuItemの更新のタイミング

NSMenuItemの更新のタイミング、[NSUserInterfaceValidations validateUserInterfaceItem:(id)anItem]が呼ばれるタイミングは、マウスがNSPopupMenuをクリックした時だった。

NSPopupMenuのmenuを表示する直前に更新処理をするのは、更新処理を必要最小限にする為と思われる。

理にかなっていると思うのだが、1点マズい事がある。

menuItemが全てDisableの場合は、NSPopupMenuもDisableにする必要がある。 しかし、更新のタイミングがNSPopupMenuをクリックする時だと奇妙な挙動が起る。

ユーザーが、NSPopupMenuをクリックした瞬間に、クリックしたNSPopupMenuがDisableに変わってしまう。(1/22追記 この認識自体が間違い)

解決方法は、更新処理をNSPopupMenuをクリックした時ではなく、NSWindowDidUpdateNotificationが飛んで来た時に更新すると良いと思われる。

コードは、色々と失敗したので挫折した。

]]>
Mon, 21 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/20/infoforbinding.html https://www.mindto01s.com/2013/01/20/infoforbinding.html <![CDATA[Cocoa bindingが行われているかのチェック方法]]> Cocoa bindingが行われているかのチェック方法

[NSObject infoForBinding:バインディング名]で確認出来る。

nilで有ればbindingがされていない。nil以外の場合はbindingに関する詳細な情報がディクショナリ形式で返ってくる。

]]>
Sun, 20 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/19/nsinvocation_addition.html https://www.mindto01s.com/2013/01/19/nsinvocation_addition.html <![CDATA[NSInvocationをカテゴリで拡張した]]> NSInvocationをカテゴリで拡張した

[NSObject valueForKey:(NSString*)inKey]はプリミティブ型をNSNumberやNSValueでラップする。

つまりKVCを使うと、ラップ済みのオブジェクトを取得する事が出来る。ただし、この場合呼出すメソッドに引数が有ってはならない。

引数がある場合はどうするか?NSInvocationを使えば出来る。

但し、[NSInvocation getReturnValue:(void*)inPtr]で取得出来る返り値の型(void*)だ。 この返り値の型は、[NSInvocation methodReturnType]で取得出来る。当然プリミティブ型も含まれる。

この2つの値を参照してObject型を返すようなメソッドを作った。

@implementation NSInvocation (MTLAddition)

- (id) objectWrappedReturnValue
{
    NSMethodSignature* theSignature = [self methodSignature];
    const char* theReturnType   = [theSignature methodReturnType];

    void* thePtr = malloc([theSignature methodReturnLength]);

    [self getReturnValue:thePtr];

    id theResult = nil;

    if( 0 == strcmp(theReturnType, @encode(id)) )
    {
        theResult = *(id*)thePtr;
    }
    else if( 0 == strcmp(theReturnType, @encode(Class)) )
    {
        theResult = *(Class*)thePtr;
    }
    else if( 0 == strcmp(theReturnType, @encode(char)) )
    {
        theResult = [NSNumber numberWithChar:*(char*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(unsigned char)) )
    {
        theResult = [NSNumber numberWithUnsignedChar:*(unsigned char*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(short)) )
    {
        theResult = [NSNumber numberWithShort:*(short*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(unsigned short)) )
    {
        theResult = [NSNumber numberWithUnsignedShort:*(unsigned short*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(int)) )
    {
        theResult = [NSNumber numberWithInt:*(int*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(unsigned int)) )
    {
        theResult = [NSNumber numberWithUnsignedInt:*(unsigned int*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(long)) )
    {
        theResult = [NSNumber numberWithLong:*(long*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(unsigned long)) )
    {
        theResult = [NSNumber numberWithUnsignedLong:*(unsigned long*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(long long)) )
    {
        theResult = [NSNumber numberWithLongLong:*(long long*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(unsigned long long)) )
    {
        theResult = [NSNumber numberWithUnsignedLongLong:*(unsigned long long*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(float)) )
    {
        theResult = [NSNumber numberWithFloat:*(float*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(double)) )
    {
        theResult = [NSNumber numberWithDouble:*(double*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(BOOL)) )
    {
        theResult = [NSNumber numberWithBool:*(BOOL*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(NSInteger)) )
    {
        theResult = [NSNumber numberWithInteger:*(NSInteger*)thePtr];
    }
    else if( 0 == strcmp(theReturnType, @encode(NSUInteger)) )
    {
        theResult = [NSNumber numberWithUnsignedInteger:*(NSUInteger*)thePtr];
    }
    else
    {
        theResult =  [NSValue valueWithBytes:thePtr
                                    objCType:theReturnType];
    }

    free(thePtr);

    return theResult;
}

@end

コードを見れば判るようにid型、Class型、NSNumberでサポートしているプリミティブ型の三つしか対応していない。

それ以外は、[NSValue valueWithBytes:(void*)inPtr objCType:(const char*)inType]を使っている。

かなりおおざっぱな実装だが、私の用途には十分だった。ポインター型、構造体、配列型、blocks型等も実装するべきなのだろうが、やり方が判らなかった。

]]>
Sat, 19 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/18/get_the_insurance_money.html https://www.mindto01s.com/2013/01/18/get_the_insurance_money.html <![CDATA[保険金Getだぜ!!]]> 保険金Getだぜ!!

保険屋さんから書類が来た。この間の雪で壊れた雨どいの修理費の書類。

築30年ごえのボロ屋に住んでる貧乏人にとっては、この手の火災保険の保険金は有り難い。

毎年の支払額を考えると損するのだろうが、修理費が定額になるので計画が立て易くなるのが利点。

なお、修理費の見積もりが終わってないので本当はまだ保険金はゲット出来ていません。

]]>
Fri, 18 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/17/c_preprocessor_macro_substring.html https://www.mindto01s.com/2013/01/17/c_preprocessor_macro_substring.html <![CDATA[Cのマクロでは部分文字列は出来ない]]> Cのマクロでは部分文字列は出来ない

ObjC上で、似たようなコードを沢山書いていたのでマクロを使おうとした。

id objectWrappingFrom(void* inPtr, const char* inType)
{
    id theResult = nil;

    if( strcmp(@encode(char), inType) )
    {
        theResult = [NSNumber numberWithChar:*(char*)inPtr];
    }
    else if( strcmp(@encode(unsigned char), inType) )
    {
        theResult = [NSNumber numberWithUnsignedChar:*(unsigned char*)inPtr];
    }// こんなのが沢山
    .
    .
    .
    .
    else
    {
        theResult =  [NSValue valueWithBytes:inPtr
                                    objCType:inType];
    }

    return theResult;
}

NSNumberのメソッド名中の型名、@encode中の型名、キャストの型名が微妙に違う。 メソッド名の型名だけcamelCaseになっている。

これをマクロで記述しようと一日中頑張ったが私の能力では無理だった。

Cのマクロでは文字列から部分文字列を取り出せないらしい。 文字列の結合は##で出来るが、文字列を切り分ける方法が無い。

1/18日追記

上のコードは思いきり間違い。

strcmpは文字列が等しい時に0を返すので、

id objectWrappingFrom(void* inPtr, const char* inType)
{
    id theResult = nil;

    if( strcmp(@encode(char), inType) == 0) // <--- コレ
    {
        theResult = [NSNumber numberWithChar:*(char*)inPtr];
    }
    .
    .
    .

としなければならない。

]]>
Thu, 17 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/16/muscular_pain.html https://www.mindto01s.com/2013/01/16/muscular_pain.html <![CDATA[筋肉痛]]> 筋肉痛

昨日の雪かきで体中が痛い。普段の運動不足を痛感する。

雨どいの破損の保険は査定が来るとの事。

今日のお仕事の成果は無し。

]]>
Wed, 16 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/15/snow_damage.html https://www.mindto01s.com/2013/01/15/snow_damage.html <![CDATA[雪害]]> 雪害

午後9時半過ぎに突然おおきな音が聞こえた。最初は地震か?と思ったが揺れていない。

外に出てみると、昼間に雪かきをしてキレイした北側の道路に雪が積もっている。 どうやら屋根から雪が落ちて来たようだ。

道路に誰もおらず事故にならずに良かった。と思っていたら、雨どいが雪に持って行かれて壊れていた。

../../../_images/snow_damage.jpg

エアコンの室外機や湯沸器にも雪があたってカバーが凹んでいるが一応動いている。雨どいの方はもう、雨どいの役割を果たしてないな。

日本生命の火災保険と家財保険に入っているのだが、雪害は保険がおりるのだろうか? 明日、保険のおばちゃんに聞いてみよう。

]]>
Tue, 15 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/14/nssegmentedcontrol_selectedtag.html https://www.mindto01s.com/2013/01/14/nssegmentedcontrol_selectedtag.html <![CDATA[NSSegmentedControlで最後にクリックされたセグメントのタグ番号を得るには]]> NSSegmentedControlで最後にクリックされたセグメントのタグ番号を得るには

NSSegmentedControlで最後にクリックされたセグメントのタグ番号を得るには、[NSSegmentedControl selectedTag]で取得出来そうだが、実際には出来ない。

このメソッドで帰ってくる値は、segmentに設定されたタグではなくNSSegmentedControlのタグ。

これはおそらく、

  1. segmentはcellではない
  2. segmentは複数選択が可能である

の2点の理由で、意図的にそのような仕様になったのだと思われる。

クリックされたセグメントに設定されたtagを得るには以下のコードを追加して、[NSSegmentedControl lastClikedSegmentedTag]を呼出せば良い。

@implementation NSSegmentedControl (MTLControlItemEnabler)

- (NSInteger) lastClikedSegmentedTag
{
    NSSegmentedCell* theCell = [self cell];

    return [theCell tagForSegment:[theCell selectedSegment]];;
}

@end
]]>
Mon, 14 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/13/why_objc_extension_is_m.html https://www.mindto01s.com/2013/01/13/why_objc_extension_is_m.html <![CDATA[なぜObjective-Cの拡張子が”m”なのか?]]> なぜObjective-Cの拡張子が”m”なのか?

理由は既に忘れ去られたらしい。

Objective-Cの作者Brad Coxによると「モジュール」、「メソッド」、「メッセージ」かも知れないとの事。

プログラミングって結構いい加減だと再認識した。

参考文献

Objective-C フレーズブック P21

]]>
Sun, 13 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/12/led_lantern.html https://www.mindto01s.com/2013/01/12/led_lantern.html <![CDATA[LEDランタンを購入]]> LEDランタンを購入

無印良品の「LEDランタン・サーチライト付」を購入しようと近所の店舗によったが、在庫が無いとの事。

諦めて家でググると、パナソニックから同様の製品が発売されていた。製品名は「無接点インテリアライト BG-KL01H-W」。

色々と調べると、OEMの製品でブランドが異なるだけでほぼ同一の製品と考えて良いようだ。早速Amazonで注文して手元に届いて現在使用中。

値段の割には使い物にならないのは、Amazonの評価通りなので気にならず。

充電中にかすかに小さな音がする。インバーターか何かが働いている音なのだろうが、気になる人は気になるかもしれない。

この手の普段使いできる防災用品というジャンルの商品がスキなのでもう少し増えてくれると有り難い。

なお、私にとっての「普段使いできる防災用品」のヒット商品は

  • カセットコンロ
冬にコレで鍋物を作るのが大好きです。
  • 封筒型寝袋
膝掛けやクッションとして年中つかってます。
  • LEDキーライト
キーライトとしては大型の単四電池を使う物です。普段は暗くなってからの足下を照らしてます。帰り道に犬のウンコが転がってるので踏んづけない為には必須です:-)
]]>
Sat, 12 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/11/late_protocol.html https://www.mindto01s.com/2013/01/11/late_protocol.html <![CDATA[カテゴリを使ってプロトコルの後付け]]> カテゴリを使ってプロトコルの後付け

Objective-Cでは、ひとまとまりのメソッド群が実装されているかを実行時に調べる方法として、プロトコルと呼ばれる物がある。

私は、まず「プロトコル」が存在し、それに対して「プロトコルに適応されたクラス」が存在すると考えていた。

しかし、Objective-Cでは、既存のクラスを後付けでプロトコルに適応出来ました。

以下のコードを実行するとログに”:-D”が表示されます。

#import "PTAppDelegate.h"

// プロトコルの定義
@protocol PTObjectValueProtocol
- (void)setObjectValue:(id<NSCopying>)obj;
- (id)objectValue;
@end

// プロトコルを後付けするカテゴリ
@interface NSControl (PTAppDelegate) <PTObjectValueProtocol>
@end
// 後付けしたプロトコルの実装
@implementation NSControl (PTAppDelegate)
@end

@implementation PTAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // "protocol(PTObjectValueProtocol)"の部分は@protocolです。
    // sphinxがうまくコードを色づけしてくれなかったので@を抜かしました。
    if( [NSButton conformsToProtocol:protocol(PTObjectValueProtocol) ] )
    {
        NSLog(@":-D");
    }
    else
    {
        NSLog(@":-(");
    }
}
@end

基本的に、

  • “@interface 既存クラス名 (カテゴリ名) <プロトコル名>”
  • “@implementation 既存クラス名 (カテゴリ名)”

の2つを作るだけで問題なくプロトコル対応なクラスとして認識してくれました。

]]>
Fri, 11 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/10/bookstore.html https://www.mindto01s.com/2013/01/10/bookstore.html <![CDATA[久しぶりに神保町]]> 久しぶりに神保町

先日、仕事帰りに神保町の明倫館書店に行った。

店の中を物色していると、床の上に積まれている本の中に欲しい物が有った。

値段はいくらなのか店員に尋ねると、整理中なので触らないで欲しいとの事。

次に寄り道する時には売れてしまっているのだろうなと思いつつ店を出る。

]]>
Thu, 10 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/09/validateuserinterfaceitem_6.html https://www.mindto01s.com/2013/01/09/validateuserinterfaceitem_6.html <![CDATA[validateuserinterfaceitem(6)]]> validateuserinterfaceitem(6)

続き。

NSMatrixやNSForm等の複数のNSCellのインスタンスを持つControlの取り扱いに難儀中。

NSMenuItem, NSToolBarItem, NSButton等は”- (IBAction) action:(id)sender”の引数にそれぞれのObjectが来る。

選択余地はないのでこれは正しい。

しかし、NSMatrixはsenderにNSMatrixが入るのは正しいのだろうか?もちろんCocoaの実装では、NSMatrixが入っている。

Radioボタン等はtagをとる為に、NSCellがsenderに入るのが”正しい”気がするのだが。

今日は眠いので寝る。

]]>
Wed, 09 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/08/validateuserinterfaceitem_5.html https://www.mindto01s.com/2013/01/08/validateuserinterfaceitem_5.html <![CDATA[validateUserInterfaceItem(5)]]> validateUserInterfaceItem(5)

さらに続き。

MTLControlItemEnablerをもっと拡張する

以下の属性のアップデートにも対応した。

  • attributedTitle
  • objectValue
  • stringValue
  • attributedStringValue
  • toolTip

NSMatrixに対応出来ていない

MTLControlItemEnablerはNSMatrixに対応出来ていません。 ラジオボタングループのstateを変更が出来ません。

おそらく、NSMatrixのサブクラスでMTLButtonのようにupdateをする事と、matrix内部のcellのstateを変更するように拡張すれば動くと思われる。

が、まだ手つかず。

サンプルコード

かなり中途半端だが、一応UPする

EnablerTest-4.zip

]]>
Tue, 08 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/07/martian_and_gundam.html https://www.mindto01s.com/2013/01/07/martian_and_gundam.html <![CDATA[ガンプラ]]> ガンプラ

お年玉として、甥っ子に正月にガンプラを買ってあげた。

彼の話によると、最近のガンダムの敵は火星人なのだそうだ。

../../../_images/martian.jpg

昔のガンダムのイメージとはかなりかけ離れている感じがする。

]]>
Mon, 07 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/06/validateuserinterfaceitem_4.html https://www.mindto01s.com/2013/01/06/validateuserinterfaceitem_4.html <![CDATA[validateUserInterfaceItem(4)]]> validateUserInterfaceItem(4)

前回の更に続き。

MTLControlItemEnablerとは何か?

MTLControlItemEnablerはコードを簡潔に表記する事を目的に書かれました。 UIの挙動を宣言的に表記出来ます。

例えば、メソッド”xyzAction:”を呼出すボタンのenable/disableを制御するには、”canXyzAction:”を定義します。

“canXyzAction:”の返り値でenable/disableを制御します。

- (IBAction) xyzAction:(id)sender
{
    // 色々と実行
}

- (BOOL) canXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return YES or NO;
}

“canXyzAction:”の呼び出しは、”validateUserInterfaceItem:”で”controlItemEnabler:”を呼出すと内部で適当に呼出します。

- (BOOL)validateUserInterfaceItem:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return [self controlItemEnabler:anItem];
}

MTLControlItemEnablerを拡張する

さらに今回のコードでは、もう一歩進めて以下のUIの挙動を変更出来ます。

  • state
NSMixedState, NSOffState, NSOnState等の値でチェックBOXやラジオボタンのON/OFFやメニューのチェック等を制御します。
  • image
buttunやToolboxItemやMenuItemの画像を制御します。例えば、音楽プレーヤーアプリのPlay/Stopを一つのボタンので行い画像を随時入れ替える場合等に使えます。
  • title
ButtonやMenuItemのタイトル文字列を変更出来ます。
  • label
toolbarItemは何故かtitle属性がなくlabel属性になっていたので追加。

メソッド”xyzAction:”が有った場合は、以下のようにメソッド名にprefixを付けたメソッドを作成します。

- (IBAction) xyzAction:(id)sender
{
    // 色々と実行
}

- (BOOL) canXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return YES or NO;
}

- (NSIntger) stateOfXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return NSMixedState;//NSMixedState or NSOffState or NSOnState;
}

- (NSImage*) imageOfXyzAction:(id)anItem
{
    if( func() )
        return [NSImage imageNamed:NSImageNameIconViewTemplate];
    else
        return [NSImage imageNamed:NSImageNameListViewTemplate];
}

- (NSString*) titleOfXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return [NSString stringWithFormat:@"xyz(%li)", 1/*適当な数字とか*/];
}

サンプルコード

プロジェクトファイルはここ、

EnablerTest-3.zip

最初は以下のようになってます。

../../../_images/ss011.tiff

action1のボタン(titleOfで置換えているので、enable(x)となってるが)を押すと、titleやimageが変わります。

../../../_images/ss021.tiff

action2のボタンを押すと、また変わります。

../../../_images/ss031.tiff

action3を押すともとに戻ります。

コードの解説は特に無し。zipを解凍してみてみてください。

]]>
Sun, 06 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/05/validateuserinterfaceitem_3.html https://www.mindto01s.com/2013/01/05/validateuserinterfaceitem_3.html <![CDATA[validateUserInterfaceItem(3)]]> validateUserInterfaceItem(3)

前回の更に続き。

今回は、NSControlの方を変更するのではなく、”validateUserInterfaceItem:”の記述の仕方の説明。

しかも、ノーマルな”validateUserInterfaceItem:”の記述方法ではなくて、私がより簡潔だと考えている記述方法です。

validateUserInterfaceItem:とは何か?

UIの有効無効を制御するCocoa標準の手法です。validateUserInterfaceItem登場以前には、toolbarやmenu等でそれぞれ異なったメソッドを使用していた。

現在は、validateUserInterfaceItemを実装する事を推奨している。

validateUserInterfaceItemの役割は、

  • UI有効無効を制御
  • UIにチェックマークを付ける
  • UIにtooltipの設定

等です。

Cocoa標準の表記法では

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;
    if([anItem action] == @selector(saveAction:))
    {
        theResult = YES or NO;
    }
    else if([anItem action] == @selector(action2:))
    {
       theResult = YES or NO;
    }
    return theResult; // or NO
}

と、こんな感じで表記します。

validateUserInterfaceItem:の表記法の問題点は何か?

プログラムコードを書く時には、関連するコードは近い場所に書くべきと言われています。 しかし、この標準的な書き方では関係ある物はまとめたいのに、actionのメソッドと有効にするためのロジックが以下のように離れてしまいます。

// UIから実行したいメソッド
- (IBAction) saveAction:(id)sender
{
    .
    .
    .
}
.
.
.
- (IBAction) action2:(id)sender
{
    .
    .
    .
}
.
.
.

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;

    // saveAction:のenable/disableの制御
    if([anItem action] == @selector(saveAction:))
    {
        theResult = YES or NO;
    }
    // action2:のenable/disableの制御
    else if([anItem action] == @selector(action2:))
    {
        .
        .
        .
        .
        .
        .
    }
    return theResult;
}

また、validateUserInterfaceItem:メソッドの中の、if-else-ifが延々と続くのも可読性が悪くなります。

解決方法は?

解決方法は単純です。

UIから呼出されるaction:メソッドとUIのenable/disableを決定するロジックを記述するcanActionメソッドの2つを対にして実装します。 これで2つのコードが離れる事を防げます。

次に、if-else-ifが延々と続くのを防ぐ為に、validateUserInterfaceItem:でaction:メソッドからcanActionメソッドを動的に探すコードを書けば解決出来ます。

これが、

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;

    if([anItem action] == @selector(saveAction:))
    {
        theResult = [self canSaveAction:anItem];
    }
    else if([anItem action] == @selector(action2:))
    {
        theResult = [self canAction2:anItem];
    }
    else if([anItem action] == @selector(action3:))
    {
        theResult = [self canAction3:anItem];
    }
    else if([anItem action] == @selector(action4:))
    {
        theResult = [self canAction4:anItem];
    }

    return theResult; // or NO
}

こうなります。

#import "NSObject(MTLControlItemEnabler).h"
    .
    .
    .
- (BOOL)validateUserInterfaceItem:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    // controlItemEnablerで全部面倒見ます
    return [self controlItemEnabler:anItem];
}

actionとcanActionメソッドは、例えば以下のように一カ所に表記出来ます。

- (IBAction) saveAction:(id)sender
{
    NSError *error = nil;

    if (![[self managedObjectContext] save:&error])
    {
        [[NSApplication sharedApplication] presentError:error];
    }
}

- (BOOL) canSaveAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return [[self managedObjectContext] hasChanges];
}

サンプルコードは、

EnablerTest-2.zip

]]>
Sat, 05 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/04/validateuserinterfaceitem_2.html https://www.mindto01s.com/2013/01/04/validateuserinterfaceitem_2.html <![CDATA[validateUserInterfaceItem(2)]]> validateUserInterfaceItem(2)

前回の続き。サンプルアプリを作った。

MTLButtonがToolbarItemやMenuItemのように、UIのenable/disableを自動で行うのを確認出来る。

AppDelegateのvalidateUserInterfaceItemで順繰りに特定のactionを持つToolbarItemやMenuItemをenable/disableを行っている。

MTLButtonではWindowがアップデートされるタイミングで、このvalidateUserInterfaceItemを呼出して自分自身のenable/disableを変更している。

Action1のselectorを持つUIがenableになっている。NSButtonはvalidateUserInterfaceItemに対応していないので、全てenableになっている。しかし、MTLButton, ToolbarItem, MenuItemはAction1しかenableになっていない。

../../../_images/ss01.tiff

action2をenableにした場合。

../../../_images/ss02.tiff

action3をenableにした場合。

../../../_images/ss03.tiff

サンプルコードは、

EnablerTest.zip

]]>
Fri, 04 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/03/validateuserinterfaceitem_1.html https://www.mindto01s.com/2013/01/03/validateuserinterfaceitem_1.html <![CDATA[validateUserInterfaceItem(1)]]> validateUserInterfaceItem(1)

メニューとツールバーの有効/無効を制御するプロトコルとして”NSUserInterfaceValidations”がある。

これをNSButtonに対して拡張する手法がある。2006年〜2007年頃に書いたコードだ。

@interface MTLButton : NSButton
{
}
- (void) updateEnableState;
@end

@implementation MTLButton

- (id)initWithFrame:(NSRect)frameRect
{
    self = [self initWithFrame:frameRect];

    if(self)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(updateEnableState)
                                                     name:NSWindowDidUpdateNotification
                                                   object:nil];
    }

    return self;
}


- (void) dealloc
{
    [self subviews];

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [super dealloc];
}

- (id) initWithCoder: (NSCoder*)aDecoder
{
    self = [super initWithCoder: aDecoder];

    if(self != nil)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(updateEnableState)
                                                     name:NSWindowDidUpdateNotification
                                                   object:nil];
    }

    return self;
}

- (void) updateEnableState
{
    // actionが設定されているかをチェック。(target == nil)の場合はfirstResponderだから有効なのでtargetのnilチェックはしない。
    if( [self action] != nil && [self window] != nil && [self isHiddenOrHasHiddenAncestor] == NO)
    {
        id                  theValidator;

        // レスポンダーチェーンをたどって実際にactionを処理するオブジェクトを探す。そのオブジェクトがメニューやボタンのenable/disableの値を決定出来るはず
        theValidator = [NSApp targetForAction:[self action]
                                           to:[self target]
                                         from:self];


        if([self infoForBinding:@"enabled"])
        {
            // enabledにCocoaBindingが設定されている場合は
            // CocoaBindingの設定を優先させるために何もしない。
        }
        else if ((theValidator == nil) || ![theValidator respondsToSelector:[self action]])
        {
            // actionを実行できるレスポンダーが無い場合は、ボタンは押せない
            [self setEnabled:NO];
        }
        else if ([theValidator respondsToSelector:@selector(validateToolbarItem:)])
        {
            // actionを実行できるレスポンダーが"validateToolbarItem:"を実装しているならば従う
            [self setEnabled:
                [theValidator validateToolbarItem:
                    (NSToolbarItem*)self]];
        }
        else if ([theValidator respondsToSelector:@selector(validateUserInterfaceItem:)])
        {
            // actionを実行できるレスポンダーが"validateUserInterfaceItem:"を実装しているならば従う
            [self setEnabled:
                [(id<NSUserInterfaceValidations>)theValidator validateUserInterfaceItem:
                    (id<NSValidatedUserInterfaceItem>)self]];
        }
        else
        {
            // レスポンダーがあるけど、"validateXXXX"系のメソッドが無い場合は、ボタンは押せる。
            [self setEnabled:YES];
        }
    }
}

@end

最近、仕事でiOSでなくCocoaのコードを書く事があり気がついた。 このコードのNotificationを登録する箇所と値が間違っている。

  1. 登録する場所は、 “[MTLButton viewDidMoveToWindow]”で行われるべき。
  2. 登録解除する場所は、”[MTLButton viewWillMoveToWindow:newWindow]”で行われるべき。
  3. Notification登録時にButtonが乗っているWindowでフィルタリングするべき。

そんな訳で、

- (id)initWithFrame:(NSRect)frameRect;
- (void) dealloc;
- (id) initWithCoder: (NSCoder*)aDecoder;

は削除され、以下のコードで置換えるべきだと思うのだが、まだ実装してません。

- (void)viewDidMoveToWindow
{
    [super viewDidMoveToWindow];

    NSWindow* theWindow = [self window];

    if( theWindow != nil )
    {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(updateEnableState)
                                                     name:NSWindowDidUpdateNotification
                                                   object:theWindow];
    }
}

- (void)viewWillMoveToWindow:(NSWindow *)newWindow
{
    [super viewWillMoveToWindow:newWindow];

    NSWindow* theWindow = [self window];

    if( theWindow != nil )
    {
       [[NSNotificationCenter defaultCenter] removeObserver:self
                                                       name:NSWindowDidUpdateNotification
                                                     object:theWindow];
    }

}

ひょっとしたら、windowが放棄される時に”viewWillMoveToWindow:newWindow”が呼ばれないかもしれない。 が、もう寝る。

]]>
Thu, 03 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/02/nswindowcontroller_esc.html https://www.mindto01s.com/2013/01/02/nswindowcontroller_esc.html <![CDATA[NSWindowControllerとESCキー]]> NSWindowControllerとESCキー

眠れぬ夜のために : OS X 用 Cocoa アプリケーションにおける環境設定ウィンドウの作り方( http://forthesleeplessnight.blogspot.jp/2012/10/os-x-cocoa.html)では、環境設定ウィンドウをESCキーでクローズする動作を、NSWindowのサブクラス化で実現している。

@interface PreferencesWindow : NSWindow
@end

@implementation PreferencesWindow

- (void) cancelOperation:(id)sender
{
  [self close];
}

@end

個人的にはNSWindowではなく、NSWindowControllerで以下のように実装するのが好みだ。

@interface PreferencesWindowController : NSWindowController
@end

@implementation PreferencesWindowController

- (IBAction) cancel:(id)sender
{
  [self close];
}

@end

delegateで拡張できるならば、subclass化よりもdelegateで拡張する方がCocoaらしいと思っています。

]]>
Wed, 02 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2013/01/01/doubutunomori.html https://www.mindto01s.com/2013/01/01/doubutunomori.html <![CDATA[とびだせ どうぶつの森]]> とびだせ どうぶつの森

このゲームのタイトルを聞いて、「とびだせ!カティンの森」を思いついた。

ググったら、すでに同じ事を思いついた善良な人々がおられて感動した。

]]>
Tue, 01 Jan 2013 00:00:00 +0900
https://www.mindto01s.com/2012/12/31/directory_layout.html https://www.mindto01s.com/2012/12/31/directory_layout.html <![CDATA[ディレクトリレイアウトのメモ]]> ディレクトリレイアウトのメモ

プロジェクトを始めるにあたって、毎回悩むディレクトリレイアウトです。 一部ではファイル構成管理と言うようですが、もっと狭い意味での構成管理です。

ファイル構成管理と言うと、svnやgitのようなバージョン管理を思い浮かべるかもしれないですが、ここではファイルとディレクトリをどのように配置するかを書いています。

私はSubversion実践入門で紹介されていたレイアウトを使っています。

BUILDING    <--- ビルドする方法を書いたメモ
GLOSSARY    <--- プロジェクト特有の用語集
README      <--- このプロジェクトが何なのか説明したメモ
bugs/       <--- ditzのissueを入れるディレクトリ
data/       <--- 本番用/テスト用のデータファイル等
doc/        <--- プロジェクトに関するドキュメント
src/        <--- ソースコード
util/       <--- プロジェクト特有の各種ツールやscript
vendor/     <--- サードパーティ製のライブラリ等
vendorsrc/  <--- サードパーティ製のライブラリのソースコード
  • BUILDING

ビルドする方法を書いたメモを書くとの事です。 内容は、ビルドに必要な環境とビルド方法を書いたメモです。

必要条件:
Xcode 4.5.2
MacOSX 10.8.2

ビルドの方法:
cd ./src/
make

詳細情報:
./doc/building.html

しかし、このメモが必要になった時には、あまり役には立ちませんでした。 XCodeのバージョンが合わなかったり、OSのバージョンアップで各種ツールの位置が変わったりしていたため、結局XCodeのプロジェクトやmakeファイルの中身を書き換える事になりました。

  • GLOSSARY
プロジェクト特有の用語をまとめて書くと良いと言われてますが、私は一度も書いた事はありません。 多分、複雑で大規模なコードを書く人たちには重要なのかもしれません。
  • README
このプロジェクトが何なのか説明したメモ。 GitHubにあるプロジェクトのREADMEを参考にすると良いだろうと思ってますが、未だに書いた事がありません。
  • bugs/
ditzのissueを入れるディレクトリ。ditz initで作成してます。今回初めて作りました。
  • data/
本番用/テスト用のデータファイル等を入れるらしい。が、これも使った事無いです。
  • doc/
プロジェクトに関するドキュメントを入れる。議事録、設計メモ、メールでのやり取り、運用マニュアル等を入れておきます。 読んでおくべき参考文献等もPDFやWebArchiveフォーマットで入れておくと、探す手間が省けて便利でした。
  • src/
プログラムのソースコー本体です。
  • util/

プロジェクト特有の各種ツールやscriptを入れておきます。意外に使います。 私の場合は、

  • deleteDotSVN.sh
出荷用パッケージに.svnデイレクトリが残らないようにするためのスクリプト
  • openUp
diskImageの”first-open-window”属性をONにするプログラム。Apple製
  • make_svn_version_h.sh
カレントディレクトリ上にsvnversionの値をc文法で定義するファイルのsvn_version.hを生成する。ビルド番号を生成を補助するツール

等を入れたり入れなかったりしてます。

  • vendor/
サードパーティ製のライブラリを入れておきます。例えば、YAML.framework等を入れたりします。
  • vendorsrc/
サードパーティ製のライブラリのソースコードを入れておきます。例えば、YAML.frameworkのソースを入れたりします。毎回ビルドする物ではないので、ビルドした成果物は、vendorに入れてバージョン管理下に加えておきます。
]]>
Mon, 31 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/30/scevents.html https://www.mindto01s.com/2012/12/30/scevents.html <![CDATA[SCEvents]]> SCEvents

ファイル監視をする為にFSEventの事を調べてみたらSCEventsとクラスを発見。

CoreFoundationを使うよりも楽そうなのでこのコードを使用する事にする。

SCEventsのSCContstans.hで定義されている列挙型SCEventFlagsの定義が少したらないのようなので以下のように付け足して運用してみる事にした。

typedef enum
{
    SCEventStreamEventFlagNone            = 0x00000000,
    SCEventStreamEventFlagMustScanSubDirs = 0x00000001,
    SCEventStreamEventFlagUserDropped     = 0x00000002,
    SCEventStreamEventFlagKernelDropped   = 0x00000004,
    SCEventStreamEventFlagEventIdsWrapped = 0x00000008,
    SCEventStreamEventFlagHistoryDone     = 0x00000010,
    SCEventStreamEventFlagRootChanged     = 0x00000020,
    SCEventStreamEventFlagMount           = 0x00000040,
    SCEventStreamEventFlagUnmount         = 0x00000080
    // 以下追加分
    SCEventStreamEventFlagItemCreated       = 0x00000100,
    SCEventStreamEventFlagItemRemoved       = 0x00000200,
    SCEventStreamEventFlagItemInodeMetaMod  = 0x00000400,
    SCEventStreamEventFlagItemRenamed       = 0x00000800,
    SCEventStreamEventFlagItemModified      = 0x00001000,
    SCEventStreamEventFlagItemFinderInfoMod = 0x00002000,
    SCEventStreamEventFlagItemChangeOwner   = 0x00004000,
    SCEventStreamEventFlagItemXattrMod      = 0x00008000,
    SCEventStreamEventFlagItemIsFile        = 0x00010000,
    SCEventStreamEventFlagItemIsDir         = 0x00020000,
    SCEventStreamEventFlagItemIsSymlink     = 0x00040000
}
SCEventFlags;

なお、”日暮れて道遠し”ではシングルトンであるとの事でしたが、複数のインスタンスを作成出来た。

間違いかもしれないが、そのまま使用してみるつもり。

参考文献

日暮れて道遠し: Cocoa FSEvent と SCEvents
http://michitoshi.blogspot.jp/2011/06/cocoa-fsevent-scevents.html
本家
http://stuconnolly.com/blog/scevents-011/
]]>
Sun, 30 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/29/nstask_and_intractive_command.html https://www.mindto01s.com/2012/12/29/nstask_and_intractive_command.html <![CDATA[NSTaskで対話型コマンドを呼出す]]> NSTaskで対話型コマンドを呼出す

ditzをCocoaから呼出す方法を調べていた。 ditzはユーザーと対話しながら実行するサブコマンドが多い。

そのためNSTaskで実装するには、ユーザーからの入力を偽装するNSPipeをNSTaskに接続し、適切なタイミングで文字列を流し込む必要がある。

その正攻法では面倒だし、コードが判りにくいと思っていたら面白いUnixコマンドを見つけた。

“expect”と呼ばれるunixコマンドです。このexpectは対話型のUnixコマンドを操作する為のunixコマンドと言うべき物でした。

詳細は以下のURLを参照してください。

Expect 入門 :
http://nsa.kpu-m.ac.jp/gijutu/expect/doc-expect.php

ditzをexceptを使って操作すれば、NSTaskへ投げっぱなしで問題なく実行出来そうだ。

例えば、以下のようなditzでissueを追加するラッパーを作った。

コマンド名 addIssue.sh
引数1 タイトル
引数2 詳細
引数3 bugか機能かタスクか
引数4 作成者名
引数5 コメント

呼出し方はこう

$ ./addIssue.sh すごいバグ 詳しく説明 b mymail@address 色々とコメント

ソースコードはこう

#!/bin/sh

TITLE=$1
DESCRIPTION=$2
BFT=$3
CREATOR=$4
COMMENT=$5

expect -c "
set timeout 5
spawn ditz add

expect \"Title:\"
send $TITLE
send \"\n\"

expect \"Description (\*):\"
send $DESCRIPTION
send \"\n\"
send \".\"
send \"\n\"


expect \"Is this a (b)ugfix, a (f)eature, or a (t)ask? \"
send $BFT
send \"\n\"


expect \"Issue creator (enter for \*):\"
send $CREATOR
send \"\n\"


expect \"Comments (\*):\"
send $COMMENT
send \"\n\"
send \".\"
send \"\n\"

interact

"

こんなコマンドをbundleに持たせて、NSTaskで呼出せばCocoaでややこしいプログラムを組まなくて良さそうです。

ついでに、issueの追加ではなくてtaskの追加専用のコマンドにしてみたり、エラーチェックを追加してみた。。

#!/bin/sh

####################
# 事前条件チェック
####################

# 引数の数をチェック
if [ $# -ne 4 ]
then
    echo "指定された引数は$#個です。" 1>&2
    echo "実行するには4個の引数が必要です。" 1>&2
    exit 1
fi

# ditzが実行パス中に有るかチェック
which ditz  >/dev/null 2>&1
if [ $? -ne 0 ]
then
    echo "ditzがインストールされていません。" 1>&2
    exit 1
fi

# ditzのバージョンをチェック
VERSION=`ditz --version | cut -d ' ' -f 2`
if [ $VERSION != "0.5" ]
then
    echo "インストールされているditzのバージョンは{$VERSION}です。" 1>&2
    echo "対応しているditzのバージョンは0.5です。" 1>&2
    exit 1
fi


# 引数を判り易い変数名にしておく
TITLE=$1
DESCRIPTION=$2
CREATOR=$3
COMMENT=$4

expect -c "
    set timeout 5
    spawn ditz add

    expect {
        default {exit 2}
        \"Title:\" {send $TITLE; send \"\n\"}
    }

    expect {
        default {exit 3}
        \"Description (\*):\" {send $DESCRIPTION; send \"\n.\n\"}
    }

    expect {
        default {exit 4}
        \"Is this a (b)ugfix, a (f)eature, or a (t)ask? \" {send \"t\n\"}
    }

    expect {
        default {exit 5}
        \"Issue creator (enter for \*):\" {send $CREATOR; send \"\n\"}
    }

    expect {
        default {exit 6}
        \"Comments (\*):\" {send $COMMENT; send \"\n.\n\"}
    }

    expect eof
"

case "$?" in
    0) exit 0 ;;
    2) echo "Titleの入力がエラーです。"         1>&2 ; exit 1 ;;
    3) echo "Descriptionの入力がエラーです。"   1>&2 ; exit 1 ;;
    4) echo "Issue typeの入力がエラーです。"    1>&2 ; exit 1 ;;
    5) echo "Issue creatorの入力がエラーです。" 1>&2 ; exit 1 ;;
    6) echo "Commentsの入力がエラーです。"      1>&2 ; exit 1 ;;
    *) echo "不明なエラーです。"                1>&2 ; exit 1 ;;
esac

exit 0

DitzのGUIフロントエンドを作るにあたって、後調べなければならない事はファイルのアップデートのタイミングを知る為の”FSEvent”を調査する必要がある。

]]>
Sat, 29 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/28/nserror_memo.html https://www.mindto01s.com/2012/12/28/nserror_memo.html <![CDATA[NSErrorのメモ]]> NSErrorのメモ

箇条書き

  • 既存のエラーを探すには
主なドメインのエラーコードを定義しているファイル
ドメイン file path
Mach mach/kern_return.h
POSIX sys/errno.h
Carbon CarbonCore/MacErros.h
Cocoa(Foundation) Foundation/FoundationErrors.h
Cocoa(AppKit) AppKit/AppKitErrors.h
Cocoa(CoreData) CoreData/CoreDataErrors.h

上記のfile pathをXCodeの”Open Quickly…”を呼出して打ち込むと、それぞれのファイルが開くので探すと良いらしい。

../../../_images/openQuick.tiff
  • 新しいドメインを作るには

逆DNS名を使い、他のドメイン名とコンクリフトしないようにする。

エラーコードは、enum等でプログラマの責任で作成する。

独自のNSErrorを作る(http://stack3ios.hateblo.jp/entry/2012/08/20/103554)のようにカテゴリを作ると良い。

]]>
Fri, 28 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/27/build_number_memo.html https://www.mindto01s.com/2012/12/27/build_number_memo.html <![CDATA[ビルド番号に関してのメモ]]> ビルド番号に関してのメモ

リンクだけ

]]>
Thu, 27 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/26/lldb_po.html https://www.mindto01s.com/2012/12/26/lldb_po.html <![CDATA[xcodeでlldbコマンド]]> xcodeでlldbコマンド

XCodeのデバッガのコンソールではログの表示だけではなくlldbのコマンドが使える。

以下の3つのコマンドを覚えるだけで便利に使える。

コマンド
コマンド 引数 機能
s   ステップ実行
c   実行を継続
po オブジェクト(式)の評価

poコマンドは多機能で式の評価と言いつつ以下の様に、値の表示、メソッドの実行、変数に代入の機能がある。

  • id型を見る
(lldb) po self.window
(NSWindow *) $7 = 0x000000010230d2c0 <NSWindow: 0x10230d2c0>
  • メソッドの実行
(lldb) po [self openPanel:nil]
<no result>
(lldb) c
  • 変数に代入
(lldb) po theEvents = nil
(SCEvents *) $2 = 0x0000000000000000 <nil>
]]>
Wed, 26 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/25/cfbundledocumenttypes_in_infoplist.html https://www.mindto01s.com/2012/12/25/cfbundledocumenttypes_in_infoplist.html <![CDATA[フォルダを読み込む為のCFBundleDocumentTypesの設定]]> フォルダを読み込む為のCFBundleDocumentTypesの設定

NSDocumentControllerでfolderを受け付ける為に、info.plistの設定を行った。 フォルダータイプを受け付ける為に、CFBundleDocumentTypesの項目に以下の項目を追加した。

追加項目
キー 説明
CFBundleTypeExtensions 任意の拡張子を表す
CFBundleTypeOSTypes fold OS9でフォルダーを示すOSType

しかし、今資料を見たら、これらの設定が”Deprecated”との事。 今のところ動作には問題はないようだが、”Deprecated”な物を使うのは気持ちが悪いので変更する。

単純に書くと

  1. CFBundleTypeExtensionsとCFBundleTypeOSTypesを削除
  2. LSItemContentTypesとLSHandlerRankを追加

で終わり。 詳しく書くと、

変更前のinfo.plistは以下

../../../_images/before_infoplist.tiff

ターゲットのinfoタブでは以下

../../../_images/before_infotab.tiff

両方とも同じinfo.plistファイルの内容を反映して表示しています。

変更後のinfo.plistは以下

../../../_images/after_infoplist.tiff

ターゲットのinfoタブでは以下

../../../_images/after_infotab.tiff

CFBundleTypeExtensionsとCFBundleTypeOSTypesを削除し、LSItemContentTypesとLSHandlerRankを追加した。

追加項目
キー 説明
LSItemContentTypes public.folder フォルダーを表すUTI
LSHandlerRank none  

扱うファイルがLSHandlerRankの値は、扱うファイルがフォルダーの為にnoneとした。

参考文献

iOSアプリケーションを特定のファイル形式に対応させる方法について :
http://safx-dev.blogspot.jp/2010/11/ios_21.html
UTI一覧 : System-Declared Uniform Type Identifiers
http://developer.apple.com/library/mac/#documentation/Miscellaneous/Reference/UTIRef/Introduction/Introduction.html
Info.plist用のキー一覧 : information Property list key Refarence
http://developer.apple.com/library/ios/#documentation/general/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
]]>
Tue, 25 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/24/ditz_6.html https://www.mindto01s.com/2012/12/24/ditz_6.html <![CDATA[man ditzが出ない]]> man ditzが出ない

私の環境では以下のようにして出せる。

こんな所にもmanのパスを通す物なのだろうか? それともrubyのgemと言う物に何か設定をしなければイケナイのかもしれない。

$ man /Library/Ruby/Gems/1.8/gems/ditz-0.5/man/ditz.1
]]>
Mon, 24 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/23/yaml_in_cocoa.html https://www.mindto01s.com/2012/12/23/yaml_in_cocoa.html <![CDATA[CocoaでYAML]]> CocoaでYAML

はじめに

MacOSX上のCocoaでYAMLを読み込む必要が出て来た。 そこでgoogle先生に伺った所、何種類かライブラリが出て来た。トップ表示されていたSyckライブラリを使う事にした。

SyckにはCocoa用ラッパーのフレームワークが入っており、コレをビルド出来ればCocoaから簡単に使う事が出来る。

この文書はその時のビルド方法のメモです。なお、iOS上での動作確認はしてません。

開発環境

開発環境は以下の通りです

MacOSX:10.8.2
XCode:4.5.2
Syck:0.7(commit 2656dcc9e879a26e0d7c36ae45f22150d2692ad0)

ビルド方法

https://github.com/indeyets/syck”から圧縮ファイルをダウンロード解凍する

そのままbootstrapファイルを実行するとエラーになるので一部修正する。 理由は”libtoolize”が見つからないため。

私の環境では”/opt/local/bin/glibtoolize”にMacPortsで入れていたのでlibtoolizeの代わりにglibtoolizeを使うよう以下のように変更した。

#! /bin/sh

set -x
aclocal
autoheader
autoconf
# libtoolizeをglibtoolizeに変更
glibtoolize
automake --foreign --add-missing --copy

この変更を行えばlibsyck.aの生成まで以下のコマンドで行えます。

$ cd 解凍後のディレクトリへのパス
$ ./bootstrap
$ ./configure
$ make

その後、./ext/cocoaにあるYAML.xcodeprojの変更を行う。 修正箇所は3カ所。

  1. “YAML_Prefix.pch”をResourcesフォルダーにコピーしないようにする

おそらく何らかの操作ミスで、コピーする項目に入ってしまったのだろう。以下の図の様にコピー対象から外す。

../../../_images/yaml-PCH-before.tiff

外すとこうなります。

../../../_images/yaml-PCH-after.tiff
  1. Info.plsitの”Bundle ID”を修正する。

“Bundle ID”がプロジェクト作成時の初期設定のままの”com.apple.yourcocoaframework”なので変更する。 ここで、IDは制作者に敬意を払い、”net.thimbleby.will.yaml.in.cocoa”とする。

元の設定が以下のようになっているのを

../../../_images/yaml-InfoPlist-before.tiff

この様に変更する。

../../../_images/yaml-InfoPlist-after.tiff
  1. GSNSDataExtensions.mで配列の容量が足らない気がするので追加

これは非常に簡単。

file:ext/cocoa/src/Base64 NSData code/GSNSDataExtensions.m
line:200

の配列outbufの長さをを3から4に変えただけ。

unsigned char         inbuf [4], outbuf [3];

unsigned char         inbuf [4], outbuf [4];

に変えた。

アドレスを他にも、32bitから64bitへ移行したので、NSLogやprintfの%dを%ldに変える必要があるらしいが、詳しい事が判らないのでパス。

最後にビルドする。ここではDeploymentビルドを手軽にコピーしたいのでxcodebuild経由でビルドしています。

$ xcodebuild -configuration Deployment -sdk macosx10.8

これで”ext/cocoa/build/Deployment”にYAML.frameworkが出来ます。

../../../_images/yaml-framework.tiff

参考文献

Cocoa Wapperを書いた人のサイト http://will.thimbleby.net/yaml-in-cocoa/

多分Syckの公式サイト https://github.com/indeyets/syck

]]>
Sun, 23 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/22/ditz_5.html https://www.mindto01s.com/2012/12/22/ditz_5.html <![CDATA[ditzのreadmeを読む(後半)]]> ditzのreadmeを読む(後半)

後半部分です。力尽きた。

DITZのデータモデル

デフォルトの設定で、Ditzはオープンソース開発のために必要な最低限の機能セットを含んでいます。

経過時間、優先度、開発者へのタスクの割当、期日等の機能は意図的にプラグインシステムに任せるようにしています。

Ditzのプロジェクトは、issues, releases, componentsの3つで構成されています。

Issues:

issuesはBTSの基本的な要素です。Ditzのissueは機能(feature), バグ(bug), タスク(task)のいずれかです。 しかし、現在の所、このissueの種類は、表示以外には影響しません。

それぞれのissuesは、1つのコンポーネントに属しており、0個か1個のリリースの一部になっています。

それぞれのissuesは、データエクスポートに使えるランダムな40桁の16進数のIDを持っています。

IDは、一意である事が保証されています。IDは通常ユーザーは目にする事はありません。

issueは人が読める形式の名前を持っています。でもその名前は一意でないしデータエクスポートにも使えません。 全てのditzコマンドはissue IDの他にも、issueの名前を使えます。

issue名は特定の状況で変更される事が有ります。例えば、”ditz drop”コマンドの後に変更されます。

issue名は、コメント、タイトル、内容等の中に書く事が出来ます。ditzはissue名が変更された時に自動的にそれらを書き換えます。

Components:

プロジェクトには常に総称的なcomponentが一つあります。そのcomponentの名前はプロジェクト名と同じになってます。

最も単純な場合は、componentはこの総称的なcomponentが唯一の物になります。そして、ユーザーがcomponentにissueを割り当てる問題に煩わされることはありません。

componentは単純にissuesを整理する為の方法です。実際には何の機能も有りません。

issues名は、割当てられるcomponent名から派生して名付けられます。

Releases:

releaseはissueの為の主要なグルーピング機構です。

“ditz status”や”ditz todo”のような状態確認コマンドは常に、releaseによってグルーピングされたissuesに作用します。

releaseが100%完了した時に、そのreleaseはリリースされたとマークを付ける事が出来ます。 そして、そのreleaseに属していたissueは停止したと”ditz status”や”ditz todo”のメッセージに表示されます。

もっと知りたい時は

  • helpを見るか
$ ditz help
  • ソースコードを見てください
$ find $DITZ_INSTALL_DIR -type f | xargs cat

必要条件

  • trollop >= 1.8.2, if running via RubyGems.

インストールするには

http://rubyforge.org/projects/ditz/”から圧縮ファイルをダウンロードするか、

$ gem install ditz

と打ち込んでください。

ライセンス

Copyright (c) 2008 William Morgan.

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

]]>
Sat, 22 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/21/ditz_4.html https://www.mindto01s.com/2012/12/21/ditz_4.html <![CDATA[ditzのreadmeを読む(前半)]]> ditzのreadmeを読む(前半)

正直言って、helpだけではよくわからないので、google先生にお願いしてREADME.txtを翻訳してもらう。 おかしな所はほんのり修正。

まずは前半部分だけ。

ditz

by William Morgan <wmorgan-ditz at the masanjin dot nets>

http://ditz.rubyforge.org

説明

Ditzはシンプルで軽量な分散型バグトラッカーです。 git, darcs, Mercurial, Bazaarのような分散型バージョン管理システムで動作するように設計されています。

また、SVNのような集中型システムで使用することができます。

DitzはYAML形式のファイルを使用して、ディスク上に問題データベースディレクトリを維持/作成します。 このディレクトリは、プロジェクトのソースコードと一緒にバージョン管理下におく事が出来ます。

Ditzは問題データベースファイルを作成/更新するためのシンプルなコマンドラインベースのインターフェイスを持っています。 また、ブラウザで現状を確認出来るように、静的HTMLの生成機能を持っています。(デモはDitzページを参照してください)

Ditzにはモデルフィールドにコマンドを追加し、出力を変更する堅牢なプラグインシステムが含まれています。 バンドルされているプラグインのマニュアルについてはPLUGINS.txtを参照してください。

現状ではDitzは、集中型システム用のバグ申請を行う方法を持っていません。

使い方

Ditzを使用するには、いくつかの異なる方法があります。

  1. ソースコードと同じ場所に問題データベースを設置する。issueはコミットの一部として含まれています。他の開発者からの変更による競合は、通常の方法で解決しマージします。
  2. ソースコードとは別のブランチに問題データベースを設置する。issueの更新をVCSによって管理することができますが、コードに直接結びつけられてはいません。
  3. VCSで問題データベースを管理しない。

これらのオプションはすべてサポートされています。どの方法を選択するかは、あなたのワークフローに依存します。

  • オプション#1
    おそらく非同期分散開発に最も適しています。個々の開発者が面倒を最小限に抑えて問題の状態を変更することができます。
  • オプション#2
    issueの追加/更新とコードの変更は、それぞれ独立してVCSにコミット出来ます。同期開発に最も適しています。(git同期プラグインを参照してください)
  • オプション#3
    集中型Webインターフェイスと同様に、いくつかの他の配布方式でのみ有用です。

コマンドライン要約

プロジェクトをセットアップして、bugs.yamlファイルを作る

  1. ditz init
  2. ditz add-release

issueの追加

  1. ditz add

現状確認

  1. ditz status
  2. ditz todo (or simply “ditz”)

お仕事をする

  1. コードを書く
  2. ditz close <issue-id>
  3. コミットする
  4. 3に戻る

ひと仕事終ったら

  1. ditz release <release-name>
]]>
Fri, 21 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/20/ditz_3.html https://www.mindto01s.com/2012/12/20/ditz_3.html <![CDATA[ditzコマンド補完]]> ditzコマンド補完

ditzのMLを読んでいると、ditzコマンド補完のbashスクリプトが有るらしい事が判った。

しかしインストール場所が判らなかったので、findすると以下の場所にインストールされていた。

$ find / -name "ditz*"
       .
       .
       .
   /Library/Ruby/Gems/1.8/cache/ditz-0.5.gem
   /Library/Ruby/Gems/1.8/doc/ditz-0.5
   /Library/Ruby/Gems/1.8/doc/ditz-0.5/rdoc/files/lib/ditz_rb.html
   /Library/Ruby/Gems/1.8/gems/ditz-0.5
   /Library/Ruby/Gems/1.8/gems/ditz-0.5/bin/ditz
   /Library/Ruby/Gems/1.8/gems/ditz-0.5/contrib/completion/ditz.bash
   /Library/Ruby/Gems/1.8/gems/ditz-0.5/lib/ditz.rb
   /Library/Ruby/Gems/1.8/gems/ditz-0.5/man/ditz.1
   /Library/Ruby/Gems/1.8/specifications/ditz-0.5.gemspec
       .
       .
       .

この中の”ditz.bash”が補完スクリプトなので、”~/.bash_profile”に

source /Library/Ruby/Gems/1.8/gems/ditz-0.5/contrib/completion/ditz.bash

を追加すると補完が効くようになる。正直ditzは補完が無いと使いにくい。

]]>
Thu, 20 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/19/selecting_dot_File_Folder.html https://www.mindto01s.com/2012/12/19/selecting_dot_File_Folder.html <![CDATA[特定のドットファイルがあるフォルダーを選択するには]]> 特定のドットファイルがあるフォルダーを選択するには

はじめに

コマンドラインツールには、ファイル名の先頭に”.”がついたファイルを作成する物がある。所謂ドットファイルだ。

このドットファイルは、Finder上では不可視ファイルになっていて通常は見る事は出来ない。

Cocoaでこのファイルを選択するには、通常

- [NSOpenPanel setShowsHiddenFiles:(BOOL)flag];

にYESを渡して使用する。

しかしこの方法では、全ての不可視ファイルが Open Panel に表示されてしまう。不可視ファイルは想像以上に多いので、ユーザーは混乱するだろう。

../../../_images/selecting_dot_File_Folder_1.tiff

こんな画面は誰も見たくないでしょう?

解決策として、

  1. 混乱を避ける為に不可視ファイルは表示させない
  2. 特定のドットファイルが存在するフォルダーだけを読込み可能にする

以上の2点を満たす方法を考えてみた。

開発環境

開発環境は以下の通りです

MacOSX:10.8.2
XCode:4.5.2

基本方針

NSOpnePanelのdelegateを使う。

- [NSOpenSavePanelDelegate panel:(id)sender
         didChangeToDirectoryURL:(NSURL *)url];

このdelegatメソッドは、OpenPaneで注目中のディレクトリが変更される毎に呼出される。 呼出された時に、ディレクトリを走査し、目的のドットファイルが有るか確認する。

目的のドットファイルが存在した場合だけ

- [NSOpenPanel setCanChooseDirectories:(BOOL)flag];

にYESに設定してフォルダーを読込み可能にする。

実装

OpenPanelを表示してドットファイル”.ditz-config”が存在するフォルダーを読込み可能にするコードを示す。 ここで、”.ditz-config”はditzの設定ファイル名なので、各自の目的に合わせて変更してください。

- (IBAction)openPanel:(id)sender;
{
    NSOpenPanel* theOpenPanel = [NSOpenPanel openPanel];

    [theOpenPanel setAllowsMultipleSelection:NO];
    [theOpenPanel setCanChooseFiles:NO];
    [theOpenPanel setDelegate:self];

    [theOpenPanel beginWithCompletionHandler:^(NSInteger result)
     {
         if (result == NSFileHandlingPanelOKButton)
         {
             NSURL* thePath = [[theOpenPanel URLs] objectAtIndex:0];
             NSLog(@" thePath: %@\n", thePath);
         }
     }
    ];
}

- (void)panel:(id)inSender didChangeToDirectoryURL:(NSURL *)inURL
{
    NSOpenPanel*   theOpenPanel   = inSender;
    NSFileManager* theFileManager = [NSFileManager defaultManager];
    NSString*      theFilePath    = [inURL path];

    NSArray*       theFileLists;
    BOOL           theFlag;

    theFileLists  = [theFileManager contentsOfDirectoryAtPath:theFilePath
                                                        error:nil];
    theFlag       = [theFileLists containsObject:@".ditz-config"];

    [theOpenPanel setCanChooseDirectories:theFlag];
}

実装はコレだけです。

]]>
Wed, 19 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/18/ditz_2.html https://www.mindto01s.com/2012/12/18/ditz_2.html <![CDATA[ditzのサブコマンド]]> ditzのサブコマンド

ditzの調査。

ditzに続くサブコマンドの説明は、”ditz help”で表示される。

$ditz help
Ditz commands:

            add: Add an issue
  add-component: Add a component
  add-reference: Add a reference to an issue
    add-release: Add a release
        archive: Archive a release
         assign: Assign an issue to a release
      changelog: Generate a changelog for a release
          close: Close an issue
        comment: Comment on an issue
           drop: Drop an issue
           edit: Edit an issue
           grep: Show issues matching a string or regular expression
           help: List all registered commands
           html: Generate html status pages
           init: Initialize the issue database for a new project
            log: Show recent activity
    reconfigure: Rerun configuration script
        release: Release a release
       releases: Show releases
  set-component: Set an issues component
       shortlog: Show recent activity (short form)
           show: Describe a single issue
          start: Start work on an issue
         status: Show project status
           stop: Stop work on an issue
           todo: Generate todo list
       unassign: Unassign an issue from any releases
       validate: Validate project status

Use "ditz help <command>" for details.

ここでサブコマンドのhelpは”ditz help COMMAND”で表示されると書いてあるけど、あまり詳しくない。

表にまとめるとこんな感じ。

サブコマンド一覧
サブコマンド 引数 機能
add   issueの追加
add-component   componentの追加
add-reference <issue> issueに他のissueへの参照を追加
add-release [name] releaseを追加
archive <release> [dir] releaseをアーカイブ
assign <issue> [release] issueをreleaseに割り当てる
changelog <release> releaseかchangelogを生成
close <open_issue> issueを閉じる
comment <issue> issueにコメントを追記する
drop <issue> issueを削除
edit <issue> issueを編集
grep <string> 文字列か正規表現に一致したissueを表示する
help [command] サブコマンドのhelpを表示する
html [dir] htmlページを生成
init   新規プロジェクトを作成する
log   活動履歴を表示する
reconfigure   設定を再設定する
release <unreleased_release> releaseを公開する
releases   リリースのリストを表示する
set-component <issue> [component] issuesをcomponentに登録する
shortlog   短い形式で活動履歴を表示する
show <issue> issueの詳細を表示する
start <unstarted_issue> issueの作業を開始する
status [release] プロジェクトの状態を表示する
stop <started_issue> issueの作業を停止する
todo [release] ToDoリストを表示する
unassign <assigned_issue> issueをreleaseの登録から外す
validate   プロジェクトのstatusを検証する

用語の整理や関係を書かないと判りにくいな。

]]>
Tue, 18 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/12/17/ditz_1.html https://www.mindto01s.com/2012/12/17/ditz_1.html <![CDATA[ditzのオプション]]> ditzのオプション

ditzの調査。

まずは”ditz –help”をGoogle先生に読んでもらう。

$ditz --help
ditz 0.5
Options:
     --issue-dir, -i <s>:   Issue database dir (default: bugs)
   --config-file, -c <s>:   Configuration file (default: ./.ditz-config)
  --plugins-file, -p <s>:   Plugins file (default: ./.ditz-plugins)
           --verbose, -v:   Verbose output
        --list-hooks, -l:   List all hooks and descriptions, and quit.
           --version, -e:   Print version and exit
              --help, -h:   Show this message

Google先生にお伺いをして、多少手直しすると、こんな感じ。

オプションとその機能
オプション 機能
–issue-dir, -i <s> Issue databaseディレクトリの指定 (デフォルト値: bugs)
–config-file, -c <s> 設定ファイルの指定 (デフォルト値: ./.ditz-config)
–plugins-file, -p <s> プラグインディレクトリの指定 (デフォルト値: ./.ditz-plugins)
–verbose, -v Verbose(冗長)出力モードにする。
–list-hooks, -l 全てのHookとその説明を出力する。
–version, -e バージョン番号を表示する。
–help, -h このヘルプメッセージを表示する。

これだけだと、全く意味不明だな。

]]>
Mon, 17 Dec 2012 00:00:00 +0900
https://www.mindto01s.com/2012/11/21/test.html https://www.mindto01s.com/2012/11/21/test.html <![CDATA[GitでVCSが分散出来るならば、BTSも分散したい]]> GitでVCSが分散出来るならば、BTSも分散したい

Ditzと呼ばれる分散型BTSソフトウェアがある。

インストールして少し使ってみたが、まだ使い方がよくわからない。

]]>
Wed, 21 Nov 2012 00:00:00 +0900