I've set up my app's user defaults to contain two toggle switches - one for help, one for sound. They are working however the starting values are false even though I've set them to be YES:
I use BOOL variables to track these values in the app, so I'd like to store YES/NO values. What else do I need to do to get the correct starting values?
I found the answer to this. Apparently there is an Application Domain AND a Registration Domain that aren't in sync the first time the app is run. The solution that worked for me is here:
http://excitabyte.wordpress.com/2009/08/12/keeping-user-defaults-synchronized-with-settings-bundle/
Not good when you want to have help and sound enabled the first time your app is run! Come on Apple.
Uhm... Aren't toggle switches setup this way:
http://screencast.com/t/bGCGIZara
Related
Question
How to use these private functions on other windows? It would be nice to have this knowledge back in the wild. I am specifically trying to get CGSOrderWindow and CGSSetWindowLevel to work.
I was trying in the direction of:
temporarily register as the dock and then register the dock as the dock again immediately afterwards
or
code injection into the Dock process per this comment:
Also, the author of the above project seems determined to make all core functionality available as a framework. It seems to be implemented as code injection into the Dock process.
Reason I know this is possible
I have been doing work on trying to setLevel on window of another app, and focus window of another app if focused. I am posting this again with the info I learned because from my searching online, I know this was done in the past, its just the knowledge is not publicly out there anymore. The sourceforge pages are no longer there. So I was wondering if you could help me make this information public again.
This is the topic I read that gave me this information - http://cocoadev.com/HowtoControlOtherAppsWindows
Here you see comments like:
You cannot control an another app's windows from a user-level process, unfortunately.
SlavaKarpenko
You can, Slava, you just need to register as the Dock. It might be possible to temporarily register as the dock and then register the dock as the dock again immediately afterwards, not sure. I think the call you'd be wanting to investigate as CoreDockRegisterDockOwner in HIServices.framework.
FinlayDobbie
You could also use APE or similar to do control the windows, or (as mentioned above) register as the Dock (look for the private APIs with Universal Connection in their name). Has anyone found a polite way of getting the Dock to give up its universal connection? The only way I can find is to force quit the Dock and grab the universal connection when it's not looking (which prevents the dock reloading).
SamTaylor
There's an open source project up on sourceforge.net that looks much more like the window managers I've used on Unix boxes than Space.app (or Space.dock): http://wsmanager.sourceforge.net/
SteveCook
Verifying things work
This is what I learned, from the sources at bottom of this post, we see all these functions work with CGWindowIds, so how do I get that, this is how:
Get all windows with CGWindowListCopyWindowInfo. Then access each element from that array with CFArrayGetValueAtIndex and then get the CGWindowId with objectForKey:, kCGWindowNumber, and then integerValue.
Now if I try to focus or set level of a window that is OWNED by the app running the code, it works fantastic. For instance:
MY_TARGET_CGWINDOW_ID = 179;
rez_CGError = CGSOrderWindow(_CGSDefaultConnection, MY_TARGET_CGWINDOW_ID, kCGSOrderAbove, 0);
Will focus it, rez_CGError is 0. Even if the window is minimized, it is unminimized, without animation, and shown.
Now however, if I try this on a window of a different app we get some errors:
MY_TARGET_CGWINDOW_ID_of_other_app = 40;
rez_CGError = CGSOrderWindow(_CGSDefaultConnection, MY_TARGET_CGWINDOW_ID_of_other_app, kCGSOrderAbove, 0);
This fails and rez_CGError is 1000, which I suspect means "cid (CGSConnection) used does not have permission to modify target window". The same happens if I first do [app activateWithOptions: (NSApplicationActivateIgnoringOtherApps | NSApplicationActivateAllWindows)] before making the call above.
So I first get the cid of that owning window like this:
var rez_CGError = CGSGetWindowOwner(_CGSDefaultConnection, MY_TARGET_CGWINDOW_ID_of_other_app, &ownerCid);
This works good and I get ownerCid is set to a value. Then I do the focus command with this new connection:
rez_CGError = CGSOrderWindow(ownerCid, MY_TARGET_CGWINDOW_ID_of_other_app, kCGSOrderAbove, 0);
However this gives rez_CGError of 268435459, which I suspect means "current app does not have permission to use this ConnectionId (cid)". (Same happens if I call activateWithOptions first.
My Sources for the Private Functions
Here is the sources for some private functions I found - https://code.google.com/p/undocumented-goodness/source/browse/trunk/CoreGraphics/CGSPrivate.h
This one source here contains a function that is not in the above link - CGSGetConnectionIDForPSN - i test it and it exists - from - https://github.com/mnutt/libqxt/blob/767498816dfa1742a6f3aee787281745afec11b8/src/gui/qxtwindowsystem_mac.h#L80
From time to time, but not always (I have had this working for a bit), the app/extension gets in a state where I can't read a flag set in my App Group between my companion app and my app extension. Don't know how it gets in this state or why the values differ, but it's critical to my application these always be in sync.
Companion app viewDidLoad:
NSUserDefaults *myAppSettings = [[NSUserDefaults alloc] initWithSuiteName:#"group.myapp"];
.....
[myAppSettings setBool:true forKey:#"myBool"];
[myAppSettings synchronize];
NSLog([myAppSettings boolForKey:#"myBool"] ? #"Companion app - bool TRUE" : #"Companion app - bool FALSE");
App extension viewDidLoad
NSUserDefaults *myAppSettings = [[NSUserDefaults alloc] initWithSuiteName:#"group.myapp"];
[myAppSettings synchronize];
NSLog([myAppSettings boolForKey:#"myBool"] ? #"App extension app - bool TRUE" : #"App extension - bool FALSE");
Console output
Companion app - bool TRUE
App extension - bool FALSE
I also synchronize before my companion app will enter background. I have my app group set up in the portal etc.
What am I doing wrong?
EDIT
Apparently others having this problem too:
https://devforums.apple.com/message/977151#977151
"I think that this is currently very glitchy.
Sometimes the data sharing works, then a change and all of a sudden the widget can't see the shared data anymore (both on Simulator and device).
Annoying and hope it's a bit more reliable in next beta!"
EDIT 2
Looks like another person has reported this exact issue as well:
"I also noticed the same thing too.This not only happen to the
NSUserDefaults, but also all the files in the container folder. The
keyboard extension suddenly will lose read/write pemission to the
container folder after using the keyboard for a while."
EDIT 3
More evidence: https://devforums.apple.com/message/1028078#1028078
After I upgrade to beta 3, I noticed that sometimes the keyboard
failed to open the database because it failed to access to the DB
file. The keyboard has been able to access to the file before.
EDIT 4
Seems like this could be because the keyboard loses the RequestsOpenAccess flag. But I can't reproduce it, and there's no way for me to tell for sure.
EDIT 5
Seems like others are reporting this in the iOS8 GM build:
This issue still persists for me in the GM. It seems related to a
keyboard crash.. but also there seems to be some contention between
keyboard and containing app in terms of who creates the suite in what
order. I think this problem is on Apple's end. Trust me, I WANT it to
be my fault but I've spent countless hours with trial and error. No
matter what I do in code and verify with NSLog, it will end up in this
state eventually. Hoping someone finds a magic pill. :S
Has anyone solved this yet?
You must request open access in order to access shared NSUserDefaults. It's stated directly in the App Extension Programming guide:
By default, a keyboard has no network access and cannot share a container with its containing app. To enable these things, set the value of the RequestsOpenAccess Boolean key in the Info.plist file to YES.
Be sure you change the RequestsOpenAccess field to YES. You'll find it in keyboard's Info.plist > NSExtension > NSExtensionAttributes > RequestOpenAccess. Then remove the keyboard in Settings, delete the app, run it again, and add the keyboard again. After you add it, tap on the keyboard name and then flip the switch to enable Allow Full Access. You'll need to instruct the users to follow those same steps to grant access (and reassure them you're not evil), otherwise it simply will not work and you'll never get the data that's stored in your shared container. Note that in iOS 8.3+, if the user hasn't enabled full access the keyboard will be able to access the shared container, but writing to it will not save the data, for security and privacy purposes. In 8.2- you can't access that data without open access granted.
I can confirm that the problem is related to RequestsOpenAccess flag.
Assuming that everything done right (NSUserDefaults use initWithSuiteName, all Capabilities for main application and custom keyboard were set, etc.) I have the next steps:
1) Install the main application and a custom keyboard on device
2) Set 'Allow full access' for the custom keyboard to YES
3) Add some items (in my case this is a simple text templates) in the main app
4) Go to keyboard and check that all items, that were added from the main app,
appeared in custom keyboard
5) Go to main app and add a few more items
6) Go to keyboard and now you will see that nothing changed
7) Go to settings and switch 'Allow full access' to NO and then to YES
8) Go to custom keyboard again and check that item which were added in step 5 appeared.
The strengthbar in my game doesn't work well after playing at least once of the level. So I wrote a method using CCLog to log out some variables and call this method in update: method. And I also set a breakpoint, when this problem appears it gets into debug mode of Xcode, then I use print to check the variables. However it appears that the values are different between using CCLog and using print command. Here's a screenshot that explains everything.
Here is my screenshot:
Does it have something to do with Multithreads(which I know nothing about)? I've checked the declaration of each logged variables, to make sure they're not declared several times using the same name.
OK, problem is solved.
When I wrote some delegate I used "Strong" which causes some retain cycles. Now I changed them all to Weak, and also changed the deployment settings to iOS 5 (or above).
The reason that the values of the variable are different is because in CCLog, the value belongs to the new scene, however there're more than one scene at the same time receiving my touches because of the retain cycles. Nothing wrong about XCode or Cocos2d, or CCBReader :-p
Hope this answer would help others. And many thanks to #Cy-4AH.
Because it was changed after it was printed. In the breakpoint you are lookng at new value.
I have an app that logs the amount of miles you travel, and it presents the data in a UITableView. A helpful thing I thought however, was to add since when this data was formed. Which would be the first time the app has opened right. So, how could I retrieve a date since the first time the app has opened. I thought of an approach that in the App Delegate, I get a string from a file. And if the string then is nil, it means the app has first been opened, then I add a simple NSDate. From there I copy a string into a file, so next time the app has opened the string will be valid and the app recognizes it has not been the first time the app has opened. Unfortunately this did not work.
Are there better solutions?
Use NSUserDefaults for this purpose. On the launch check if #"firstLaunchDate" key is missing. If it is - this is the first launch of your app and you can store the launch date. Other times application will be launched - the condition will evaluate to false and won't overwrite previously set value.
How can I detect if the user has just downloaded the application and opened it for the first time? IS this a NSUserDefaults? I want to be able to show a welcome screen only the first time my application is run.
Thanks
check for a bool in NSUserDefaults and if it is not set do whatever you want and save a YES-bool back to NSUserDefaults. If you show an alert you probably should put the setBool:forKey: in the delegate method which is called after you have dismissed the alert.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"wasLaunchedBefore"]) {
NSLog(#"First launch");
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"wasLaunchedBefore"];
}
You can use the NSUSerDefaults.By this when the user opened app check whether there is any values in your user defaults for a key.If it is not then that is first time.After this check you have to update the value for the key which you have checked previously.
You can check if flag is set in NSPreferences or check if file exists in app's file (file that you create after the first launch).
You need to set in your application if opened first time then show that stuff which you want to show first time. There is no any other way to find out that user downloaded your application and he run it or not. On run process you need to set it inside your application.
If you use NSUserDefaults then user can reinstall your application. And application will think thank user use it for first time again. But after updating application remembers that the user has already launched it.
I can't understand from your question if it is appropriate for you but the most of applications work this way