Objective-C (OS X): OS X 用のスクリーンセーバーを作成する

完成品

時計を表示するだけのデモスクリーンセーバーです (for 10.10 以上)。
署名とかしてませんが、「右クリック」→「開く」→なんかいわれるけど無視して「開く」すると、システム環境設定「スクリーンセーバー」に「Clock」が追加されます。
Mac の設定次第ではインストールできないかも。その時はごめんなさい。

 

作成

Xcode OS X プロジェクトから「Screen Saver」を指定して新規プロジェクト作成。
Xcode 6.4 時点だと Objective-C プロジェクトとして作られるが設定をごにょごにょすると Swift でも作れるようになるが、とりあえず Objective-C で適当に ScreenSaverView クラスを実装。

//
//  ClockView.m
//  Clock
//
//  Created by TakahashiNobuhiro on 2015/07/19.
//  Copyright (c) 2015年 feb19. All rights reserved.
//

#import "ClockView.h"

@implementation ClockView

- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
    self = [super initWithFrame:frame isPreview:isPreview];
    if (self) {
        [self setAnimationTimeInterval:1/10.0];
    }
    return self;
}

- (void)startAnimation
{
    [super startAnimation];
}

- (void)stopAnimation
{
    [super stopAnimation];
}

- (void)drawRect:(NSRect)rect
{
    [super drawRect:rect];
}

- (void)animateOneFrame
{
    int width = [self frame].size.width;
    int height = [self frame].size.height;
    float strSize = 120.0;
    
    NSColor* back = [NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:1];
    NSRect rect = NSMakeRect(0, 0, width, height);
    
    NSDate *date = [NSDate date];
    NSDateFormatter *outputDateFormatter = [[NSDateFormatter alloc] init];
    NSString *outputDateFormatterStr = @"MM/dd HH:mm:ss";
    [outputDateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"JST"]];
    [outputDateFormatter setDateFormat:outputDateFormatterStr];
    NSString *outputDateStr = [outputDateFormatter stringFromDate:date];
    
    NSColor* color = [NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:1.0];
    
    NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithString:outputDateStr];
    [str addAttribute:NSFontAttributeName
                value:[NSFont fontWithName:@"Helvetica" size:strSize]
                range:NSMakeRange(0, [outputDateStr length])];
    [str addAttribute:NSForegroundColorAttributeName
                value:color
                range:NSMakeRange(0, [outputDateStr length])];
    
    // Draw string
    [back set];
    [NSBezierPath fillRect:rect];
    [str drawAtPoint:NSMakePoint(120,(float)height/2-21.0)];
    
    return;
}

- (BOOL)hasConfigureSheet
{
    return NO;
}

- (NSWindow*)configureSheet
{
    return nil;
}

@end

 

Edit Scheme でデバッグ実行できるようにする

ScreenSaver プロジェクトは、普通の OS X アプリみたいに Run しても初期設定ではただのプラグイン形式なので実行されない。
デバッグ実行できるようにするために、Edit Scheme から実行エンジンを呼びだすようにする。
(ただこの辺が毎回キャッシュがかかったりして、自分の環境だとうまくいっていない→後述)

Edit Scheme > Run > executable で ScreenSaverEngine を指定して、Arguments で

-background
-module "$(EXECUTABLE_NAME)"

を指定、さらに Options で「Allow debugging when using document Versions Browser」のチェックを外すとログが静かになる。

実行すると、デスクトップの背景として作成したスクリーンセーバーが実行される。

 

補足)コマンドでスクリーンセーバーを実行(上記設定で行われること)

コマンドでスクリーンセーバーを実行する方法がある。デバッグ実行ではこれが行われている。
ScreenSaverEngine というアプリを呼び出す。

$ /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine -background -module "スクリーンセーバー名.saver"

background オプションをつけると、デスクトップでスクリーンセーバーを実行できる。
module オプションで指定。

 

謎)キャッシュ?

* スクリーンセーバーを手動でインストールしてからじゃないと上記コマンドが実行できない気がする
* DerivedData (~/Library/Developer/Xcode/DerivedData) を毎回消して Xcode のキャッシュをクリアしないと新しい saver ファイルになっていない気がする
(なんか俺の環境だけの気がするけど・・・)

ここの挙動がよくわからんすぎて挫折気味です