I currently have a quiz game app where each round is represented by a different view controller. When the user closes the app and later re-opens the app I want it to keep the same score and stay on the same viewcontroller it was last on. I believe NSUserDefaults is the way to go but not sure how to set it up to remember the last round it was on. Any help will be appreciated!
You can create an user defaults object using:
// Create an object reference to the NSUserDefaults class
let defaults = NSUserDefaults.standardUserDefaults()
you can request and set objects for this NSUserDefaults class which will be persistent upon closing and opening the app.
// Set a string for a key
defaults.setObject("a string", forKey: "myVar")
// Request the object associated with the key:
let myVar : String = defaults.stringForKey("myVar")
You can save and update the value for the object whenever you feel like saving it. If you save the StoryBoard ID of the View Controller you want to present when starting the app then you can reopen it whenever the app appears using:
// Create a reference to the Storyboard file
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
// Create a reference to the ViewController you want to open
let vc : UIViewController = storyboard.instantiateViewControllerWithIdentifier("ID FROM NSUserDefaults")
// Present the ViewController
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(vc, animated: true, completion: nil)
})
Related
I'm in front of a big issues, and the only reason I can't find a solution is because my lack of knowledge about swift and memory management in swift. So here is my concerns. I'm working in swift 4.0 and iOS 9.3
I'm actually making a picture gallery app with login/logout. Basic application.
I'm working in cleanSwift So I don't have those HUGE ViewControllers.
My application is separate in 3 VC : The login, the gallery and the settings (which contains the LogOut).
Here is my problem. When I log out, I want to create a new loginVC and clear all previous VC.
So I have my cleanMemory function which set all the UIImage to nil
func cleanMemory(request: Gallery.Request) { // Interactor
worker.cleanMemory(completionHandler: { (Value) in
self.interventions?.removeAll() // Interventions contains UIImages
self.interventionsSelected.removeAll() // InterventionsSelected contains UIImages
})
}
and then I delete the rests of the UIImage and the VC
func cleanMemory() {
interactor?.cleanMemory(request: Gallery.Request())
self.displayedInterventions.removeAll() // displayedInterventions contains UIImages
interactor = nil
router = nil
self.removeFromParentViewController()
self.navigationController?.popViewController(animated: true)
}
But when I create my new LoginVC.. my RAM didn't decrease.. And when I check the app memory, not a single VC was deleted.. And when I execute the loop Logout/Login 3 times, my app crash because I'm not well managing my RAM_
So where did I get wrong, and why ??
Thanks you for your answer.
EDIT: I was having 2 problems :
My completionHandler was keeping my VC alive
I was switching VC with .present, so that was keeping my VC in memory.
So you should change VC like that :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyboard.instantiateViewController(withIdentifier: "LoginController")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = destinationVC
To remove viewController from memory you simply need to remove it from navigation stack. So when you call navigationController?.popViewController(animated: true) and back to previous view controller you already destroy that controller.
Then,
Here is my problem. When I log out, I want to create a new loginVC and clear all previous VC. So I have my cleanMemory function which set all the UIImage to nil
on logout it's good practice to stop all request but you don't need to do any changes to UI, because it takes some time and it doesn't need to "remove controller from memory". How to check if view controller completely removed from navigation stack? Simply write print statement in deinit func, compile code and go back from this view controller.
deinit {
print("ViewController deinit")
}
If this print works fine (you can see text in xcode console), you achieve the result - controller has been removed from navigation stack, but if there is no print result you probably forget to right manage your closures. For example
worker.cleanMemory(completionHandler: { (Value) in
...
})
this closure may hold your controller when your think that controller already deallocated and it means that your controller present somewhere in memory. To prevent these retain cycles you need to use [unowned self] of [weak self] (just google for this keywords, it's very easy to understand) like this:
// or you can use `[unowned self]`
worker.cleanMemory(completionHandler: { [weak self] (Value) in
guard let `self` = self else { return } // only for `weak` way
...
})
So, in this case there are nothing that can hold your controller alive after pop from navigation stack action or so.
These are simple rules that you should follow to write well managed code.
I have an app that is not super intuitive. I want to take screenshots of the confusing screens and then use MSpaint to write instructions and doodles. When the user opens the view in the app for the first time, I want to present the series of altered screenshots along with an "OK" button. pressing OK will dismiss the screenshot and it will not be shown again. Is there an efficient way to do this? I am new to Swift and Xcode. Any help would be appreciated
You need to use NSUserDefaults to save the state of the app (tutorial shown or not) - NSUserDefaults saves data between the runs of the app to the device storage.
Then you need to change AppDelegate to change the initial view controller according to the value you saved - that way if tutorial has been shown it will not show again.
Assuming you have var called toturialShown
Set it to false and each run check its value to determine if the tutorial needs to be shown
When the user taps on dismiss tutorial button use NSUSerDefaults to save this new status
Store
UserDefaults.standard.set(toturialShown, forKey: "toturialShownKey")
Retrieve
UserDefaults.standard.bool(forKey: "toturialShownKey")
Remove - in case you want to delete it completely from storage
UserDefaults.standard.removeObject(forKey: "toturialShownKey")
On AppDelegate in applicationDidFinishWithOptions function
(Note that I didn't test the code)
var vc = ""
If toturialShown {
vc = "regularVC"
} else {
vc = "toturialVC"
}
let initialViewController = mainStoryboard.instantiateViewController(withIdentifier: vc)
let initialViewController = mainStoryboard.instantiateViewController(withIdentifier: "LoginSignupVC")
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
Note1: you need to add identifiers to you VCs in storyboard
Note 2: if you set your initial VC (on storyboard) to the regular VC, you can change the code above to set the initial VC programmatically only if the tutorial has not been shown, results in slightly more elegant code
I have a file in Swift that holds all my queries. And when saving a record with saveOperation.perRecordProgressBlock this file call ChatView view controller and updates the progressBarUpdate function.
So far I can get the print within progressBarUpdate to print the progress just fine. But when I get to update progressBarMessage.setProgress(value!, animated: true) the application just crash with the following error: fatal error: unexpectedly found nil while unwrapping an Optional value
If I try to run progressBarMessage.setProgress(value!, animated: true) through viewDidLoad it updates the progress bar fine, no error. Which means the outlet is working just fine.
Other thing to consider, is that my print(".... perRecordProgressBlock - CHAT VIEW\(value)") works just fine. If gets the updates from Queris.swift. It is just the progressBarUpdate that is causing issues.
# my Queries.swift file option 1
saveOperation.perRecordProgressBlock = { (recordID, progress) -> Void in
print("... perRecordProgressBlock \(Float(progress))")
var chatView = ChatView()
chatView.progressBarUpdate(Float(progress))
}
# my Queries.swift file option 2
saveOperation.perRecordProgressBlock = { (recordID, progress) -> Void in
print("... perRecordProgressBlock \(Float(progress))")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let chatViewController = storyboard.instantiateViewControllerWithIdentifier("ChatViewVC") as! ChatView
chatViewController.progressBarUpdate(Float(progress))
}
# ChatView view controller
func progressBarUpdate(value: Float)
{
print(".... perRecordProgressBlock - CHAT VIEW\(value)")
if (value as? Float) != nil
{
progressBarMessage.setProgress(value, animated: true)
}
}
The way you are instantiating the viewController is not the right way and hence the crash/nil val. viewController loads its view hierarchy only when something sends it a view message. The system will do this by its own when to put the view hierarchy on the screen. And it happens after calls like prepareForSegue:sender: and viewWillAppear: , loadView(), self.view.
So here your outlets are still nil since it is not loaded yet.
Just try to force your viewController to call self.view and then access the functions from that viewController.
var chatView = ChatView()
I'm going to go out on a limb here and say you are using storyboards/xibs. If so, the above would not be the correct way to instantiate a new view controller. Here's some information on the difference (the question refers to Objective-C but the concept is the same in Swift)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let chatViewController = storyboard.instantiateViewControllerWithIdentifier("identifier-you-set-in-storyboard") as! ChatView
Where identifier-you-set-in-storyboard is set in the interface builder (the linked question is old but illustrates the concept, the field label might have changed in newer versions)
If by some off chance you are creating you are setting up your views in code (as opposed to storyboards), you'd need to call chatView.loadView() before chatView.progressBarUpdate.... (Or just try to access the view property and it should call loadView for you.)
this is my code:
let swswipe:SWRevealViewController=UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("swswipe") as! SWRevealViewController
if(indexPath.row==0)
{
let swipe=swipeView()
swipe.collectionClick="one"
self.presentViewController(swswipe, animated: true, completion: nil)
}
front
|
1st view <->swreveal<->2ndview(swipe)
i am trying to sent values from 1st to 2nd but intermediate there is a swrevealview controller is there ,so that value didn't receive
but i didn't receive anything
print("coming to collection view", self.collectionClick)
You can do this in any of following ways:
Send a notification when the second controller is going away and have the parent listen for it.
Create a delegate protocol that lets the second controller reference a parent method directly.
(If that needs to implement over the back button action)Use the parent's viewWillAppear: and check the isMovingToParentViewController property for whether it's just re-appearing.
and apply what is best for your value passing conditions and availability of values.
I have an app where a user logs in by entering their details and that sends a HTTP GET request to my API which authenticates the user and sends the users data/user object back from the database.
Once this is done, within my HTTP request which is triggered on button tap I send the users data onto the next view and perform a transition to the view by using the following code:
if let parseJSON = json
{
// parseJSON contains the return data. We are programmatically creating a segue between two views
// passing data between them.
dispatch_async(dispatch_get_main_queue())
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("mainView") as! MainViewController
var fullname = parseJSON["employeeName"] as! String
var job = parseJSON["jobTitle"] as! String
var email = parseJSON["email"] as! String
vc.username = fullname
vc.jobTitle = job
vc.userEmail = email
self.presentViewController(vc, animated: true, completion: nil)
}
The above works perfectly.The from my second view I create a prepare for segue for another view where I am trying to pass the user data that was just passed to the 2nd view when the user logged in by using the code below:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "settings") {
// pass data to next view
let viewController = segue.destinationViewController as! SettingsTableViewController
viewController.username = username
viewController.jobTitle = jobTitle
}
}
This again works properly. When I tap on the button to go to view 3 the prepareforsegue function passes the data and shows the following view (ignore email field):
But when I click on the back button and try to access to the same view all the data thats coming from the API and is being passed from View 1 to View 2 to View 3 disappears. See below:
I DO understand the reason why this happening, I am passing data in 1 direction and it is not being saved only being passed from one view to another and when I go back a view that process of passing between breaks and hence the data is lost.
My question is how can I preserve data when the user first logs in and the API sends the data back. Can I save it? and keep passing it through multiple views and still keep it no matter what?
Any help will be greatly appreciated.
Thanks in advance.
In short : Store the data you get in an instance variable in your view and use that variable for the segues.
Long Explanation:
Best practice is to create a model class for the stuff you're getting from your API, put all data in a variable of that class when retrieving the info, and like a said have a variable of that type in your view classes.
Tip: read a bit about the MVC paradigma's (lotsa stuff online, if you have some time read the book Design Patterns by the gang of four)