Swift: Hide a View Controller after first time use - ios

In swift I am making a app where it asks for a code before going to the actual app. I only want this to be showed once. How do I have this page only showed the first time then they enter the code and they only have to do this once then when they use the app again they don't need to enter a code. Thanks.

Your best shot is NSUserDefaults. Save a flag (a boolean) in NSUserDefaults to indicate whether it is the first time the user has opened the app.
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("IS_FIRST_TIME") as? Bool != nil {
// First time, do something...
// ...
// ...
// Now, save a flag to indicate that this was the first time.
userDefaults.setValue(true, forKey: "IS_FIRST_TIME")
userDefaults.synchronize() // Needed to save the new value.
}

Related

NSUserDefault takes a few seconds to store the value

As the title says, I am trying to store coins in NSUserDefault.
This is how I register it in the AppDelegate -didFinishLaunchingWithOptions:
let defaults:UserDefaults = UserDefaults.standard
defaults.register(defaults: ["firstLaunch":true,
"name":"",
"mycoins":0,
"didRate":false,])
That works just fine.
This is currently how I store it:
extension UserDefaults {
func incrementIntegerForKey(key:String, by: Int) {
let int = integer(forKey: key)
set(int + by, forKey:key)
}
}
UserDefaults.standard.incrementIntegerForKey(key: "mycoins", by: +1)
UserDefaults.standard.synchronize()
I am using this to check for the amount:
print("Coins: \(UserDefaults.standard.integer(forKey: "mycoins"))")
So let's say I increment the score a few times, and fully closes the app 2 seconds after running the increment and sync function, the new score doesn't fully store. Maybe one or two coins get's stored, but not all five. Now - if I wait 7-10 seconds after running that same function, it get's stored perfectly.
What am I doing wrong here? I am using synchronize() so it doesn't wait for the sync. How can I make it store at the exact time?

Photo Editing Extension - Revert Image to Original

