iOS WatchKit - how to determine if your code is running in watch extension or the app - ios

With WatchKit you have your app that runs on the phone, and the watch app that runs as an extension.
If you create a library that contains common code to be used in both the phone app and the watch extension, is there a way to tell if the code is running in the phone app or the watch extension?
I.e.
if ([self isRunningInWatchExtension]) {
NSLog(#"this is running on watch");
} else {
NSLog(#"this is running on phone app");
}
- (BOOL)isRunningInWatchExtension {
???
}

In target conditionals there are some conditionals that may help you,
#if TARGET_OS_WATCH
//do something for watch
#else
//do something for ios ==> assuming you only support two platforms
#endif

I've accomplished this by checking the bundle identifier:
if ([[[NSBundle mainBundle] bundleIdentifier] isEqualToString:kAppBundleIdentifier]) {
// Running in main app
}
else if ([[[NSBundle mainBundle] bundleIdentifier] isEqualToString:kWatchBundleIdentifier]) {
// Running in extension
}

This can be easy if you are calling any custom methods in your common framework class. You just need to add additional method parameters to method. And if you are calling this method from iOS app or Watchkit app then add appropriate key-value pair to dictionary for parameters. And compare this in your framework methods.
To determine this from init or any other method then you can still get to know by this code,
NSLog(#"%#",[NSThread callStackSymbols]);
So, you need to parse this string and get appropriate target names. If it is called by iOS app then you will get 'UIKit' string and from watch kit app extension you will get 'YourApp WatchKit Extension' string somewhere. You can also refer this SO answer for parsing this string and compare it - https://stackoverflow.com/a/9603733/602997

Related

Possible to bring the app from background to foreground?

When running an XCT UI test it is possible to put the application under test in the background with:
XCUIDevice().pressButton(XCUIDeviceButton.Home)
It it possible in some way to bring the app back to foreground (active state) without relaunching the application?
Update for Xcode 9: Starting in Xcode 9, you can now simply call activate() on any XCUIApplication.
let myApp = XCUIApplication()
myApp.activate() // bring to foreground
https://developer.apple.com/documentation/xctest/xcuiapplication/2873317-activate
Yes, it is. But, you'll need XCUIElement's private headers (which are available via header dump from Facebook here). In order to foreground the app, you need to call resolve which I believe resolves the element's query (which for applications means foregrounding the app).
For Swift, you'll have to import the XCUIElement.h into your bridging header. For Objective-C you'll just need to import XCUIElement.h.
With the app backgrounded:
Swift:
XCUIApplication().resolve()
Objective-C
[[XCUIApplication new] resolve];
If this is the only functionality you need, you could just write a quick ObjC category.
#interface XCUIElement (Tests)
- (void) resolve;
#end
If you need to launch / resolve another app. Facebook has an example of that here by going through the Springboard.
As of Xcode 8.3 and iOS 10.3, you can accomplish this with Siri:
XCUIDevice.shared().press(XCUIDeviceButton.home)
XCUIDevice.shared().siriService.activate(voiceRecognitionText: "Open {appName}")
Include #available(iOS 10.3, *) at the top of your test suite file and you should be good to go!
This is what I have in my XCUITest and it works like a charm (xcode 10.1 and test device is iPhone X 11.0)
func testWhatever() {
// You test steps go here until you need the background foreground to run
XCUIDevice.shared.press(XCUIDevice.Button.home) // To background the app
XCUIApplication().activate() // To bring the app back
// You test continues after background foreground has been done.
}
If somebody needs just move app back from background i have written (based on answer above) category that really works(great thanks to pointing to FB git)
#implementation XCUIApplication(SpringBoard)
+ (instancetype)springBoard
{
XCUIApplication * springboard = [[XCUIApplication alloc] performSelector:#selector(initPrivateWithPath:bundleID:)
withObject:nil
withObject:#"com.apple.springboard"];
[springboard performSelector:#selector(resolve) ];
return springboard;
}
- (void)tapApplicationWithIdentifier:(NSString *)identifier
{
XCUIElement *appElement = [[self descendantsMatchingType:XCUIElementTypeAny]
elementMatchingPredicate:[NSPredicate predicateWithFormat:#"identifier = %#", identifier]
];
[appElement tap];
}
#end
For Swift, you need to declare the XCUIApplication private methods interface in Bridging-Header.h like this:
#interface XCUIApplication (Private)
- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;
- (void)resolve;
#end
Then call resolve() in your test cases to bring the app back:
XCUIApplication().resolve()
Since Xcode 13 we got several errors that the app was not in foreground state after returning to the app.
applying this code to our "goToSpringboardAndBack()" works
XCUIDevice.shared.press(XCUIDevice.Button.home)
if XCUIApplication().wait(for: .runningBackground, timeout: 5.0) {
XCUIApplication().activate()
}
_ = XCUIApplication().wait(for: .runningForeground, timeout: 5.0)
ยดยดยด

ABAddressBook to CNContact App Transition

I am working on an app that is close to launch but uses the ABAddressBook framework. With the deprecation of ABAddressBook in iOS9, do I need to check the user's iOS version, and use ABAddressBook for pre iOS9 users, and CNContact for iOS9 users?
How is everyone else handling this? I haven't been in a situation like this before.
I have also been dealing-with and researching this issue, what I've opted to do is as you suggest; check the users iOS version doing something like the following:
NSString *version = [[UIDevice currentDevice] systemVersion];
BOOL isVersion8 = [version hasPrefix:#"8."];
BOOL isVersion7 = [version hasPrefix:#"7."];
//...
...continuing based on the versions you've decided to support for your app.
Then I do a check to either use the Addressbook framework for earlier than iOS 9, and Contacts framework for iOS 9 and beyond.
if(isVersion7 || isVersion8){
//Use AddressBook
}
else{
//Use Contacts
}
That's the best way I could think to deal with this deprecation business...
Deprecated doesn't mean removed. Just make linking to both frameworks as optional and start to design data workflow that can handle both frameworks. Also please mind that CNContact is new and full of bugs.
Once you think your app is refactored and iOS evolved to 9.1 give it a green light
How to know if system supports functionality
1) Check if the class exists
if(NSClassFromString(#"CNContact")) {
// Do something
}
For weakly linked classes, it is safe to message the class, directly. Notably, this works for frameworks that aren't explicitly linked as "Required". For missing classes, the expression evaluates to nil.
2)
#ifned NSFoundationVersionNumber_iOS_9
#def NSFoundationVersionNumber_iOS_9 NUMBER
#endif
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9) {
// Use address book
} else {
// Use contact framework
}
Run the app in simulator to find the NSFoundationVersionNumber constant
if #available(iOS 9, *)
{
// iOS 9 - CNContact
}
else
{
// iOS 8 - ABAddressBook
}
This is the right way to check.

Creating an app with deployment target of iOS 7.1 and optional iOS 8-only features

I'm creating an app that works with CloudKit framework (iOS 8-only) but still want to keep compatibility with iOS 7.1, with no CloudKit functionality, of course. Apple documentation recommends checking for optional classes like this:
if ([CKRecordID class]) {
// iOS 8 code
} else {
// iOS 7 code
}
This works. On the other hand, if I write
#interface Foo : NSObject
#property (nonatomic) CKRecordID *recordID;
#end
anywhere in the code, the app will crash on iOS 7 when loading the Foo class. How can I define properties with those optional classes?
You could use the forward declaration
#class CKRecordID;
but you will need runtime checks for the iOS version, such as
[[NSProcessInfo processInfo] operatingSystemVersion]
Other solutions for detecting the iOS version are shown here or here.
But how about two different builds for different iOS versions?
You can make your property recordId of type id or NSObject.
And when you need to access this property (after checking that your runtime is iOS8+), you cast it to CKRecordID class.

Determining if Facebook app is installed from Unity

We are using the Facebook SDK for Unity (v6.0) and I'd like to now whether there's a way that I can check if the Facebook app is installed on the device.
The reason is an existing bug in the Facebook SDK (see here: bug)
I want to identify this scenario (occurs only when the FB app is installed), and react accordingly.
"In order to use a native plugin you firstly need to write functions
in a C-based language to access whatever features you need and compile
them into a library. In Unity, you will also need to create a C#
script which calls functions in the native library."
from http://docs.unity3d.com/Manual/NativePlugins.html
So, basically you need to write your code in Objective-C and provide the communication between the Unity and the Native Code.
The code that you need to implement for checking Facebook APP is;
(void) checkFacebookApp
{
if ([[UIApplication sharedApplication] canOpenURL:[NSURLURLWithString:#"fb://"]])
{
return true;
}
}
However you need some communication between the Unity and Xcode project. So;
class SomeScript : MonoBehaviour {
#if UNITY_IPHONE || UNITY_XBOX360
// On iOS and Xbox 360 plugins are statically linked into
// the executable, so we have to use __Internal as the
// library name.
[DllImport ("__Internal")]
#else
// Other platforms load plugins dynamically, so pass the name
// of the plugin's dynamic library.
[DllImport ("PluginName")]
#endif
private static extern float checkFacebookApp ();
void Awake () {
// Calls the FooPluginFunction inside the plugin
// And prints 5 to the console
bool check = checkFacebookApp ();
}
}

How to intercept adding calls to call history in iOS?

I'm developing a tweak for jailbroken iPhones. I'm trying to intercept the process of a call being added to the call history. With a little bit search I found CTCallHistoryStoreAddCall function in CoreTelephony framework found here. When I try to use it I get an error:
Undefined symbols for architecture armv7: "_CTCallHistoryStoreAddCall"
I linked the CoreTelephony framework and the way I used it in my code was:
typedef struct __CTCall * CTCallRef;
extern "C" void CTCallHistoryStoreAddCall(CTCallRef call);
I guess that means this function does not exist anymore or if it does I'm not using it in the correct way.
How can I find the right function that is responsible for adding an incoming phone call to the call history?
Thanks in advanced.
I'm using iOSOpenDev on Xcode 5.
There is no such function. At least in iOS7.
I've posted solution for iOS7 here Hide a phone call completely in iOS (jailbreak device)
Here is the code:
//Private API from CoreTelephony.framework
void CTCallDeleteFromCallHistory(CTCallRef call);
%hook PHRecentCall
-(id)initWithCTCall:(CTCallRef)call
{
if (IsCallShouldBeDeleted(call) == YES)
{
//Delete call from call history
CTCallDeleteFromCallHistory(call);
//Update MobilePhone app UI
id PHRecentsViewController = [[[[[UIApplication sharedApplication] delegate] rootViewController] tabBarViewController] recentsViewController];
if ([PHRecentsViewController isViewLoaded])
{
[PHRecentsViewController resetCachedIndexes];
[PHRecentsViewController _reloadTableViewAndNavigationBar];
}
//Try uncommenting this, may be it will work. Should make the code faster.
//return nil;
}
return %orig;
}
%end
Tweak hooks class inside MobilePhone app so bundle filter is com.apple.mobilephone.
IsCallShouldBeDeleted is pseudo function that determines whether a call should be deleted. You can remove it or implement your own. It's there just to make the code more clear.
On iOS6 class names are different but code is exactly the same - Apple just renamed the classes. I use that solution since iOS4. Also on iOS4 it requires a bit more code as there was no CTCallDeleteFromCallHistory function.
You are encountering this error because the CoreTelephony framework is not being linked to your program. To fix this, add the following to your makefile:
PROJECT_NAME_PRIVATE_FRAMEWORKS = CoreTelephony
Note that you have to replace PROJECT_NAME with your own project's name.

Resources