ホームページ開発ツール>Xojo / Real Studio Trial and Error・XcodeとCocoaのDeclareでDockTilePlugInを試す(アイコン編)

 Xojo / Real Studio Trial and Error

XcodeとCocoaのDeclareでDockTilePlugInを試す(アイコン編)

目次
 はじめに

 以下は、Xojo Cocoaビルドについての話題です。

 ドックアイコン関連の処理を担うDockTilePlugInについて調べてみました。

 なお検証には、Xojo 2021 Release 2.1とXcode 13.3.1を用いています。(Mac mini 2018 + macOS 12.3.1 Monterey)
注)Xojoは標準でDockItemクラスを持っていて、ドックアイコンの再描画は可能です。以下は、標準機能だけでは不足がある場合を想定しています。

 方針

 かねてより、純正アプリであるカレンダー.appのドックアイコンが当日の日付を表示していることに、どうやっているんだろうと思っていました。
 少し調べてみたら、どうもNSDockTilePluginというプラグインが関係しているらしい、ということが分かりました。
 ただし、使い方は見当もつかなかったので、まずはサンプルを探してみたところ、以下が見つかりました。

 参考サイト(1):NSDockTilePlugin tutorial.... | Apple Developer Forums
 参考サイト(2):GitHub - CartBlanche/MacDockTileSample: Shows how to write a NSDockTilePlugin, so your DockTile can update while the app isn't running

 ダウンロードして眺めてみたところ、
 (1) 本体とプラグイン(バンドル)は別々にビルドする。
 (2) プラグインが担うのは、ドック内アイコンの描画と、ドックアイコン右クリック時のメニュー表示のみ。
 (3) アイコン描画のきっかけは、通知センターから受け取るのが作法?のような印象。

 問題は、Xojoで利用できるかどうかですが、どうもプラグインのビルドはXojoでは無理そうです。(バンドル単体のビルドが出来ない??)
 ですが、ビルド済プラグインがあれば、本体部分をXojoで作成することはできそうです。(Metalの時と同じアプローチ。)

 さて、上掲サンプルは、(残念ながら当初の期待とは異なり)バッジを表示するものでした。
 さらに探すと以下が見つかりました。

 参考サイト(3):How to display a Custom View inside macOS Dock using NSDockTile | This Dev Brain by Michal Tynior | This Dev Brain by Michal Tynior

 これは、プラグイン内でドックアイコンを作って描画する、という方式を採用しています。
では、カレンダー.appはどうやっているのか。当初はベースとなる画像を持っておき、月日のみリアルタイムで描画しているのかと思ったのだが、Assets.carも含めてResourcesフォルダーを探しても、それらしい画像は見当たらなかった。バイナリーを検索してもImageNamed:のようなメソッドが呼ばれている形跡もなく、ここは謎。CALayerを使えばグラデーションやシャドウも容易なので、カレンダー.appのアイコン程度ならリアルタイム描画もありかもしれないが、はたして…
 一方、日付変更の情報はNSCalendarDayChangedNotificationで得るのが筋(カレンダー.appも利用していることを確認)のようなので、これを使うこととします。

 ということで、今回はアイコン処理について、二つ作ってみることにします。(注:メニュー関連は対象としません。)

 一つ目は、MacDockTileSampleをアレンジした、二つのアプリで構成されるものです。
 一方は、タイマーで数値をカウントアップしながら通知センターに送信(以下、送信アプリ)し、もう一方は、通知センターから通知(カウント)を受け取ってアイコンバッジに表示(以下、受信アプリ)します。

 二つ目は、カレンダー.appと同様に、まずは当日の月日を表示し、日付が変わったら更新します。
 といっても、前述の通りカレンダー.appの方式は不明なので、ここでは月日を含め、全体をリアルタイムに描画する方式としました。
 作成にあたっては、以下のサイト他を参考にさせて頂きました。

 参考サイト(4):[iOS] CALayerが便利そう - Qiita
 参考サイト(5):Swift CAShapeLayerで図形にグラデーションをつける方法 | by Swiftでアプリな日々🐟 | Medium
 参考サイト(6):[XCODE] UIViewを角丸にする、ドロップシャドウをかける - YoheiM .NET
 参考サイト(7):CALayerのサブクラスCATextLayerを使ってみる - Qiita
 参考サイト(8):CATextLayerの上下中央揃え - Qiita
 参考サイト(9):iphone - Can't compile code when working with CALayer - Stack Overflow
 参考サイト(10):How to extract current year, month, and day from an NSDate with NSCalendar, NSDateComponents, and NSDateFormatter

 以上を踏まえ、それぞれの仕様は以下の通りとしました。

 例1(バッジの遠隔操作)
 例2(日付自動更新)

 XcodeでのPluginビルド(例1)