I'm developing a photo editing extension for iOS, bundled in a container app that provides the same photo editing functionality.
In order to reuse code, I have a view controller class that adopts the required PHContentEditingController protocol, and I subclassed it for use both as the main interface of the app extension, and as the "working screen" of the container app.
On the editing extension, the controller's methods are called by the Photos app's editing session as described in Apple's documentation and various tutorials you can find around the web.
On the container app, on the other hand, I first obtain a PHAsset instance by means of the UIImagePickerController class, and directly start the editing session manually on my "work" view controller like this:
// 'work' is my view controller which adopts
// `PHContentEditingController`. 'workNavigation'
// embeds it.
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { (adjustmentData) in
return work.canHandle(adjustmentData)
}
asset.requestContentEditingInput(with: options, completionHandler: { [weak self] (input, options) in
// (Called on the Main thread on iOS 10.0 and above)
guard let this = self else {
return
}
guard let editingInput = input else {
return
}
work.asset = asset
work.startContentEditing(with: editingInput, placeholderImage: editingInput.displaySizeImage!)
this.present(workNavigation, animated: true, completion: nil)
})
When the user finishes editing, the work view controller calls finishContentEditing(completionHandler: on itself to finish the session:
self.finishContentEditing(completionHandler: {(output) in
// nil output results in "Revert" prompt.
// non-nil output results in "Modify" prompt.
let library = PHPhotoLibrary.shared()
library.performChanges({
let request = PHAssetChangeRequest(for: self.asset)
request.contentEditingOutput = output
}, completionHandler: {(didSave, error) in
if let error = error {
// handle error...
} else if didSave {
// proceed after saving...
} else {
// proceed after cancellation...
}
})
})
Within the editing session, the user can 'clear' the previous edits passed as adjustment data, effectively reverting the image to its original state.
I've noticed that, if I finish the editing by calling the completion handler passed to finishContentEditing(completionHandler:) with nil as its argument (instead of a valid PHContentEditingOutput object), the Photos framework will prompt the user to "revert" the image instead of "modifying" it:
func finishContentEditing(completionHandler: #escaping (PHContentEditingOutput?) -> Void) {
guard let editingInput = self.editingInput, let inputURL = editingInput.fullSizeImageURL else {
return completionHandler(nil)
}
if editingInput.adjustmentData != nil && hasUnsavedEdits == false {
// We began with non-nil adjustment data but now have
// no outstanding edits - means REVERT:
return completionHandler(nil)
}
// (...proceed with writing output to url...)
However, this only works when running from the container app. If I try the same trick from the extension (i.e., load an image that contains previous edits, reset them, and tap 'Done') I get the dreaded "Unable to Save Changes" message...
What is the correct way to revert previous edits to an image from within a photo editing extension?
Months later, still no answer, so I begrudgingly adopted this workaround (which is still preferable to an error alert):
When the user taps "Done" from the Photos extension UI and the image has no edits applied to it (either because the user reset previous edits, or didn't apply any changes to a brand new image), perform the following actions from within finishContentEditing(completionHandler:):
Create adjustment data that amounts to no visible changes at all ("null effect") and archive it as Data.
Create a PHAdjustmentData instance with the "null effect" data from above, with the formatVersion and formatIdentifier properly set.
Create a PHContentEditingOutput instance from the editing input passed at the beginning of the session (as usual), and set the adjustment data created above.
Read the unmodified image from the inputURL property of the editing input, and write it unmodified to the url specified by the PHContentEditingOutput instance's renderedContentURL property.
Call the completionHandler block, passing the editing output instance (as normal).
The result: The image is saved in its original state (no effects applied), and no alerts or errors occur.
The drawback: The library asset remains in the 'edited' state (because we passed non-nil editing output and adjustment data, there was no other choice), so the next time the user tries to edit it from Photos.app, the red 'Revert' button will be present:
However, choosing 'revert' results in no visible changes to the image data (duh!), which can be confusing to the user.
—-
Update
I checked what the built-in “Markup” extension does:
...and it is consistent with my workaround above, so I guess this is the best that can be done.

Inconsistent value from NSUserDefaults at app startup

Under the iOS simulator, I'm having trouble getting a consistent value back from NSUserDefault on app startup. I've set the value in a previous run of the app (and I put some prints in there to make sure there was no accidental changes), so I don't think there's a problem with the way I'm calling synchronize(). The reads are happening on the main thread also, and after I've set the default values. After a couple of seconds, if I retry the fetch from NSUserDefaults, I get the correct value (I put a timer in there to verify that the value gets corrected and seems to stay the same after startup).
The value represents where the user will store the data (on iCloud or just on the local device) and is represented by an integer in the user defaults. Here's the enum representing the values:
class MovingDocument: UIDocument {
enum StorageSettingsState: Int {
case NoChoice = 0
case Local = 1
case ICloud = 2
}
}
Here's an excerpt of the class used to access NSUserDefaults:
class Settings {
static let global = Settings()
private enum LocalStoreKeys : String {
case IsHorizontal = "IsHorizontal"
case ItemInPortrait = "ItemInPortrait"
case RotateTitle = "RotateTitle"
case StorageLocation = "StorageLocation"
}
// Other computed variables here for the other settings.
// Computed variable for the storage setting.
var storageLocation: MovingDocument.StorageSettingsState {
get {
print("Storage state fetch: \(NSUserDefaults.standardUserDefaults().integerForKey(LocalStoreKeys.StorageLocation.rawValue))")
return MovingDocument.StorageSettingsState(rawValue: NSUserDefaults.standardUserDefaults().integerForKey(LocalStoreKeys.StorageLocation.rawValue)) ?? .NoChoice
}
set {
print("Setting storage location to \(newValue) (\(newValue.rawValue))")
NSUserDefaults.standardUserDefaults().setInteger(newValue.rawValue, forKey: LocalStoreKeys.StorageLocation.rawValue)
synchronize()
CollectionDocument.settingsChanged()
}
}
func prepDefaultValues() {
NSUserDefaults.standardUserDefaults().registerDefaults([
LocalStoreKeys.IsHorizontal.rawValue: true,
LocalStoreKeys.ItemInPortrait.rawValue: true,
LocalStoreKeys.RotateTitle.rawValue: true,
LocalStoreKeys.StorageLocation.rawValue: MovingDocument.StorageSettingsState.NoChoice.rawValue
])
}
func synchronize() {
NSUserDefaults.standardUserDefaults().synchronize()
}
}
The value that I should be reading (and which I always seem to get after startup) is the value '1' or .Local. The value that I am sometimes getting instead is '0' or .NoChoice (which just happens to be the default value). So, it looks like it's using the default value until some later point in the application startup.
For completeness sake, here's the relevant app delegate code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
Settings.global.prepDefaultValues() // Must be called before doing CollectionDocument.start() because we need the default value for storage location.
CollectionDocument.start()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
browserController = ViewController()
navController = UINavigationController(rootViewController: browserController!)
navController!.navigationBar.translucent = false
self.window!.rootViewController = navController
self.window!.makeKeyAndVisible()
checkStorageLocation()
return true
}
The getter is called within CollectionDocument.start() (CollectionDocument is a subclass of MovingDocument, where the location enum is defined). The call at the end to checkStorageLocation() is my periodic debugging check for the value of the storage location (it calls itself back via dispatch_after()).
As a side note, I also tried putting most of the code in the application(didFinishLaunchingWithOptions...) into a dispatch_async call (on the main thread) to see if that made a difference (just the call to set up the default values and the return statement were done immediately, the rest of the calls were put into the asynchronous call block), and it still got the wrong value (sometimes). I guess I could try using dispatch_after instead (and wait a second or two), but I can't really start the app until this is complete (because I can't read the user data from the doc until I know where it is) and a loading screen for just this one problem seems ridiculous.
Any ideas about what might be going wrong? I'm unaware of any limitations on NSUserDefaults as far as when you are allowed to read from it...
Rebooting my mac fixed the problem (whatever it was). iOS 10, NSUserDefaults Does Not Work has a similar problem and the same solution (though the circumstances are quite different, since I have tried neither Xcode 8 nor iOS 10).

Get user visits count of window in appcelerator titanium

I am working on ios app,,User will login to the app.I have multiple screens in one screen i have a requirement is if user first time visits that screen i need to show the instructions screen.If screen visits count more than once no need to show the instructions.
I need to store that screens count for each user separately how can we achieve this in appcelerator titanium
Please help me thanks in advance.
You could use the open or focus event to pick up when someone hits the screen and then either:
If this is a connected app, and you're downloading the user profile on login, you could store a flag to say tutorialSeen or similar. Check this when the window opens / focuses or
if not connected or you want to store locally, store the flag in the Ti.App.Properties under a user profile.
(you could equally store a count value that increments when they hit the screen but since you just want the first time captured, I'd go with a) capture event, b) check property, c) if false, set to true and show tutorial, d) if true, skip.)
Hope that helps!
I think you do not need to store the user visit count if your only purpose is to show the Instruction window only when user login into the app for first time.
You can do it this way:
someController.js
function onLoginSuccess() {
// getInt() will return the value of INSTRUCTION_WINDOW property, you can name this property whatever you want.
// if this property does not exist, then it will return the value of the second parameter.
var showInstruction = Ti.App.Properties.getInt("INSTRUCTION_WINDOW", 1);
if (showInstruction) {
Alloy.createController('instructionWindow').getView().open();
} else {
Alloy.createController('nextWindow').getView().open();
}
}
// logout function can be anywhere
// remember to set the property to 1 if you want to show the instructions again after logout and then login.
function logout() {
Ti.App.Properties.setInt("INSTRUCTION_WINDOW", 1);
}
instructionWindow.js
$.instructionWindow.addEventListener('open', function () {
// after opening the instruction window, set this property to 0
// so it won't be shown up the next time the already logged-in user opens the app
Ti.App.Properties.setInt("INSTRUCTION_WINDOW", 0);
});
first you set the nsuserdefaults value #"1".
[[NSUserDefaults standardUserDefaults] setObject:#"1" forKey:#"showtheinstruction"];
[[NSUserDefaults standardUserDefaults] synchronize];
after this you have to compare this string and shows the instruction message.
NSString *str_savedValue = [[NSUserDefaults standardUserDefaults]
stringForKey:#"showtheinstruction"];
if (str_savedValue isEqualToString:#"1") {
// show the instruction window.
}
else
{
// don't show the instruction window.
}

How to make a screen show only on initial launch

I'm trying to make the intro screens for my app - namely the terms of service screen that all users have to agree to the 1st time they download and run the app. obviously, once the users agree, i dont want to show them it again every time they log on the app. what is the best way to go about doing this? I've been reading about NSUserDefaults, but am stuck pretty much after that.
In your AppDelegate or whichever class needs to check for first-run status:
Bool isFirstRun = !NSUserDefaults.standardUserDefaults().boolForKey("kAppPreviousLaunchKey")
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"kAppPreviousLaunchKey")
if isFirstRun {
// React here
}
Play with this piece of code in the AppDelegate Class
let launchedBefore = NSUserDefaults.standardUserDefaults().boolForKey("launchedBefore")
if launchedBefore {
print("Not first launch.")
}
else {
print("First launch, setting NSUserDefault.")
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "launchedBefore")
}

Resources