I'm trying to add a Today Extension to a project I've been working on for quite some time. In fact the app is in the AppStore already and I'm looking to enhance it with a Today Extension.
The problem is that the Extension won't launch at all. Not on the device nor on the simulator.
EDIT: just skip the next sections and read on at the last EDIT as I think I found the problem. I just not sure how to fix it.
I've done a test project following a tutorial and it works just fine. The environment seem(!) to be identical. Xcode 6.1.1, iOS 8.1 on the device and simulator.
My project is Objective-C based. For the Extension I’ve tried both Objective-C and Swift targets. On both occasions all three (four with obj-C) files were created as expected (storyboard, viewController and PLIST).
Having done nothing more (as with the example project) I'm trying to launch the widget with the widget scheme selected. With the test projects the widget would launch while it won't with the actual project.
I put a println()/NSLog in the viewDidLoad of the widgets viewController to see if anything happens but nothing.
Happy to provide code or settings but at this pointing time I've no idea where to start.
I just realised that with the test project the today view would launch/appear automatically when the widget gets run from Xcode. With my actual project I'm just getting the HomeScreen and have to pull down the Today view myself. So, really nothing at all happens regarding the widget while everything looks identical compared to the test project.
Any help is appreciated.
EDIT: Here is something I came across which might constitute the problem. The widget never gets launched really and gets stuck at ´Waiting to Attach´ in Xcode's Debug navigator. While other seemed to have had the same problem all potential solutions I found so far did't work for me.
EDIT: I noticed that when I add a Today widget as a target the binary is named .app. All test projects I did the binary gets created as .appex. All the information on the web suggests that it should be named .appex really. Where does this come from and how do I alter this?
I had the same problem.
The following steps helped:
selected target Today Extortion -> Build Settings -> line Wrapper Extension add (change) value to appex
See:
Same problem happened today when I created a Notification Content extension in an old project.(2016, Xcode8 iOS10)
Finally I found the cause:
"Wrapper Extension" in Build Settings of the project was “app”, and when the new target of extension was created, "Wrapper Extension” inherited from the project settings as “app”.
Clearing the project setting before adding an extention target will make Xcode creat an extention as “appex” automatically.
I am herewith sharing the step and source code.
Step 1:- App extension must have a containing app - you can't just create an app extension to be downloaded from the store, first create a regular app to contain the app extension. For the sake of this demonstration just create a new single view project and leave it untouched. Go to File-> New-> Project and select Single view application under iOS -> Applications call it 'ExtendableApp'.
Step 2:- If you want to create your custom experience simply set your ExtensionViewController to inherit from UIViewController, Once your extension is activated all the regular viewDidLoad, viewDidAppear, etc will be called.
Step 3:- In your controller storyboard create outlets for button, I am herewith describing 3 buttons.
Step 4:- In ExtensionViewController.m write
- (void)viewDidLoad {
[super viewDidLoad];
self.preferredContentSize = CGSizeMake(self.view.frame.size.width, 60.0f);
// Do any additional setup after loading the view from its nib.
}
Step 5:- I am assuming that you have set the outlets and IB Action of your buttons in extension storyboard
- (IBAction) mActionButtonTapped :(UIButton *) sender {
switch (sender.tag) {
case 0: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_1://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
case 1: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_2://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
case 2: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_3://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
default:
break;
}
}
Step 6:- In your project write these code in appDelete.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
[self appExtensionCallBack:url.absoluteString];
return YES;
}
- (void) appExtensionCallBack :(NSString *)urlString {
if ([urlString isEqualToString:#"IDENTIFIER_1://"]) {
[self.tabBarController setSelectedIndex:0];
} else if ([urlString isEqualToString:#"IDENTIFIER_2://"]) {
[self.tabBarController setSelectedIndex:1];
} else if ([urlString isEqualToString:#"IDENTIFIER_3://"]) {
[self.tabBarController setSelectedIndex:2];
}
}
Note :- I am using Tab Bar Controller in my project, You can give own respected controller.
- (void) moveToControllerScene {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:STORY_BOARD_IDENTIFIER bundle:nil];
YOUR_CONTROLLER_OBJECT *obj = [storyboard instantiateViewControllerWithIdentifier:#"YOUR_CONTROLLER_OBJECT"];
[navController pushViewController:obj animated:YES];
}
Step 7:- For testing the Extension in real device you have to make a separate App ID and Provisioning profile. Delete appropriate provisioning profile in extension and ur project.
Related
Currently in React-Native, according to the documentation, to build your iOS app for production, you need to :
change your scheme to Release
change your AppDelegate.m to load the correct bundle
change your Info.pList for ATS
This is a strong violation of 12 factor config recommandation, and it leads to mistakes being made in a continuous integration process.
RN does not provide either out-of-the box strategies to know the configuration environment in the JS code, leading to the existence of the package react-native-config, which does a great job already, but is not perfect (Xcode is not fully supported).
Why is it so? Is it because there are actually so few RN app in production today that nobody cares about this? Can we do better than react-native-config so that steps listed above are not required? I would like a command line that archives my app in the same way that I can run cd android && ./gradlew assembleRelease, without changing anything to my config.
EDIT:
Fastlane makes deployment a lot easier through its gym command (thank you Daniel Basedow). Apparently, the philosophy of Xcode is to call environments "schemes", only you cannot store variables in them, or know which scheme you're running in your code... Anyway, David K. Hess found a great way to export your scheme name in your Info.plist, and then in your Objective C code, which means I'm now able to chose my bundle according to the current scheme, and not touch my code.
Here is my code:
NSString *schemeName = [[[NSBundle mainBundle] infoDictionary] valueForKey:#"SchemeName"];
if ([schemeName isEqualToString:#"scheme1"]) {
jsCodeLocation = [NSURL URLWithString:#"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
} else if ([schemeName isEqualToString:#"scheme2"]) {
jsCodeLocation = [NSURL URLWithString:#"http://<my_local_ip_address>:8081/index.ios.bundle?platform=ios&dev=true"];
} else if ([schemeName isEqualToString:#"scheme3"]) {
jsCodeLocation = [[NSBundle mainBundle] URLForResource:#"main" withExtension:#"jsbundle"];
}
Now my problem is : I also want to know which scheme I'm running in my JS code. react-native-config's way is self-described as hacky, and overly complicated considering the fact the information is already in my Objective C code. Is there a way to bridge this information to my JS code?
Only knowing which scheme I'm running is not as good as being able to set environment variables, but at least I'll be able to switch between environments only by changing my scheme.
EDIT 2:
I managed to export my scheme to my JS code. I created a cocoa touch class with the following code:
// RNScheme.h
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
#interface RNScheme : NSObject <RCTBridgeModule>
#end
// RNScheme.m
#import "RNScheme.h"
#interface RNScheme()
#end
#implementation RNScheme
{
}
RCT_EXPORT_MODULE()
- (NSDictionary *)constantsToExport
{
NSString *schemeName = [[[NSBundle mainBundle] infoDictionary] valueForKey:#"SchemeName"];
NSLog(#"%#", schemeName);
return #{
#"scheme_name": schemeName,
};
}
#end
and then in my JS code:
import {NativeModules} from 'react-native'
let scheme = NativeModules.RNScheme.scheme_name
EDIT 3:
There is actually another way than using schemes. You can create new "configurations" ("Release" and "Debug" are called configurations) with the following steps (thanks CodePush):
Open up your Xcode project and select your project in the Project
navigator window
Ensure the project node is selected, as opposed to one of your
targets
Select the Info tab
Click the + button within the Configurations section and select
which configuration you want to duplicate
Then you can define keys with different values according to your configuration.
Select your app target
Chose Build Settings
Go to User-Defined section (at the bottom of the scroll area)
You can define constants with a different value according to your configuration (for instance API_ENDPOINT)
You can then reference this value in your Info.plist file :
Open your Info.plist file
Create a new value and give it a name (ApiEndpoint)
Give it the value $(API_ENDPOINT) or whatever name you gave to your constant
Now you can reference this value in your code using the code I gave you in my second edit to this question.
You can create one scheme per configuration to switch quickly from one to the other, or change the build configuration each time (option click on the run button).
In your AppDelegate you can use the correct bundle like this
#ifdef DEBUG
jsCodeLocation = [NSURL URLWithString:#"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
#else
jsCodeLocation = [[NSBundle mainBundle] URLForResource:#"main" withExtension:#"jsbundle"];
#endif
When you do a release build, the DEBUG flag is not set. You can also use different files as your Info.plist depending on build type. There will probably be situations where you want an Xcode debug build with a production JS bundle or vice versa. In that case you need to touch code.
Building ios apps from command line can be a bit tricky. The problems you're describing are not specific to react-native but the Xcode build system. If you haven't already, check out fastlane especially the gym command. It is much simpler than using xcodebuild directly.
But you still have to define your schemes.
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)
´´´
I am a new iOS developer. I have to develop a static library. I have number of viewcontrollers and the flows between them are defined using a story board. I have created a two targets from my project. One is a static library and the other is a bundle. I use my bundle to include my story board in it. I have used this code to launch my first viewcontroller from the storybaord.
-(void)showFromViewController:(UIViewController *)vc{
NSBundle * bundle = [NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:#"storyBoards" withExtension:#"bundle"]];
UIStoryboard * libStoryBoard = [UIStoryboard storyboardWithName:#"Main1" bundle:bundle];
ViewController1 * viewController = [libStoryBoard instantiateViewControllerWithIdentifier:#"ViewController1"];
[vc presentViewController:viewController animated:YES completion:NULL];
}
vc is a ViewController that calls this function.
And yes ViewController has target membership with the static library.
However after including the '.a' and '.bundle' in another project and calling the above function it throws an error in the console Log.
Unknown class ViewController1 in Interface Builder file.
Can anyone suggest meright way to use a story board along with a static library. And if Yes then can you point out the right way to launch the first viewcontroller from the storyboard who's class is included in the static library.
simply you can navigate like this
LoginViewController *loginVC=[self.storyboard instantiateViewControllerWithIdentifier:#"loginView"];
[self presentViewController:loginVC animated:YES completion:nil];
I have a similar setup. I have my main project, and I have a ModelController sub project which has a static library and a resources bundle that are used in my main project.
I created a storyboard and a test view controller and used your method above to load.
The only things that might catch you out are in your static library > target > build phases > copy headers > ensure you add your ViewController1.h file into this list.
Other than that, everything worked fine for me.
One possible cause here is that the view controller class (implemented in the static library) is stripped out by the linked, and therefore unavailable to the storyboard loader at runtime. I ran into this problem, and it went away when I made sure that the class in question was referenced from the code that was using the storyboard.
In my case that was easy because I needed to reference the class anyway (I just hadn't written that code yet), but I verified that [ViewController class] was enough to prevent the linker from erroneously stripping the class out.
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
I integrate Aviary SDK on my app to enhance my app image editor feature. I read its documents, run its sample code and it works fine. But when run on my app, I face an issue. It crashed EXC_BAD_ACCESS after run over a method
[AFOpenGLManager beginOpenGLLoad];
I followed the setup guide on Aviary document
https://developers.aviary.com/docs/ios/setup-guide#project-setup
At first, I just create a Singleton manager to manage. I call [AFOpenGLManager beginOpenGLLoad]; on init function
- (id)init {
if (self = [super init]) {
[AFOpenGLManager beginOpenGLLoad];
}
return self;
}
- (void) launchPhotoEditorWithImage:(UIImage *)editingResImage
highResolutionImage:(UIImage *)highResImage
fromController:(UIViewController *)controller
{
// Customize the editor's apperance. The customization options really only need to be set
once in this case since they are never changing, so we used dispatch once here.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self setPhotoEditorCustomizationOptions];
});
// Initialize the photo editor and set its delegate
AFPhotoEditorController * photoEditor = [[[AFPhotoEditorController alloc]
initWithImage:editingResImage] autorelease];
[photoEditor setDelegate:self];
// If a high res image is passed, create the high res context with the image and the
photo editor.
if (highResImage) {
[self setupHighResContextForPhotoEditor:photoEditor withImage:highResImage];
}
// Present the photo editor.
[controller presentViewController:photoEditor animated:YES completion:nil];
}
After run over the init function, it crashed on
Do I miss somethings, the sample code run well.
Edit 1:
compileShader is called from createProgram but I can read this method
Edit 2:
I realize somethings. My app project has a lib named libmediastreamer_voip.a . I think there is misunderstanding. I mean maybe Aviary lib and libmediastreamer_voip.a lib also have the function named compileShader. So when on Aviary lib calls compileShader it runs on compileShader on Aviary lib but run into compileShader on libmediastreamer_voip.a.
I wonder I could be like that? I create a new project and integrate Avairy SDK, it works well, just integrate to my app it crashes
I am a member of the iOS team at Aviary. This is caused by a conflict between our compileShader function and yours. Our function was not properly namespaced and resulted in the conflict. We will be addressing this in the next release of the SDK.
Michael
What I think about it. Check your shader value. It should have correct shader path from your resources or somewhere else with appropriate type (GL_VERTEX_SHADER or GL_FRAGMENT_SHADER).
Seems to me you've it's nil