For each UITextView using UserDefaults, I've made a function to save and a function to display.
Whatever text is added needs to be displayed at the time of adding, saved and then displayed again when opening the app again.
If I install the app with ONLY the function to save, quit the app and add the function to display then reinstall without deleting the installed app everything works perfectly.
If I install the app with both functions added it doesn't work.
There has to be a simple solution for this, I'm obviously doing something wrong.
The data from one textView is used to calculate results and then to display them on the other textView.
All data is added with other functions, none by the user.
numberHistoryView.isEditable = false
numberHistoryView.isSelectable = false
func saveHistoryTextView()
{
let defaults = UserDefaults.standard
let numberHistory = numberHistoryView.text
defaults.set(numberHistory, forKey: "combos")
}
func displaySavedHistory()
{
let defaults = UserDefaults.standard
let savedCombos = defaults.object(forKey: "combos") as? String ?? ""
numberHistoryView.text = savedCombos
}
func saveFrequencyTextView()
{
let defaults = UserDefaults.standard
let numberFrequency = numberFrequencyCount.text
defaults.set(numberFrequency, forKey: "frequency")
}
func displaySavedFrequency()
{
let defaults = UserDefaults.standard
let savedFrequency = defaults.object(forKey: "frequency") as? String ?? ""
numberFrequencyCount.text = savedFrequency
}
override func viewWillDisappear(_ animated: Bool)
{
saveHistoryTextView()
saveFrequencyTextView()
}
override func viewWillAppear(_ animated: Bool)
{
displaySavedHistory()
displaySavedFrequency()
}
This depends on the order and timing in which you are calling save and display methods.
When you're installing a fresh app, there will be no data in saved in UserDefaults. So when you call displaySavedHistory() and displaySavedFrequency() methods in viewWillAppear(_:), nothing will be fetched because nothing is saved yet.
Now, when you save the data using saveHistoryTextView() and saveFrequencyTextView() methods in viewWillDisappear(_:) and then you kill and run the app again, the saved data will be fetched and displayed.
Also, since you're saving the data in UserDefaults, and UserDefaults are saved within the sandbox, so the data won't persist when you delete the app. You've to save the data in iCloud or keychain etc. if you want to persist the data even after app deletion.
Once I put my brain into a theta state with the right frequency I managed to figure it out.
Thanks to #Naresh and all other contributors for trying to help as you may have assisted me a little.
The solution basically just required a simple if statement.
Everything now works perfectly.
func saveHistoryTextView()
{
if numberHistoryView.text?.count != nil
{
let defaults = UserDefaults.standard
defaults.set(numberHistoryView.text!, forKey: "combos")
}
}
func displaySavedHistory()
{
let defaults = UserDefaults.standard
if let savedCombos = defaults.string(forKey: "combos")
{
numberHistoryView.text = savedCombos
}
}
func saveFrequencyTextView()
{
if numberFrequencyCount.text?.count != nil
{
let defaults = UserDefaults.standard
defaults.set(numberFrequencyCount.text!, forKey: "frequency")
}
}
func displaySavedFrequency()
{
let defaults = UserDefaults.standard
if let savedFrequency = defaults.string(forKey: "frequency")
{
numberFrequencyCount.text = savedFrequency
}
}
}
override func viewWillDisappear(_ animated: Bool)
{
saveHistoryTextView()
saveFrequencyTextView()
}
override func viewWillAppear(_ animated: Bool)
{
displaySavedHistory()
displaySavedFrequency()
}
You can do it with property observer as:
private let DATA_KEY = "Saved Data"
//After initialising the outlet we can set the data
#IBOutlet weak var textView: UITextView! {
didSet {
textView.text = self.data
}
}
private var data: String {
set {
//Save data in user defaults
UserDefaults.standard.set("The value you will assign", forKey: DATA_KEY)
}
get {
//get the data from user defaults.
return UserDefaults.standard.value(forKey: DATA_KEY) as? String ?? ""
}
}
//UITextViewDelegate: set the text data on end of UITextView editing
func textViewDidEndEditing(_ textView: UITextView) {
self.data = textView.text
}
Related
I am currently building a story feature in my iOS app. Like on Instagram and Snapchat. The story also has a circle around the image. It changes the color if the story has been seen.
At the start of the App, I save some values in CoreData to identify every story.
var context: NSManagedObjectContext!
var storys = [Story]()
private init() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
context = appDelegate.persistentContainer.viewContext
fetchStoryItems()
}
func addStorys(with shoeID: String, raffles: String, seenStory: Bool) {
let storyItem = NSEntityDescription.insertNewObject(forEntityName: "Story", into: context) as! Story
storyItem.shoeID = shoeID
storyItem.newRaffles = raffles
storyItem.seen = seenStory
if !storys.contains(where: {$0.shoeID == shoeID}) {
storys.append(storyItem)
saveContext()
}
}
func fetchStoryItems() {
let request: NSFetchRequest<Story> = NSFetchRequest<Story>(entityName: "Story")
do {
let storyItemsArray = try context.fetch(request)
storys = storyItemsArray
} catch {
print(error.localizedDescription)
}
}
func saveContext() {
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
This works fine and every value is stored once in CoreData. So that the circle is displayed in the correct color, I change the value if the story has been seen. If the user taps von a story the following methods are executed.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
CoreDataStoryCheck.sharedInstance.storyTapped(with: Array(storyShoeModel.values)[indexPath.row].ID!, seenStory: true)
collectionViewOrTableView = 1
performSegue(withIdentifier: "details", sender: indexPath.row)
}
func storyTapped(with shoeID: String, seenStory: Bool) {
if let story = storys.first(where: {$0.shoeID == shoeID && $0.seen == false}) {
story.setValue(true, forKey: "seen")
saveContext()
}
}
Now here comes my issue: Every time I am calling this method my data duplicates in CoreData. I have found that this happens every time when I call saveContext(). I don't understand why this happens.
I want to track completed Topics list in TableView
I have set delegate which confirms to tableView topic mark as completed
protocol TopicDetialVCDelegate: class {
func hasBeenCompletedTopic()
}
TableViewVC
func hasBeenCompletedTopic() {
isPerformedDelegate = true
if !completedTopicIdArray.contains(completedTopicId) {
completedTopicIdArray.append(completedTopicId)
}
print("completed Topics \(completedTopicIdArray)")
print("TopicVC: Completed Topics total: \(completedTopicIdArray.count)")
}
This is working, but i want to persist always mark as completed which already marked
Here is code of CellForRowAt
if isPerformedDelegate {
for _ in 0...completedTopicIdArray.count {
if completedTopicIdArray.contains(filteredTopicArray[indexPath.row].id!) {
cell.topicCompletedTickImageView.image = #imageLiteral(resourceName: "Tick")
}
}
}
What i want
There should be a array which get all completed topics idz, and on every run of app checks if cell indexpath contain topicID show Tick image
I can use UserDefaults like this
UserDefaults.standard.set(array, forkey: "abc")
but problem is array will reinitialize on run of app again
like so in ViewWillDisappear
override func viewWillDisappear(_ animated: Bool) {
UserDefaults.standard.set(completedTopicIdArray, forKey: "completedTopics")
UserDefaults.standard.synchronize()
}
accessing in ViewDidLoad
let topics = UserDefaults.standard.array(forKey: "completedTopics")
print(topics as? [Int] ?? [Int]())
I have been solved this Problem Using CoreData, want to share
Declare empty array
var completedTopicsIdPersistArray: [Int] = [Int]()
CoreData Method for Creating
func persistIdIntoCoreData() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "ComTopicDB", in: managedObjectContext)
else {
print("Entity Not Found")
return
}
let topicIdz = NSManagedObject(entity: entity, insertInto: managedObjectContext)
topicIdz.setValue(completedTopicId, forKey: "topicId")
do {
try managedObjectContext.save()
}
catch let error as NSError {
print("Unable to save into CoreData: \(error.userInfo)")
}
}
CoreData Method for Retrieving
func retrieveIdFromCoreData() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "ComTopicDB")
do {
let topicIdz = try managedObjectContext.fetch(fetchRequest)
for id in topicIdz {
let unwrapId = id.value(forKey: "topicId") as! Int
if !completedTopicsIdPersistArray.contains(unwrapId) {
completedTopicsIdPersistArray.append(unwrapId)
}
}
}
catch let error as NSError {
print("Found issue during retrieve id from CoreData:\(error.userInfo)")
}
}
Calling inside of delegate method, which confirms topic completed
func hasBeenCompletedTopic() {
isPerformedDelegate = true
if !completedTopicIdArray.contains(completedTopicId) {
completedTopicIdArray.append(completedTopicId)
persistIdIntoCoreData()
}
print("completed Topics \(completedTopicIdArray)")
print("TopicVC: Completed Topics total: \(completedTopicIdArray.count)")
retrieveIdFromCoreData()
}
and finally CellForRowAt Method
if completedTopicsIdPersistArray.contains(filteredTopicArray[indexPath.row].id!) {
cell.topicCompletedTickImageView.image = #imageLiteral(resourceName: "Tick")
}
Finally call inside ViewWillAppear, because it comes from topicDetailVC back
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
retrieveIdFromCoreData()
}
Now all topics will show Tick icon, which marked as completed, even terminate and relaunch app
My idea is that when Button "A" is tapped every time, it will set the NSDate value automatically. When the current time is larger than the existing NSDate value, it print "yes". Here is my code but I dont know what's wrong.
import UIKit
class ViewController: UIViewController {
var currentDateTime = NSDate()
override func viewDidLoad() {
super.viewDidLoad()
observeTime()
}
#IBAction func show(_ sender: Any) {
print(UserDefaults.standard.dictionaryRepresentation())
}
let userDefaults = UserDefaults.standard
func observeTime() {
let posttime = userDefaults.object(forKey: "LastPostingTime") as? NSDate
if ((posttime?.isGreaterThanDate(dateToCompare: currentDateTime))!) {
print("yes")
}
}
#IBAction func hihi(_ sender: Any) {
observeTime()
userDefaults.set(NSDate(), forKey: "LastPostingTime")
}
}
extension NSDate {
func isGreaterThanDate(dateToCompare: NSDate) -> Bool {
//Declare Variables
var isGreater = false
//Compare Values
if self.compare(dateToCompare as Date) == ComparisonResult.orderedDescending {
isGreater = true
}
//Return Result
return isGreater
}
}
In function hihi you first store the current date into NSUserDefaults before reading it. So you will get back what you just stored: the current time.
You may want to read it first, compare it to currentDateTime and then store it into user defaults.
I am trying to load a value that has been inputted by the user in the viewDidLoad via a String. I am using UserDefaults to save the users value that they input into a UITextField (userValue), I then save this to the String 'search'. I am able to print out the value of search in the GoButton function, and it works fine, but when I load my ViewController as new, the value of 'search' is equal to nil. The aim here is to have the users previous search saved, and loaded into the UITextField (that is used as a search box) upon loading the ViewController.
Code Below:
class ViewController: UIViewController {
#IBOutlet weak var userValue: UITextField!
var search: String!
}
viewDidLoad:
override func viewDidLoad() {
if (search != nil)
{
userValue.text! = String (search)
}
}
Button Function:
#IBAction func GoButton(_ sender: Any) {
let userSearch: String = userValue.text!
let perference = UserDefaults.standard
perference.set(userSearch, forKey: "hello")
perference.value(forKey: "hello")
let value = perference.value(forKey: "hello") as! String
search = value
print (search) // <<this works, it prints out the users search value
}
#VishalSharma has the right idea, but the code should probably look more like…
override func viewDidLoad() {
super.viewDidLoad()
if let search = UserDefaults.standard.string(forKey: "hello") {
userValue.text = search
}
}
or even more simply…
userValue.text = UserDefaults.standard.string(forKey: "hello")
When you load, search is effectively nil.
So either you read userDefaults in viewDidload or you come through a segue: then you can load search in the prepare.
I've always found it convenient and useful to store all UserDefault properties as an extension within the same file along with their getters and setters. It is far easier to maintain, use and read. by using the #function keyword for the key you are referencing the variable's name and not a string that can be accidentally changed somewhere else in code.
UserDefaults.swift
import Foundation
// An Extension to consolidate and manage user defaults.
extension UserDefaults {
/// A value Indicating if the user has finished account setup.
/// - Returns: Bool
var finishedAcountSetup: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
/// The hello text at the start of the application.
/// - Returns: String?
var helloText: String? {
get { return string(forKey: #function) }
set {set(newValue, forKey: #function) }
}
//etc...
}
When you use these values reference the standard settings:
//Setting
UserDefaults.standard.helloText = "Updated Hello Text"
// Getting
// for non-optional value you can just get:
let didCompleteSetup = UserDefaults.standard.finishedAcountSetup
// Otherwise, safely unwrap the value with `if-let-else` so you can set a default value.
if let text = UserDefaults.standard.helloText {
// Ensure there is text to set, otherwise use the default
label.text = text
} else {
// helloText is nil, set the default
label.text = "Some Default Value"
}
obviously, it provides nil because when view controller load the search is nil try this.
let perference = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
if (perference.value(forKey: "hello") != nil) {
search = perference.value(forKey: "hello") as! String
userValue.text! = String (search)
}
}
I'm trying to check if the a user default exists, seen below:
func userAlreadyExist() -> Bool {
var userDefaults : NSUserDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.objectForKey(kUSERID) {
return true
}
return false
}
However, no mater what it will always return true even when the object doesn't exist yet? Is this the right way for checking existence ?
Astun has a great answer. See below for the Swift 3 version.
func isKeyPresentInUserDefaults(key: String) -> Bool {
return UserDefaults.standard.object(forKey: key) != nil
}
I copy/pasted your code but Xcode 6.1.1 was throwing some errors my way, it ended up looking like this and it works like a charm. Thanks!
func userAlreadyExist(kUsernameKey: String) -> Bool {
return NSUserDefaults.standardUserDefaults().objectForKey(kUsernameKey) != nil
}
Swift 5:
if UserDefaults.standard.object(forKey: "keyName") != nil {
//Key exists
}
Yes this is right way to check the optional have nil or any value objectForKey method returns AnyObject? which is Implicit optional.
So if userDefaults.objectForKey(kUSERID) have any value than it evaluates to true. if userDefaults.objectForKey(kUSERID) has nil value than it evaluates to false.
From swift programming guide
If Statements and Forced Unwrapping
You can use an if statement to find out whether an optional contains a value. If an optional does have a value, it evaluates to
true; if it has no value at all, it evaluates to false.
Now there is a bug in simulators than after setting key in userDefaults they always remain set no matter you delete your app.You need to reset simulator.
Reset your Simulator check this method before setting key in userDefaults or remove key userDefaults.removeObjectForKey(kUSERID) from userDefaults
and it will return NO.On devices it is resolved in iOS8 beta4.
This is essentially the same as suggested in other answers but in a more convenient way (Swift 3+):
extension UserDefaults {
static func contains(_ key: String) -> Bool {
return UserDefaults.standard.object(forKey: key) != nil
}
}
usage: if UserDefaults.contains(kUSERID) { ... }
Simple Code to check whether value stored in UserDefault.
let userdefaults = UserDefaults.standard
if let savedValue = userdefaults.string(forKey: "key"){
print("Here you will get saved value")
} else {
print("No value in Userdefault,Either you can save value here or perform other operation")
userdefaults.set("Here you can save value", forKey: "key")
}
Many of the solutions here are valid. Still, I think they solve the wrong problem.
Usually, code like this is used to check if a value is set so another default value can be used:
if isKeyPresentInUserDefaults(key: "username") {
return UserDefaults.standard.string(forKey: "username")
} else {
return "No username was set"
}
You shouldn't care if a key is set or not. There is a far more elegant approach for having default values in UserDefaults:
UserDefault.standard.register(defaults: ["username": "No username was set"])
If you run this code at app launch, subsequent calls to UserDefaults.standard.string(forKey: "username") will return the default value of "No username was set" if no value was set for the key yet.
for swift 3.2
func userAlreadyExist(kUsernameKey: String) -> Bool {
return UserDefaults.standard.object(forKey: kUsernameKey) != nil
}
public class PreferencesUtils {
private init() {
}
public static func setBoolData(boolValue: Bool, dataName: String) {
UserDefaults.standard.set(boolValue, forKey: dataName)
}
public static func getBoolData(dataName: String)-> Bool{
let defaults = UserDefaults.standard
if(defaults.value(forKey: dataName) != nil) {
return defaults.value(forKey: dataName)! as! Bool
} else {
return false
}
}
public static func saveStringData(data: String, dataName: String){
let preferences = UserDefaults.standard
preferences.set(data, forKey: dataName)
let didSave = preferences.synchronize()
if !didSave {
debugPrint("Not saved yet")
}
}
public static func getSavedStringData(dataName: String)-> String{
let defaults = UserDefaults.standard
if(defaults.value(forKey: dataName) != nil){
return defaults.value(forKey: dataName) as! String
} else {
return ""
}
}
public static func saveIntData(data : Int, dataName: String){
let preferences = UserDefaults.standard
preferences.set(data, forKey: dataName)
let didSave = preferences.synchronize()
if !didSave {
debugPrint("Not saved yet")
}
}
public static func getSavedIntData(dataName: String) -> Int {
let defaults = UserDefaults.standard
if(defaults.value(forKey: dataName) != nil){
return defaults.value(forKey: dataName) as! Int
}else{
return 0
}
}
}
Or you can try this library: Link
func keyExists(key: String) -> Bool {
guard let _ = UserDefaults.standard.object(forKey: key) {
return false;
}
return true;
}
override func viewDidLoad()
{
super.viewDidLoad()
if UserDefaults.standard.string(forKey: "CHARRY") == "CHARRY"{
lb.text = "CHARRY"
im.image = UIImage(named: "CHARRY")
}
}
#IBAction func PressedCar(_ sender: UIButton){
lb.text = "CHARRY"
im.image = UIImage(named: "CHARRY")
UserDefaults.standard.set("CAR", forKey: "CHARRY")
}