Flipswiitchを使ってTweakのON/OFFを行う機会があると思います。その時にFlipswitchと設定app(PreferenceLoader)間でリアルタイムに同期する方法を紹介します。
##基本的な書き方
https://github.com/ichitaso/UIRotation9/blob/master/UIRotationSwitch/Switch.x
#define PREF_PATH @"/var/mobile/Library/Preferences/com.ichitaso.uirotation9.plist"
#define kPrefKey @"enabled"
@interface UIRotationSwitch : NSObject <FSSwitchDataSource>
@end
@implementation UIRotationSwitch
- (FSSwitchState)stateForSwitchIdentifier:(NSString *)switchIdentifier
{
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:PREF_PATH];
id enable = [dict objectForKey:kPrefKey];
BOOL isEnabled = enable ? [enable boolValue] : YES;
return isEnabled ? FSSwitchStateOn : FSSwitchStateOff;
}
これで、「/var/mobile/Library/Preferences/com.ichitaso.uirotation9.plist」内の「enabled」keyのBOOL値によってFlipswitchのON/OFF時の動作が適用されます。
##Flipswitch側での設定のON/OFF
- (void)applyState:(FSSwitchState)newState forSwitchIdentifier:(NSString *)switchIdentifier
{
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:PREF_PATH];
NSMutableDictionary *mutableDict = dict ? [[dict mutableCopy] autorelease] : [NSMutableDictionary dictionary];
switch (newState) {
case FSSwitchStateIndeterminate:
return;
case FSSwitchStateOn:
[mutableDict setValue:@YES forKey:kPrefKey];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOn"), NULL, NULL, true);
break;
case FSSwitchStateOff:
[mutableDict setValue:@NO forKey:kPrefKey];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOff"), NULL, NULL, true);
break;
}
[mutableDict writeToFile:PREF_PATH atomically:YES];
// Update Flipswitch state
[[FSSwitchPanel sharedPanel] stateDidChangeForSwitchIdentifier:@"com.ichitaso.uirotationfs"];
}
ここがFlipswitchを動かした時に動作する部分です。
「NSMutableDictionary」によってplistファイルの書き込みと「CFNotificationCenterPostNotification」で通知を行います。
ONにするときは「FSSwitchStateOn」
[mutableDict setValue:@YES forKey:kPrefKey];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOn"), NULL, NULL, true);
OFFは「FSSwitchStateOff」
[mutableDict setValue:@NO forKey:kPrefKey];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOff"), NULL, NULL, true);
最後にplistファイルに書き込みと、Switchの反映を行います。
[mutableDict writeToFile:PREF_PATH atomically:YES];
// Update Flipswitch state
[[FSSwitchPanel sharedPanel] stateDidChangeForSwitchIdentifier:@"com.ichitaso.uirotationfs"];
##通知の処理
このままでは通知が反映されないので、少し工夫します。Tweak側で通知を受けた時に、PreferenceLoaderでも反映されるようにします。
@interface UIRController : NSObject
{
NSUserDefaults *prefs;
}
@property (nonatomic) BOOL prefsChangedFromSettings;
@property (nonatomic) BOOL enable;
+ (UIRController *)sharedInstance;
- (void)updateSettings;
- (void)setEnabled:(BOOL)enable;
@end
#import "UIRController.h"
#define PREF_PATH @"/var/mobile/Library/Preferences/com.ichitaso.uirotation9.plist"
#define Preferences @"com.ichitaso.uirotation9"
@implementation UIRController
+ (UIRController *)sharedInstance {
static dispatch_once_t p = 0;
__strong static id _sharedObject = nil;
dispatch_once(&p, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
- (id)init {
if (self = [super init]) {
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:PREF_PATH];
id isEnabled = [dict objectForKey:@"enabled"];
BOOL enabled = isEnabled ? [isEnabled boolValue] : YES;
prefs = [[NSUserDefaults alloc] initWithSuiteName:Preferences];
[prefs registerDefaults:@{
@"enable": @YES
}];
[prefs setBool:enabled forKey:@"enabled"];
_prefsChangedFromSettings = NO;
_enable = enabled;
}
return self;
}
- (void)updateSettings
{
[self setEnabled:[prefs boolForKey:@"enabled"]];
_prefsChangedFromSettings = NO;
}
- (void)setEnabled:(BOOL)enable
{
if (enable == _enable)
return;
_enable = enable;
if (!_prefsChangedFromSettings)
[prefs setBool:_enable forKey:@"enabled"];
}
@end
このようなファイルを用意してTweak.xmと一緒にcompileするようにします。
// Called by the flipswitch toggle
//==============================================================================
#import "UIRController.h"
void prefsChanged() {
[UIRController sharedInstance].prefsChangedFromSettings = YES;
[[UIRController sharedInstance] updateSettings];
}
void switchToggleOn() {
[[UIRController sharedInstance] setEnabled:YES];
}
void switchToggleOff() {
[[UIRController sharedInstance] setEnabled:NO];
}
//==============================================================================
%ctor
{
@autoreleasepool {
...
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
NULL,
(CFNotificationCallback)switchToggleOn,
CFSTR("com.ichitaso.uirotation-switchOn"),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
NULL,
(CFNotificationCallback)switchToggleOff,
CFSTR("com.ichitaso.uirotation-switchOff"),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
...
これでdylib側とPreferenceLoaderで設定がリンクされるようになりました。
##設定(PreferenceLoader)
https://github.com/ichitaso/UIRotation9/blob/master/Preference.m
@interface UIRPreferenceController : PSListController <UIActionSheetDelegate, UIAlertViewDelegate>
{
}
- (void)reloadPrefs:(NSNotification *)notification;
@end
NSNotificationクラスを使用します。
- (instancetype)init
{
self = [super init];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"reloadPrefs" object:nil];
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
NULL,
(CFNotificationCallback)reloadPrefsCallBack,
CFSTR("com.ichitaso.uirotation-switchOn"),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
NULL,
(CFNotificationCallback)reloadPrefsCallBack,
CFSTR("com.ichitaso.uirotation-switchOff"),
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadPrefs:) name:@"reloadPrefs" object:nil];
return self;
}
「- (instancetype)init」で、通知処理を追加します。
void reloadPrefsCallBack() {
[[NSNotificationCenter defaultCenter] postNotificationName:@"reloadPrefs" object:nil];
}
- (void)reloadPrefs:(NSNotification *)notification
{
[self reloadSpecifiers];
}
これで通知を受けた際に[self reloadSpecifiers];で設定の更新を行うようにすると、Flipswitch側で操作した時に設定app側でもリアルタイムでスイッチがON/OFFされるようになりました。
細かい部分ですが、ちょっとしたこだわりですね!
それではまた。