注:以下は、Xcode上でプラグインをデバッグするための、本体側の設定ステップを含みます。
  1. Xcodeで新規プロジェクトを作成(テンプレートダイアログで「App」を選択。Product Nameはここでは「NSDockTilePlugIn1」)
  2. プロジェクトにNew Groupを追加。(名前はここでは「DockTilePlugin」)
  3. DockTilePluginに「New File...」でファイル作成(テンプレートダイアログで「Cocoa Class」を選択。ファイル名はここでは「DockTilePlugIn」)
  4. DockTilePlugIn.hの#import〜@endを、以下に置き換え
    #import <Cocoa/Cocoa.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface DockTilePlugIn : NSObject <NSDockTilePlugIn> {
    	id notifyObserver;
    }
    @property(retain) id notifyObserver;
    @end
    
  5. DockTilePlugIn.mの#import〜@endを、以下に置き換え
    #import "DockTilePlugIn.h"
    @implementation DockTilePlugIn
    @synthesize notifyObserver;
    // Dockアイコンを更新
    static void updateIcon(NSDockTile *tile, NSString *str) {
    	// バッジを表示
    	[tile setBadgeLabel:str];
    }
    
    - (void)setDockTile:(NSDockTile *)dockTile {
    	if (dockTile) {
    		// 通知センターに登録
    		self.notifyObserver = [[NSDistributedNotificationCenter defaultCenter] addObserverForName:@"com.mycompany.docktile.notification" object:nil queue:nil usingBlock:^(NSNotification *notification) {
    			NSDictionary *userInfo = notification.userInfo;
    			NSNumber *myNumber = [userInfo objectForKey:@"Value"];
    			NSInteger value = myNumber.integerValue;
    			NSString *valStr = [NSString stringWithFormat:@"%ld", value];
    			updateIcon(dockTile, valStr);
    		}];
    		// 初期状態は0をセット(送信アプリが動作していないことが前提)
    		updateIcon(dockTile, @"0");
    	} else {
    		// macOS 10.11以降では解除は不要になったので、OSのバージョンを見た方がいいかも
    		[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.notifyObserver];
    	}
    }
    @end
    

    以下の用語が示す場所の例
    (クリックで拡大。注:画面はNSDockTilePlugIn2のものになっているが、1も同様)
    S Shot1 S Shot2
  6. プロジェクトにターゲットを追加。(TARGETS下部の+ボタンを押し、テンプレートダイアログで「Bundle」を選択。Product Nameはここでは「DockTilePlugin」)
  7. プロジェクトのTARGETSからDockTilePlugInを選び、Build Settingsで、Packaging > Wrapper Extensionをdocktilepluginに。
  8. プロジェクトのTARGETSからDockTilePlugInを選び、Build Phasesで、Compile SourcesにDocTilePlugin.mを追加。
  9. プロジェクトのTARGETSからDockTilePlugInを選び、Infoで、Key : Principal class、Value : DockTilePlugInを追加。
  10. プロジェクトのTARGETSからNSDockTilePlugIn1を選び、Build Phasesで、上段の+ボタンを押して、New Copy Files Phaseを選び、DestinationをPluginsに。下段の+ボタンを押して、ダイアログからDockTilePlugin.docktilepluginを選択。
  11. プロジェクトのTARGETSからNSDockTilePlugIn1を選び、Infoで、Key : Dock Tile plugin path、Value : DockTilePlugIn.docktilepluginを追加。
  12. ビルド
  13. 出来上がったDockTilePlugin.docktilepluginフォルダーを、PlugInsフォルダーを新規作成して内部に置く。(PlugInsの置き場所は任意)
