ICDeviceBrowser - does not find camera in Mac OS 13? - cocoa

We are using ICDeviceBrowser to find the camera devices connected to a Mac system and it runs ok in macOS 12 but the delegate is never called and the device array is always empty in macOS 13.
#interface NKDeviceBrowser () < NKDeviceBrowserDelegate, ICDeviceBrowserDelegate>
{
-(void)startICDeviceBrowser
{
NSLog(#"CALL [ %# ]", NSStringFromSelector(_cmd));
if ((_browsedDeviceTypeMask & NkDeviceLocationTypeBonjour)) {
mDeviceBrowser = [[ICDeviceBrowser alloc] init];
mDeviceBrowser.delegate = self;
mDeviceBrowser.browsedDeviceTypeMask = (ICDeviceTypeMask)(ICDeviceLocationTypeMaskLocal|ICDeviceTypeMaskCamera|ICDeviceLocationTypeBonjour);
[mDeviceBrowser start];
}
}
-(void)deviceBrowser:(ICDeviceBrowser*)browser didAddDevice:(ICDevice*)addedDevice moreComing:(BOOL)moreComing
{
NSLog(#"CALL [ %# ]", NSStringFromSelector(_cmd));
if ( addedDevice.type & ICDeviceTypeCamera ) {
NSLog(#"Added Device Name %#",addedDevice.name);
if ([_delegate respondsToSelector:#selector(deviceBrowser:didAddDevice:moreComing:)]) {
[_delegate deviceBrowser:self didAddDevice:addedDevice moreComing:moreComing];
}
}
}
}
Its working fine till macOS 12 but not working in macOS 13.

Related

OSX cocoa app - Get safari tab info

I'm wondering if it's possible to get any tab/window info from safari programmatically?
Is there a library to do it?
I'd prefer not applescript, as I've found that - I'd like to know if, and how it's possible in the Cocoa framework.
You can do this with Scripting Bridge, which is like AppleScript translated into Objective-C, or with accessibility objects, which you can inspect with Developer Tool Accessibility Inspector. Both technogies have their quirks and aren't documented very well.
Edit:
Scripting Bridge example:
SafariApplication *SafariApp = [SBApplication applicationWithBundleIdentifier:#"com.apple.Safari"];
for (SafariWindow *window in SafariApp.windows)
{
for (SafariTab *tab in window.tabs)
NSLog(#"%#", tab.name);
}
The hierarchy of accessibility objects in Safari is
AXApplication
AXWindow
AXTabGroup
AXRadioButton
Example (doesn't win a prize in a beauty contest):
static NSArray *getAXUIElements(AXUIElementRef theContainer, CFStringRef theRole)
{
// get children of theContainer
AXError error;
NSMutableArray *array = [NSMutableArray array];
CFTypeRef children;
error = AXUIElementCopyAttributeValue(theContainer, kAXChildrenAttribute, &children);
if (error != kAXErrorSuccess)
return nil;
// filter children whose role is theRole
for (CFIndex i = 0; i < CFArrayGetCount(children); i++)
{
AXUIElementRef child = CFArrayGetValueAtIndex(children, i);
CFTypeRef role;
error = AXUIElementCopyAttributeValue(child, kAXRoleAttribute, &role);
if (error == kAXErrorSuccess)
{
if (CFStringCompare(role, theRole, 0) == kCFCompareEqualTo)
[array addObject:(__bridge id)child];
CFRelease(role);
}
}
CFRelease(children);
return [NSArray arrayWithArray:array];
}
static void logTabs()
{
// get the title of every tab of every window of Safari
NSArray *appArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.Safari"];
AXUIElementRef SafariApp = AXUIElementCreateApplication([[appArray objectAtIndex:0] processIdentifier]);
if (SafariApp)
{
NSArray *windowArray = getAXUIElements(SafariApp, kAXWindowRole);
for (id window in windowArray)
{
NSArray *tabGroupArray = getAXUIElements((__bridge AXUIElementRef)(window), kAXTabGroupRole);
for (id tabGroup in tabGroupArray)
{
NSArray *radioButtonArray = getAXUIElements((__bridge AXUIElementRef)(tabGroup), kAXRadioButtonRole);
for (id radioButton in radioButtonArray)
{
CFTypeRef title = NULL;
AXError error = AXUIElementCopyAttributeValue((__bridge AXUIElementRef)radioButton, kAXTitleAttribute, &title);
if (error == kAXErrorSuccess)
{
NSLog(#"%#", title);
CFRelease(title);
}
}
}
}
CFRelease(SafariApp);
}
}

CloudKit didReceiveRemoteNotification not called on the Mac

I am using the following CKNotification Info and this seems to work fine:
CKNotificationInfo *note = [[CKNotificationInfo alloc] init];
note.alertBody = #"Something Happened";
note.shouldBadge = NO;
note.shouldSendContentAvailable = NO;
When something changes on an iOS device, my Mac app receives a Push notification based on a subscription with this notification. However, didReceiveRemoteNotification is never called so I can't process the event. I need to be able to refresh and fetch new changes. How do I do that?
Calling registerForRemoteNotificationTypes: and implementing didRegisterForRemoteNotificationsWithDeviceToken:
should be enough code, and the App ID should include the Push Notifications service.
I'm using CloudKit in a cross-platform (iOS/OS X) app to synchronize favorites between devices like so:
// OS X specific code
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp registerForRemoteNotificationTypes:NSRemoteNotificationTypeNone];// silent push notification!
}
- (void)application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[self.favCon handleCloudKitNotificationWithUserInfo:userInfo];
}
Note the usage of NSRemoteNotificationTypeNone which means silent push notification! This is how I set up CloudKit in the FavController class:
- (void)getOrCreateFavZoneWithCompletionHandler:(successCompletionHandler)handler {
// check if FavZone exists op
__block int createZone = 0;
CKFetchRecordZonesOperation *fetchRecZonesOp = [[CKFetchRecordZonesOperation alloc] initWithRecordZoneIDs:#[[FavController favRecordZoneID]]];
CKModifyRecordZonesOperation *saveRecZoneOp = [[CKModifyRecordZonesOperation alloc] initWithRecordZonesToSave:nil recordZoneIDsToDelete:nil];
fetchRecZonesOp.fetchRecordZonesCompletionBlock = ^(NSDictionary *recordZonesByZoneID, NSError *operationError) {
if (recordZonesByZoneID.count == 0) {// zone doesn't exist
createZone = 1;
CKRecordZone *favZone = [[CKRecordZone alloc] initWithZoneName:UTXAFavZoneName];
saveRecZoneOp.recordZonesToSave = #[favZone];
NSLog(#"Creating new Zone %#", favZone.zoneID.zoneName);
} else {
NSLog(#"Zone %# already exists.", [FavController favRecordZoneID].zoneName);
}
};
// create FavZone op
saveRecZoneOp.modifyRecordZonesCompletionBlock = ^(NSArray *savedRecordZones, NSArray *deletedRecordZoneIDs, NSError *operationError) {
[self successCompletionHandler:(savedRecordZones.count == createZone) error:operationError informDelegate:YES handler:handler];
};
[saveRecZoneOp addDependency:fetchRecZonesOp];
[[FavController favDatabase] addOperation:fetchRecZonesOp];
[[FavController favDatabase] addOperation:saveRecZoneOp];
}
- (void)subscribeToFavChanges:(successCompletionHandler)handler {
// get current subscription
[[FavController favDatabase] fetchSubscriptionWithID:UTXAFavConCKSubscriptionID completionHandler:^(CKSubscription *subscription, NSError *error) {
if (subscription) {
NSLog(#"using existing subscription: %#", subscription);
[self successCompletionHandler:YES error:nil informDelegate:NO handler:handler];
} else {
CKSubscription *sub = [[CKSubscription alloc] initWithZoneID:[FavController favRecordZoneID]
subscriptionID:UTXAFavConCKSubscriptionID
options:0];// "You must specify 0 for this parameter. Zone subscriptions currently do not support any options."
[[FavController favDatabase] saveSubscription:sub completionHandler:^(CKSubscription *subscription, NSError *error) {
NSLog(#"created new subscription: %# %#", subscription, error);
[self successCompletionHandler:(error == nil) error:error informDelegate:YES handler:handler];
}];
}
}];
}
As soon as I add or remove a record on one device, I'll get a notification on all other device, which I handle like so in the FavController class:
/// #abstract Handle push notifications sent by iCloud.
/// #discussion App delegates call this method when they receive a push notification through didReceiveRemoteNotification.
/// Currently, only airport favorites produce a PN, it is of type CKNotificationTypeRecordZone.
/// #param userInfo The userInfo dict tied to each push notification.
- (void)handleCloudKitNotificationWithUserInfo:(NSDictionary *)userInfo {
[self recursivelyCheckForPreviousCloudKitNotifications];
}
- (void)recursivelyCheckForPreviousCloudKitNotifications {
CKFetchNotificationChangesOperation *fetchOp = [[CKFetchNotificationChangesOperation alloc] initWithPreviousServerChangeToken:_defCon.notificationChangeToken];
__weak CKFetchNotificationChangesOperation *weakOp = fetchOp;
fetchOp.notificationChangedBlock = ^(CKNotification *notification) {
[self handleNotification:notification];
};
fetchOp.fetchNotificationChangesCompletionBlock = ^( CKServerChangeToken *serverChangeToken, NSError *operationError) {
NSLog(#"new notification change token: %#", serverChangeToken);
_defCon.notificationChangeToken = serverChangeToken;
if (weakOp.moreComing) {
NSLog(#"more coming!!");
[self recursivelyCheckForPreviousCloudKitNotifications];
} else {
NSLog(#"done handling notification changes.");
}
};
[[FavController favContainer] addOperation:fetchOp];
}
- (void)handleNotification:(CKNotification *)notification {// withCompletionHandler:(successCompletionHandler)handler {
if (notification.notificationType == CKNotificationTypeRecordZone) {// make sure we handle only zone changes
CKRecordZoneNotification *noti = (CKRecordZoneNotification *)notification;
if ([noti.recordZoneID.zoneName isEqualToString:[FavController favRecordZoneID].zoneName]) {
// received an update for the fav zone
[self queuedFavUpdateFromCloud];
} else {
// received an update for an unknown zone
NSLog(#"WARNING: received an update for an unknown zone: %#", noti.recordZoneID.zoneName);
}
} else {
NSLog(#"WARNING: received unknown notification: %#", notification);
}
}
Okay I've finally figured it out. If you use a CKNotificationInfo for your alerts, didReceiveRemoteNotification will NOT be called on the Mac until and unless you set CKNotificationInfo.soundName to an empty string! This looks like a bug only in OS X (10.10 & 10.11 so far) but can be worked around by this simple change.

How to correctly set application badge value in iOS 8?

Old method
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
is now gives error Attempting to badge the application icon but haven't received permission from the user to badge the application.
Then I tried to use new API (that I'm think is related to badge value)
CKModifyBadgeOperation * operation = [[CKModifyBadgeOperation alloc] initWithBadgeValue:50];
[operation setModifyBadgeCompletionBlock:^(NSError *error) {
NSLog(#"%#", error);
}];
[operation start];
But I'm receiving error <CKError 0x165048a0: "Not Authenticated" (9/1002); "This request requires an authenticated account">
How to set badge or receive some new permissions?
In addition to Daij-Djan's answer: it's possible to stack the enums so you can request them all at once. Like follows:
UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
Debug output mentions I should ask for Application Badge permission
to modify the badge under ios8 you have to ask for permissions
let settings = UIUserNotificationSettings(forTypes: UIUserNotificationType.Badge, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
or in objC
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
Additional info for previous posts (in complete to registerUserNotificationSettings):
Apple makes new API for registering notifications and working with badges.
See WWDC 2014 session video , text version
and documentation.
User can change permissions for every UIUserNotificationType (UIUserNotificationTypeBadge, UIUserNotificationTypeSound, UIUserNotificationTypeAlert) in Settings.
Before changing badge you must check permissions.
Code sample from my AppDelegate:
#ifdef __IPHONE_8_0
- (BOOL)checkNotificationType:(UIUserNotificationType)type
{
UIUserNotificationSettings *currentSettings = [[UIApplication sharedApplication] currentUserNotificationSettings];
return (currentSettings.types & type);
}
#endif
- (void)setApplicationBadgeNumber:(NSInteger)badgeNumber
{
UIApplication *application = [UIApplication sharedApplication];
#ifdef __IPHONE_8_0
// compile with Xcode 6 or higher (iOS SDK >= 8.0)
if(SYSTEM_VERSION_LESS_THAN(#"8.0"))
{
application.applicationIconBadgeNumber = badgeNumber;
}
else
{
if ([self checkNotificationType:UIUserNotificationTypeBadge])
{
NSLog(#"badge number changed to %d", badgeNumber);
application.applicationIconBadgeNumber = badgeNumber;
}
else
NSLog(#"access denied for UIUserNotificationTypeBadge");
}
#else
// compile with Xcode 5 (iOS SDK < 8.0)
application.applicationIconBadgeNumber = badgeNumber;
#endif
}
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
The CurrentUserNotificationSettings method is available in the UI application instance and will give you the most up-to-date user notification preferences.
Working with badge number:
[self setApplicationBadgeNumber:0];
instead of
application.applicationIconBadgeNumber = 0;
PS: Checking at compiling (#ifdef __IPHONE_8_0) due to the need to build in Xcode5 and Xcode6.
If you do not have this need, the code can be simplified.
I write a class to handle it when I use swift:
class ZYUtility
{
/// Set badge
class func setApplicationBadgeNumber(badge: Int) {
if ZYUtility.SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO("8.0") {
if UIApplication.sharedApplication().currentUserNotificationSettings().types & UIUserNotificationType.Badge != nil {
UIApplication.sharedApplication().applicationIconBadgeNumber = badge
} else {
println("No permission to set badge number")
}
} else {
UIApplication.sharedApplication().applicationIconBadgeNumber = badge
}
}
/// System check
class func SYSTEM_VERSION_EQUAL_TO(version: String) -> Bool {
return UIDevice.currentDevice().systemVersion.compare(version,
options: NSStringCompareOptions.NumericSearch) == NSComparisonResult.OrderedSame
}
class func SYSTEM_VERSION_GREATER_THAN(version: String) -> Bool {
return UIDevice.currentDevice().systemVersion.compare(version,
options: NSStringCompareOptions.NumericSearch) == NSComparisonResult.OrderedDescending
}
class func SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(version: String) -> Bool {
return UIDevice.currentDevice().systemVersion.compare(version,
options: NSStringCompareOptions.NumericSearch) != NSComparisonResult.OrderedAscending
}
class func SYSTEM_VERSION_LESS_THAN(version: String) -> Bool {
return UIDevice.currentDevice().systemVersion.compare(version,
options: NSStringCompareOptions.NumericSearch) == NSComparisonResult.OrderedAscending
}
class func SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(version: String) -> Bool {
return UIDevice.currentDevice().systemVersion.compare(version,
options: NSStringCompareOptions.NumericSearch) != NSComparisonResult.OrderedDescending
}
}
update to 8.3, ObjC: we should add Daij-Djan script to replace NSLog(#"access denied for UIUserNotificationTypeBadge"); in Spidy & KepPM solution above. Hope this help s.o.

Is there some straightforward documentation on implementing the Game Center?

I'm trying to implement Game Center into my ios7 game (Xcode 5), but the material in the apple docs and the stuff I've seen online doesn't seem to work very well.
These are the two main methods I'm using wish produce no errors but I don't get any data either:
- (void) retrieveTopTenScores
{
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
if (leaderboardRequest != nil)
{
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeToday;
leaderboardRequest.identifier = kLeaderboardID;
leaderboardRequest.range = NSMakeRange(1,10);
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
// Handle the error.
}
if (scores != nil)
{
// Process the score information.
} else {
NSLog(#"scores retrieved successfully but no scores in the leaderboard");
}
}];
}
}
-(void)submitMyScore
{
//This is the same category id you set in your itunes connect GameCenter LeaderBoard
GKScore *myScoreValue = [[GKScore alloc] initWithLeaderboardIdentifier:kLeaderboardID];
myScoreValue.value = 5123123;
[myScoreValue reportScoreWithCompletionHandler:^(NSError *error){
if(error != nil){
NSLog(#"Score Submission Failed");
} else {
NSLog(#"Score Submitted");
}
}];
}
So I'm looking for some simple example code to do this successfully...
thanks
rich
I see nothing wrong with your code. Is the player authenticated when you run it?, what error are you getting? If you look for sample GameKit code there is some at iOS 6 Advanced Cookbook from Erica Sadun, but nothing you shouldn't be able to figure out reading the API.
the answer is this for submitting scores in iOS7 to the game centre
Game Center Helper/Manager/Control (Object).h
+ (gamecenterhelper/manager/control *)sharedInstance;
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier;
Game Center Helper/Manager/Control (Object).m
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier
{
GKScore *scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier: identifier];
scoreReporter.value = score;
scoreReporter.context = 0;
NSArray *scores = #[scoreReporter];
[GKScore reportScores:scores withCompletionHandler:^(NSError *error) {
}];
}
viewcontroller.h
#import "gamecenterhelper/manager/control"
viewcontroller.m
[[gamecenterhelper/manager/control sharedInstance] reportScore:(int64_t) forLeaderboardID:(NSString*)];
//in place of int64_t place your integer you want uploaded, and instead on NNString* add your leaderboard identifier

randomly displaying sprite image

Hi currently my game is loading fl_gfood.png or fl_bfood.png from the fl_food.plist. That works fine but now I have 17 bad food items .png and 17 good food items .png. My question is how do I randomly display one of the 17 items from each group? Can I just do a wildcard for the sprite png file name? See code comment below. Also a suggestion was made that I could possibly load the .plist file names into an array and randomly pick a name, how would that be done.
#implementation Food
+ (void)loadAssets {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[FLSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:[AssetHelper getDeviceSpecificFileNameFor:#"fl_food.plist"]];
});
}
- (id)init {
self = [super init];
if (self) {
self.size = CGSizeMake(16 * __HIGHRES_SCALE, 16 * __HIGHRES_SCALE);
self.offset = CGPointMake(8 * __HIGHRES_SCALE, 8 * __HIGHRES_SCALE);
self.removeAfterCollision = YES;
self.collideable = NO;
self.score = 10;
}
return self;
}
- (FLSprite *)sprite {
if(_sprite == nil) {
if(self.score < 10) {
_sprite = [FLSprite spriteWithSpriteFrameName:#"fl_gfood.png"]; // fl_gfood_*.png ... Can I do something like this?
} else {
_sprite = [FLSprite spriteWithSpriteFrameName:#"fl_bfood.png"];
}
}
return _sprite;
}
I would use arc4random() and stringByAppendingString
- (FLSprite *)sprite {
if(_sprite == nil) {
NSString *disFood;
disFood = (self.score < 10) ? #"fl_gfood" : #"fl_bfood";
//random # between 1 - 10
int randNum = (arc4random() % 10)+1;
NSString *formattedName = [NSString stringWithFormat:#"_%i.png",randNum];
disFood = [disFood stringByAppendingString:formattedName];
_sprite = [FLSprite spriteWithSpriteFrameName:disFood];
NSLog(disFood);//Logging disFood String;
}
return _sprite;
}

Resources