I am trying to build share extension. Created the target, fixed the project settings (provisioning profile, app group, NSExtension).
Tried to run and saw the logs I added to the three methods isContentValid, didSelectPost, and configurationItems. This is my first time to create a share extension so, I was just looking around first and see when each method is called.
Now for some reason (I don't know what I did wrong), I am unable to see the logs from the extension. When I hit the run button in Xcode, the message Finished running ... gets displayed in the status bar on top.
Tried to change the scheme settings to the extension in the executable field, I get the following message when trying to run:
Could not locate installed application
Install claimed to have succeeded, but application could not be found on device. bundleId = <my-bundle-id-value>
Followed every solution I could find on the internet. Tried the solution from here and here.
UPDATE
I wanted to check if the extension actually runs or just the logs don't work. So I added the following logic to isContentValid:
- (BOOL)isContentValid {
// Do validation of contentText and/or NSExtensionContext attachments here
NSLog(#"isContentValid");
NSUserDefaults * userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"<group-id>"];
[userDefaults setObject:#"haha" forKey:#"key"];
[userDefaults synchronize];
return YES;
}
and read then read the haha in the main application it worked! So, the question is: Why can't I debug the application? Why doesn't the application print the log statements. Developing without logging and/or debugging could take 2x, if not more, time.
UPDATE 2
The best I was able to come up with is to manually attach the debugger to the extension, add breakpoints here and there (where I wanted to put logs) and edited the breakpoints to log as action and continue after evaluation.
Related
I've seen several other posts on SO and elsewhere about this error (System.InvalidOperationException: You MUST call Xamarin.Forms.Init(); prior to using it. at Xamarin.Forms.Device.get_PlatformServices), but none of them shed any light on my particular situation.
I've created a Xamarin.Forms application that works great on Android (emulators and device, including when distributed through Google Play), and works fine both on the iPhone simulator and when provisioned to the iPhone. And when I say 'works fine', I mean 'is able to get to the MainPage without error.'
However, when I use Ad-Hoc deployment and upload it to Apple so it can be distributed with TestFlight, the TestFlight-downloaded version shows the splash screen as expected, and then crashes without ever showing the MainPage. In looking at the device logs, the above error appears, and in doing some Internet research, it's become apparent that my AppDelegate needs to have global::Xamarin.Forms.Forms.Init(); as the first line in the FinishedLaunching() method.
So I went to take a look and put that line in that method in that file, and lo and behold, it's already there. This is not unexpected, given the fact that the app works on the sim, and on the iPhone when deployed directly from Visual Studio.
So my question is 'Why does Ad-Hoc deployment fail when all other deployment types do not, and what can I do to overcome this problem and get the app to actually distribute through TestFlight in a runnable fashion?'
I was able to resolve the issue by putting the command (global::Xamarin.Forms.Forms.Init();) first thing in every constructor in every class in the iOS platform-specific services. It's possible that only one was really necessary, but it doesn't seem to have caused any issue to over-deploy that particular command, and in the case that more than one of the services is called before the AppDelegate's FinishedLaunching() method is called, that would seem to be necessary in the first one called, which could potentially vary by program flow, depending on environment and other variables that may be checked.
In .iOS project make sure the line
"global::Xamarin.Forms.Forms.Init();"
is the first line in the event
"public override bool FinishedLaunching(UIApplication app, NSDictionary options)"
in
"AppDelegate.cs" file.
In .UWP VB project make sure the line
"Xamarin.Forms.Forms.Init(e)"
is after the line
"AddHandler rootFrame.NavigationFailed, AddressOf OnNavigationFailed" in
"Protected Overrides Sub OnLaunched(e As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs)" event
in "App.Xaml.vb" file.
In .Android project
"global::Xamarin.Forms.Forms.Init(this, savedInstanceState);"
is after the line
"base.OnCreate(savedInstanceState);"
in "protected override void OnCreate(Bundle savedInstanceState)" event.
Hope this helps.
I have an application that implements Today extension. Till iOS10 it was working fine, but now I see that it can't read keys stored in shared NSuserDefaults.
My Host app code:
NSUserDefaults *sharedPrefs = [[NSUserDefaults alloc] initWithSuiteName:kSharedGroupIdentifier];
[sharedPrefs setObject:username forKey:#"username"];
[sharedPrefs setObject:#"Test" forKey:#"test"];
[sharedPrefs synchronize];
My extension code:
NSUserDefaults *sharedPrefs = [[NSUserDefaults alloc] initWithSuiteName:kSharedGroupIdentifier];
NSString *str = [sharedPrefs valueForKey:#"test"];
And str var is always nil in extension. I have set up my App Groups in Capabilities for both Host app and extension app, and I have checked it against the settings on Apple developer portal. It is all OK. Can anyone please help, is there something different that I must do?
EDIT
Another thing that I have noticed is that I have a red exclamation mark on: "Add the Add Groups entitlement to your entitlement file", and when I fix the issue, the exclamation mark goes away, but when I close and reopen project it is there again. I have recreated provisional profiles, but still the same issue
It sounds like the issues is that your Entitlements file is missing the relevant line.
To diagnose where the issue is, start with your build settings:
Under Code Signing Entitlements there should be a file listed
Open that file and ensure that it has the App Groups key with the value that matches your kSharedGroupIdentifier
Ensure that the file is not marked read-only (this would potentially cause the "fix" to not work correctly)
Verify that all of the following match:
kSharedGroupIdentifier in both apps
kSharedGroupIdentifier and the value in your entitlements file for App Groups
Next, we can narrow the issue down to the specific key or the entire preferences domain as follows:
NSUserDefaults *sharedPrefs = [[NSUserDefaults alloc] initWithSuiteName:kSharedGroupIdentifier];
if (sharedPrefs == nil) {
NSLog(#"Error loading shared user defaults!");
}
A nil value indicates that the suite name is invalid.
Rebooting the mac solved the issue for me.
Apple RNotes WWDC Seed: Running multiple iOS simulators can cause NSUserDefaults to not work Running an iOS 8 or 9 simulator followed by an iOS 10 simulator will cause NSUserDefaults to stop working in the simulator. This can be worked around by rebooting the host Mac.
I hope it will helps you,
Thanks.
I've updated my Xcode 8 to beta 2 today and I'm trying to share data between App and Today Extension. I'm facing with this log warning:
2016-07-08 18:00:24.732472 ProjetctX[941:42801] [User Defaults] Failed
to read values in CFPrefsPlistSource<0x1700f1280> (Domain:
group.x.p.t.o, User: kCFPreferencesAnyUser, ByHost: Yes, Container:
(null)): Using kCFPreferencesAnyUser with a container is only allowed
for System Containers, detaching from cfprefsd
Anyone can help me?
This is actually a spurious warning that was introduced in iOS 10 and macOS 10.12:
NSUserDefaults tip: in the current OSs there's a logged error "…with a container is only allowed for System Containers…".
This is spurious.
Trying to catch a particular failure mode, caught a normal operation case at the same time.
My successor on UserDefaults also has not figured out a way to make this less alarming without making the symptomatic case impossible to debug :/
https://twitter.com/Catfish_Man/status/784460565972332544 [thread]
The advice of prepending your team ID will silence the warning, but will also create a new empty user defaults. This will result in any previously stored data being unreadable.
For the time being, the solution is just to ignore it.
Also, Apple staff member CFM on the forums:
The logged message is spurious unless you're doing very specific things that I don't think are possible without using private functions (it was added to catch misuse of those functions, but unfortunately also caught a normal usage case).
Here’s how to use UserDefaults with App Groups to pass data between your main app and your extension:
In your main app, select your project in the Project Navigator.
Select your main app target and choose the Capabilities tab.
Toggle the App Groups switch to ON. This will communicate with the
Developer Portal in order to generate a set of entitlements.
Create a new container. According to Apple, your container ID must
start with "group", so a name such as "group.io.intrepid.myapp" is
perfect.
Select your extension target and repeat the process of enabling App
Groups. Do not create a new App Group, simply select the group that
was just created in the main app target.
When reading or writing UserDefaults in either your app or your
extension, do not access UserDefaults.standard.
Instead use UserDefaults(suiteName: "group.io.intrepid.myapp").
Note: The suite name is the name of your App Group container created
in Step 4.
Make sure, group enable and using same group id for both extension and app capability section!
Credit goes to http://blog.intrepid.io/ios-app-extensions
Change you group name in Xcode entitlements from:
group.com.mycompany.myapp
To
group.MYTEAMID.com.mycompany.myapp
ps: you can find your MYTEAMID in developer.apple.com membership
The solution for me was to not use the same identifier for the application Bundle Identifier and the part after "group.".
Say, the app bundle id is "com.app.id", then group id as "group.com.app.id" is causing issues. After I change it to "group.com.app.id.something" it stops.
Also had same issue with my macOS app.
Solved it by: Reboot the device!
https://stackoverflow.com/a/39876271
The SuiteName (postfix) must not be the main Bundle ID.
I’m facing this same issue when I’m trying to use initWithSuiteName. Seems that this is a bug from Apple.
The only solution / workaround I found is to reset all the settings of the device.
Go to Settings -> General -> Reset -> Reset All Settings.
This doesn’t erase any content on the iPhone, just erases all the settings.
After resetting the setting, everything worked fine. Let me know if it helps you too.
Build with Xcode 8.1 Beta and you will see the same warning, but you will also get the value.
By default, if you are using the Settings.Bundle/Root.plist for displaying and editing your app preferences via Apple Settings App, it uses the UserDefaults.standard dictionary.
So if you are using App-Groups and you want to share this defaults / settings within your apps and extension, you need to change the container of your settings.
Step 1: Open your Settings.Bundle -> Root.plist
Step2: Add the key ApplicationGroupContainerIdentifier and as value set your App-Group-Id, defined in your Signing & Capabilities: Looks like group.xx.yy
After you have implemented this step, the default container for your App-Settings will now switch from UserDefaults.standard (your apps Path) to the Shared Path.
Set by example
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.xxx.xxx"];
[userDefaults setValue:#"value" forKey:#"key"]
[userDefaults synchronize]; // return maybe false, but it doesn't matter
Get by
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
[userDefaults addSuiteNamed:#"group.com.xxx.xxx"];
NSString *value = [useDefaults valueForKey:#"key"];
Although the same error will still be printed when setting, the value is indeed set and can be read correctly. But I don't know why this is happening, it's just the result of various attempts.
if you suffer this problem when you try to save data to extension APP by using userDefault, maybe you had written this code:
[[NSUserDefaults standardUserDefaults] initWithSuiteName:#"group.xxx.com"];
This code reset default userDefault.
Actually,the correct code is:
[[NSUserDefaults alloc] initWithSuiteName:#"group.xxx.com"];
http://www.jianshu.com/p/e782104c3bc3
Change from
[[NSUserDefaults alloc] initWithSuiteName:#"group.com.xxx.xxx"];
to
[[NSUserDefaults alloc] initWithSuiteName:#"nnnnnnnnnn.group.com.xxx.xxx"];
Where nnnnnnnnn is your team number, the one which you use to sign your code.
Tested under Xcode 8 GM and iOS 10 GM, and worked!
Is there any way to know that iOS app is launched after being updated?
I think i can save app current version every time i launch the application for example in NSUserDefaults and check this version every time i open the application.
And what about the case:
1) User installs app version 1.0 , but doesn't launch it.
2) User installs app version 2.0.
How to handle that case for example?
Thanks in advance.
If always done what you have suggested, saving the app version in the NSUserDefaults.
And about your other case, if the app does not start with version 1 then it does with version 2 you could just see it as a new install.
Since your app never started in the first place you can just treat it as a fresh install. If you doing this to track update in some kind of analytics tool you will have an issue. But you could use apple install/update reports to get the correct list of install/updates.
Just be sure that if you do any updates from any version you make you code in such a way that you can upgrade from any previous version. So installing verion 4 from 1 will preform any and all changes for version 2 and 3 as well.
I found the following note at this website from Apple.
When a user downloads an app update, iTunes installs the update in a new app directory. It then moves the user’s data files from the old installation over to the new app directory before deleting the old installation. Files in the following directories are guaranteed to be preserved during the update process:
/Documents
/Library
Although files in other user directories may also be moved over, you should not rely on them being present after an update.
In every version you release, you can put a txt file with a unique name (unique for every version) in one of these update-persistent directories and check for the previous version txt file(s) at initial launch of application. This should work even in the case where your application was not launched between the download and an initial update.
Every time your application is launched, the following function in your appDelegate class gets called after the launching process is complete:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
This is a point where you can check the version of the application, probably using somoething like:
[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"]
I'm a bit late to the party, but if this is still an issue, I use a saved Boolean to see if this is the app's first launch:
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedOnce"]) {
NSLog(#"First launch");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:#"HasLaunchedOnce"];
[defaults synchronize];
}
I can then deal with an install or update as you already mention in your question.
I think this could be useful: MTMigration manages blocks of code that need to run once on version updates in iOS apps. This could be anything from data normalization routines, "What's New In This Version" screens, or bug fixes.
It is available through CocoaPods: pod 'MTMigration'
Please take a look to MTMigration repository at GitHub (https://github.com/mysterioustrousers/MTMigration) for usage and examples.
[MTMigration applicationUpdateBlock:^{
/* This code run on every version change. */
}];
[MTMigration migrateToVersion:#"1.0" block:^{
/* This code only run once in version 1.0 */
}];
[MTMigration migrateToVersion:#"2.0" block:^{
/* This code only run once in version 2.0 */
}];
If a user was at version 1.0 , skipped 2.0 , and upgraded to 3.0 , then both the 1.0 and 2.0 blocks would run.
I have code that is triggered on launch only after the app is upgraded or installed. I want to debug this code, but can't think of a way to do it.
I can't launch the app with Xcode because it would overwrite the existing app and wouldn't trigger the update.
I can't attach Xcode to the process manually because the code I want to debug will have executed by the time I get Xcode attached.
Is there someway of pulling this off that I'm missing?
Use the "Wait for MyApp.app to launch" option.
In Xcode, hold down the Option key and choose Product->Run... (the ellipses are added when you hold Option down). This will bring up the scheme editor, with the Run scheme selected. Click on the Info tab, and on the resulting Info panel you'll see a radio group labelled "Launch" with two options: "Automatically" and "Wait for MyApp.app to launch". If you select the second option, Xcode will start the debugger and wait for you to launch your application manually. You can set a breakpoint at the beginning of the code you want to debug, and the debugger will stop there.
Reset the app to its previous state.
You may want to consider adding some code that resets the app to whatever state it was in before the upgrade. This can be debug code that's excluded from the release build. Debugging problems that require you to re-install the app every time you want to run through your debug can take a lot of time. Even just having to restart the app at every debug cycle eats up a lot of your time. It's very often worthwhile to spend a little extra time adding a button to your app that undoes whatever changes you're making so that you can do them again.
Unit test.
One great way to debug code that deals with app states that are difficult to recreate is to write unit tests. In a test, you can create any state you want. You can even create app states that may be impossible to recreate in your app. And you can debug that code over and over again. Like the previous solution, it takes a little more time to write the code up front, but you'll save so much time on each iteration of your debug cycle that its well worth it. Even better, you can add the test to your build process so to help ensure that the functionality doesn't break later.
I also have some code that I only run on upgrades, i have this method below which I use at the end of applicationDidFinishLaunching. I can debug this too:
- (void) determineWhetherToForceSync {
NSString *currentVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
NSString *savedVersionKey = #"savedVersionKey";
NSString *savedVersion = [[NSUserDefaults standardUserDefaults] valueForKey:savedVersionKey];
if (!savedVersion) {
//This is a fresh install....
} else {
//They have a saved version, so check if it's the same as current version
if (![currentVersion isEqualToString:savedVersion]) { //They have done an upgrade
//This is an update
} //.... else they are starting the app on a second attempt
}
//Save the current version
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:savedVersionKey];
}
To re-test, delete the app and re-install. Or change your version number.