注)プラグインの名前やIdentifierは、同じものを使うと誤動作する可能性がありますので、複数のプロジェクトを作成する場合は、各々固有化して下さい。

 Xojoでの実装(例1)
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
 まずは通知を送信するアプリです。
  1. Xojoで新規プロジェクトを作成
  2. 以下をWindow1にペースト(できなければConstantsに、Constant Name:kIdentifier、Type:String、を追加)後、Default Valueにcom.mycompany.docktile.notificationをコピー
    Public Const kIdentifier as String = com.mycompany.docktile.notification
    
  3. Window1に、PushButton(Name:PushButton1)、ProgressBar(Name:ProgressBar1、Value:0)、Timer(Name:Timer1、Run Mode:Off、Period:100)を追加
  4. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // 初期化
      ProgressBar1.Value=0  // プログレスバー
      
      // タイマー起動
      Timer1.Mode=2
    End Sub
    
  5. 以下をTimer1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // インクリメント
      progressValue=progressValue+1
      
      // 100を超えたら0に戻す
      if progressValue>=100 then
        progressValue=0
      end if
      
      // 通知を発行
      PutPost()
    End Sub
    
  6. 以下をWindow1にペースト
    Sub Open() Handles Open
      // 通知センターに通知名と受け取り手を登録
      Dim tmp As NotificationCenter = new NotificationCenter("myNotif", kIdentifier, AddressOf GetPost)
    End Sub
    
  7. 以下をWindow1にペースト
    Private Sub GetPost(notify As Ptr)
      // notifyが無効なら終了
      if notify=nil then goto Termination
      
      // notifyからuserInfoを取得
      Declare Function userInfo Lib "Cocoa" Selector "userInfo" (receiver As Ptr) As Ptr
      Dim info As Ptr = userInfo(notify)
      if info=nil then goto Termination  // userInfoが無効なら終了
      
      // userInfoから値を取得
      Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As Ptr
      Dim vPtr As Ptr = objectForKey(info, "Value")
      if vPtr=nil then goto Termination  // 値が無効なら終了
      
      // 値を取得
      Declare Function integerValue Lib "Cocoa" Selector "integerValue" (receiver As Ptr) As Integer
      progressValue = integerValue(vPtr)
      
      // 0が渡ってきたら終了
      if progressValue=0 then goto Termination
      
      // プログレスバーに値をセット
      ProgressBar1.Value=progressValue
      
      // 戻る(ないと、以下が実行されてしまう)
      return
      
      
      Termination: // label to jump to
      
      ProgressBar1.Value=0  // プログレスバーリセット
      Timer1.Mode=0  // タイマー停止
      msgBox "Done"  // メッセージ表示
    End Sub
    
  8. 以下をWindow1にペースト
    Private Sub PutPost()
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // 通知と一緒に渡すパラメータを生成
      Dim numb As Ptr = NSClassFromString("NSNumber")  // 整数をNSNumber形式に変換
      Declare Function numberWithInteger Lib "Cocoa" Selector "numberWithInteger:" (receiver As Ptr, path As Integer) As Ptr
      numb = numberWithInteger(numb, progressValue)
      Dim dict As Ptr = NSClassFromString("NSDictionary")  // NSDictionaryにセット
      Declare Function dictionaryWithObject Lib "Cocoa" Selector "dictionaryWithObject:forKey:" (receiver As Ptr, objt As Ptr, key As CFStringRef) As Ptr
      dict = dictionaryWithObject(dict, numb, "Value")
      
      // defaultCenterを取得
      Dim nCenter As Ptr = NSClassFromString("NSDistributedNotificationCenter")
      Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr
      nCenter = defaultCenter(nCenter)
      
      // 通知を発行
      Declare Sub postNotification Lib "Cocoa" Selector "postNotificationName:object:userInfo:" (receiver As Ptr, nam As CFStringRef, obj As CFStringRef, info As Ptr)
      postNotification(nCenter, kIdentifier, nil, dict)
    End Sub
    
  9. 新規クラスを作成(名前は、ここでは「NotificationCenter」とした。)
  10. 以下をNotificationCenterにペースト(できなければ移譲に、名前:ActionDelegate、を追加)
    Private Sub ActionDelegate(notify As Ptr)
    
  11. 以下をNotificationCenterにペースト
    Public Sub Constructor(clsName As String, notName As String, action As ActionDelegate)
      // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      
      // ObserverとSelectorを生成
      MakeObserver(clsName)
      
      // defaultCenterを取得
      Dim nCenter As Ptr = NSClassFromString("NSDistributedNotificationCenter")
      Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr
      nCenter = defaultCenter(nCenter)
      
      // defaultCenterにObserver/Selector/名前/オブジェクトをセット
      Declare Sub addObserver Lib "Cocoa" Selector "addObserver:selector:name:object:" (receiver As Ptr, obs As Ptr,sel As Ptr, nam As CFStringRef, obj As Ptr)
      addObserver(nCenter, NotifyObserver, NotifySelector, notName, nil)
      
      // クラス生成元でActionを受け取るメソッドを登録
      ActionHandler = action
    End Sub
    
  12. 以下をNotificationCenterにペースト
    Private Sub MakeObserver(clsName As String)
      // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。
      Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
      Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr
      
      // Declare宣言
      Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr
      Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr)
      Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean
      
      // 既にインスタンス作成済なら戻る
      if NotifyObserver <> nil then
        return
      end if
      
      // クラス名をmyNotif、メタクラス名をNSObjectにして、生成
      Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), clsName, 0)
      // ランタイムに登録(参照を可能とするため)
      objc_registerClassPair newClassId
      // 通知の受け口となるメソッドを追加(getPost:をXojo側で用意したgetPostメソッドで受け取る。)
      NotifySelector = NSSelectorFromString("getPost:")
      if not class_addMethod (newClassId, NotifySelector, AddressOf getPost, "v@:@") then
        msgBox "error."
        return
      end if
      
      // 上記で生成したクラスのインスタンスを作成
      Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr
      Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr
      Dim targetId As Ptr = init(alloc(newClassId))
      
      // インスタンスを保持
      NotifyObserver = targetId
    End Sub
    
  13. 以下をNotificationCenterにペースト(できなければプロパティに、名前:NotifyObserver、データ型:Ptr、を追加)
    Private Property NotifyObserver as Ptr
    
  14. 以下をNotificationCenterにペースト(できなければプロパティに、名前:NotifySelector、データ型:Ptr、を追加)
    Private Property NotifySelector as Ptr
    
  15. 以下をNotificationCenterにペースト
    Private Shared Sub getPost(id As Ptr, SEL As CString, notify As Ptr)
      ActionHandler.Invoke(notify)  // クラス生成元でActionを受け取るメソッドを呼び出す
    End Sub
    
  16. 以下をNotificationCenterにペースト(できなければ共有プロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
    Private Shared Property ActionHandler as ActionDelegate
    
 引き続き、通知を受信するアプリです。こちらはコードの実装はありません。ファイル関連の処理のみです。
  1. Xojoで新規プロジェクトを作成

  2. 以下の内容でテキストファイルを作成し、名前をInfo.plistとして保存
    <?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>NSDockTilePlugIn</key>
    	<string>DockTilePlugin.docktileplugin</string>
    </dict>
    </plist>
    
  3. プロジェクトに、Info.plistをドラッグ&ドロップ

  4. プロジェクト左ペインのBuild Settings>macOS上で右クリックし、Add to "Build Settings">Build Step>Copy Filesを選択
  5. InspectorのDestinationにContents Folderを指定。
  6. 中央ペインにPlugIns(上記「XcodeでのPluginビルド」で作成したもの)をドラッグ&ドロップ
 受信アプリを起動後に送信アプリを実行すると、カウントの変化に応じてDockアイコンバッジの値が変化することを確認しました。
S Shot3

 XcodeでのPluginビルド(例2)
注1:以下は、Xcode上でプラグインをデバッグするための、本体側の設定ステップを含みます。
注2:出自の異なるコードを組み合わせたため、書式が統一されていません。気になる場合は変更して下さい。
  1. Xcodeで新規プロジェクトを作成(テンプレートダイアログで「App」を選択。Product Nameはここでは「NSDockTilePlugIn2」)
  2. プロジェクトにNew Groupを追加。(名前はここでは「DockTilePlugin」)
  3. DockTilePluginに「New File...」でファイル作成(テンプレートダイアログで「Cocoa Class」を選択。ファイル名はここでは「DockTilePlugIn」)
  4. DockTilePlugIn.hの#import〜@endを、以下に置き換え
    #import <Cocoa/Cocoa.h>
    @import QuartzCore;
    NS_ASSUME_NONNULL_BEGIN
    @interface DockTilePlugIn : NSObject <NSDockTilePlugIn> {
    	id notifyObserver;
    }
    @property(retain) id notifyObserver;
    @end
    
  5. DockTilePlugIn.mの#import〜@endを、以下に置き換え(注:指定された数値は全て現物合わせによる経験値です。理論値とは異なる場合があります。)
    #import "DockTilePlugIn.h"
    @implementation DockTilePlugIn
    @synthesize notifyObserver;
    
    static NSView * makeIconView() {
    	// 月日の取得
    	NSDate *now = [NSDate date];
    	unsigned units = NSCalendarUnitMonth | NSCalendarUnitDay;
    	NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    	NSDateComponents *components = [calendar components:units fromDate:now];
    	NSInteger month = [components month];
    	NSInteger day = [components day];
    	NSString *str1 = [NSString stringWithFormat:@"%ld月", month];
    	NSString *str2 = [NSString stringWithFormat:@"%ld", day];
    	[calendar release];
    	// viewの生成
    	NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 128, 128)];
    	view.wantsLayer = YES;
    	// ベースとなる角丸矩形(白)
    	CALayer *baselayer = [CALayer layer];
    	baselayer.backgroundColor = [NSColor whiteColor].CGColor;
    	baselayer.frame = CGRectMake(13, 13, 102, 102);
    	baselayer.cornerRadius = 22.0;
    	baselayer.masksToBounds = NO;  // 以下、下部のシャドウ
    	baselayer.shadowOffset = CGSizeMake(0.0f, -3.0f);
    	baselayer.shadowOpacity = 0.25f;
    	baselayer.shadowColor = [NSColor blackColor].CGColor;
    	baselayer.shadowRadius = 1.0f;
    	[view.layer addSublayer:baselayer];
    	// 月表示の下地のマスク(上部の角を角丸にするため)
    	CALayer *masklayer = [CALayer layer];
    	masklayer.backgroundColor = [NSColor blackColor].CGColor;
    	masklayer.frame = CGRectMake(0, -22, 102, 54);
    	masklayer.cornerRadius = 22.0;
    	// 月表示の下地(青)(角丸だと下部も角丸になってしまうので、矩形で生成。上部の角丸はマスクで実現)
    	CALayer *monthlayer = [CALayer layer];
    	monthlayer.backgroundColor = [NSColor blueColor].CGColor;
    	monthlayer.frame = CGRectMake(13, 83, 102, 32);
    	monthlayer.mask = masklayer;
    	[view.layer addSublayer:monthlayer];
    	// 月
    	CATextLayer *textLayer1 = [CATextLayer layer];
    	textLayer1.frame = CGRectMake(13, 48, 102, 64);
    	textLayer1.string = str1;
    	textLayer1.foregroundColor = [NSColor whiteColor].CGColor;
    	textLayer1.font = (__bridge CFTypeRef _Nullable)([NSFont systemFontOfSize:22 weight:NSFontWeightBold]);  // fontSizeは下のパラメーターで決定されるので冗長だが、ウェイトだけを指定するメソッドが見当たらなかった
    	textLayer1.fontSize = 22.0;
    	textLayer1.alignmentMode = kCAAlignmentCenter;
    	[view.layer addSublayer:textLayer1];
    	// 日
    	CATextLayer *textLayer2 = [CATextLayer layer];
    	textLayer2.frame = CGRectMake(13, 0, 102, 92);
    	textLayer2.string = str2;
    	textLayer2.foregroundColor = [NSColor blackColor].CGColor;
    	textLayer2.font = (__bridge CFTypeRef _Nullable)([NSFont systemFontOfSize:66 weight:NSFontWeightLight]);
    	textLayer2.fontSize = 66.0;
    	textLayer2.alignmentMode = kCAAlignmentCenter;
    	[view.layer addSublayer:textLayer2];
    	// 全面にかかる透明度の高いグラデーション
    	CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    	gradientLayer.cornerRadius = 22.0;
    	NSColor *color1 = [NSColor blackColor];
    	NSColor *color2 = [NSColor whiteColor];
    	gradientLayer.colors = [NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil];
    	gradientLayer.startPoint = CGPointMake(0.0, 0.0);
    	gradientLayer.endPoint = CGPointMake(0.0, 1.0);
    	gradientLayer.frame = NSMakeRect(13, 13, 102, 102);
    	gradientLayer.opacity = 0.1;
    	[view.layer addSublayer:gradientLayer];
    	
    	return view;
    }
    
    // アイコン描画
    static void updateIcon(NSDockTile *tile) {
    	NSView *view = makeIconView();  // アイコン画像を生成
    	tile.contentView = view;  // viewをDockTileにセット
    	[tile display];  // DockTileを描画
    	[view release];
    }
    
    - (void)setDockTile:(NSDockTile *)dockTile {
    	if (dockTile) {
    		// 通知センターに登録
    		self.notifyObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSCalendarDayChangedNotification object:nil queue:nil usingBlock:^(NSNotification *notification) {
    			updateIcon(dockTile);
    		}];
    		// 初期化
    		updateIcon(dockTile);
    	} else {
    		// macOS 10.11以降では解除は不要になったので、OSのバージョンを見た方がいいかも
    		[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.notifyObserver];
    	}
    }
    @end
    

    以下の用語が示す場所の例
    (クリックで拡大)
    S Shot1 S Shot2
  6. プロジェクトのPROJECTからNSDockTilePlugIn2を選び、Build Settingsで、Apple Clang - Langauge - Objective-c > Objective-c Automatic Reference CountingをNoに。(注:XojoではARCが使えないのでreleaseメソッドを使用しますが、デフォルトではXcode上でコンパイルエラーになるための対処。)
  7. プロジェクトにターゲットを追加。(TARGETSの+ボタンを押し、テンプレートダイアログで「Bundle」を選択。Product Nameはここでは「DockTilePlugin」)
  8. プロジェクトのTARGETSからDockTilePlugInを選び、Build Settingsで、Packaging > Wrapper Extensionをdocktilepluginに。
  9. プロジェクトのTARGETSからDockTilePlugInを選び、Build Phasesで、Compile SourcesにDocTilePlugin.mを追加。
  10. プロジェクトのTARGETSからDockTilePlugInを選び、Infoで、Key : Principal class、Value : DockTilePlugInを追加。
  11. プロジェクトのTARGETSからNSDockTilePlugIn2を選び、Build Phasesで、上段の+ボタンを押して、New Copy Files Phaseを選び、DestinationをPluginsに。下段の+ボタンを押して、ダイアログからDockTilePlugin.docktilepluginを選択。
  12. プロジェクトのTARGETSからNSDockTilePlugIn2を選び、Infoで、Key : Dock Tile plugin path、Value : DockTilePlugIn.docktilepluginを追加。
  13. ビルド
  14. 出来上がったDockTilePlugin.docktilepluginフォルダーを、PlugInsフォルダーを新規作成して内部に置く。(PlugInsの置き場所は任意)
注)プラグインの名前やIdentifierは、同じものを使うと誤動作する可能性がありますので、複数のプロジェクトを作成する場合は、各々固有化して下さい。

 Xojoでの実装(例2)

 作成方法は、上掲Xojoでの実装(例1)の受信アプリと同一です。(プラグインの中身(DockTilePlugin.docktileplugin)は上掲・例2を用います。)

 アプリをドックにドロップしたところ、日付が変わるとアイコンの月日が変化することを確認しました。
(注:本体アイコンは設定していないので、Finder上ではデフォルトのアイコンで表示される。実行時またはドロップ時のドック内でのみ、描画アイコンが表示される。)
S Shot4
注:左はカレンダー.app。

 おわりに

 Xcodeの併用という点では、Metalと同様、新規というよりは既存のプロジェクトの拡張に適しているかもしれません。
(通知メッセージがOS標準、または別アプリから送られる場合は、アプリ本体には手を入れる必要がない。)

 メニューについては、次回に続く
追記:プラグインの置き場所は、MacDockTileSampleに倣ってPlugInsフォルダーとしていますが、カレンダー.appではResourcesフォルダーになっています(アイコンの置き場所は通常Resourcesだから、とか?)。どちらに置いても機能はするので、気にしなくていいのかもしれませんが、よく分かりません。

 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):NSDockTilePlugin tutorial.... | Apple Developer Forums
 参考サイト(2):GitHub - CartBlanche/MacDockTileSample: Shows how to write a NSDockTilePlugin, so your DockTile can update while the app isn't running
 参考サイト(3):How to display a Custom View inside macOS Dock using NSDockTile | This Dev Brain by Michal Tynior | This Dev Brain by Michal Tynior
 参考サイト(4):[iOS] CALayerが便利そう - Qiita
 参考サイト(5):Swift CAShapeLayerで図形にグラデーションをつける方法 | by Swiftでアプリな日々🐟 | Medium
 参考サイト(6):[XCODE] UIViewを角丸にする、ドロップシャドウをかける - YoheiM .NET
 参考サイト(7):CALayerのサブクラスCATextLayerを使ってみる - Qiita
 参考サイト(8):CATextLayerの上下中央揃え - Qiita
 参考サイト(9):iphone - Can't compile code when working with CALayer - Stack Overflow
 参考サイト(10):How to extract current year, month, and day from an NSDate with NSCalendar, NSDateComponents, and NSDateFormatter


 更新履歴

 2022.05.30 おわりに、に追記を追加。
 2022.05.17 タイトルを一部変更。おわりに、のメニュー関連の記述を改変。XcodeでのPluginビルド(例1,2とも)に、注を追加。
 2022.05.02 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]