I have 2 ViewControllers, one displays the UI and the 2nd one displays a segmented control used as a settings button. Im using the below code to save the segmented control state:
UserDefaults.standard.set(selectorLabel.selectedSegmentIndex, forKey: "stateSelected")
I then retrieve that usedefault on the viewdidload method:
if let value = UserDefaults.standard.value(forKey: "stateSelected"){
let selectedIndex = value as! Int
selectorLabel.selectedSegmentIndex = selectedIndex
}
So far this works as intended and the state of the segmented controlled is loaded properly each app load.
The segmented control has two text titles - one is "LBs & INs" and the second is "KGs & CMs".
How would I save those two segmented control text titles as UserDefaults and then call them on the first ViewController to set two labels on the viewdidload?
Define a model to represent data you want to store and restore:
struct SegmentedControlState: Codable {
let selectedIndex: Int
let titles: [String]
}
Initialize a model, encode and store it somewhere (like user default):
func saveState(of segmentedControl: UISegmentedControl) {
let state = SegmentedControlState(
selectedIndex: segmentedControl.selectedSegmentIndex,
titles: (0..<segmentedControl.numberOfSegments).map { segmentedControl.titleForSegment(at: $0) ?? ""})
let plist = try! PropertyListEncoder().encode(state)
UserDefaults.standard.set(plist, forKey: "SegmentedControlState")
//UserDefaults.standard.synchronize() //if targeting older iOS
}
for restoring, you should reverse the order like this:
func loadState(on segmentedControl: UISegmentedControl) {
guard let plist = UserDefaults.standard.value(forKey: "SegmentedControlState") as? Data else { return }
let state = try! PropertyListDecoder().decode(SegmentedControlState.self, from: plist)
for element in state.titles.enumerated() {
segmentedControl.setTitle(element.element, forSegmentAt: element.offset)
}
segmentedControl.selectedSegmentIndex = state.selectedIndex
}
usage:
// store `selectorLabel` data
saveState(of: selectorLabel)
// restore `selectorLabel` data
loadState(on: selectorLabel)
Note that it is not a good idea to store data like this to userdefaults at all. If you want to access some data from anywhere in code, you should follow singleton pattern and define your own singleton instance instead of standard userdefault.
Just store the value as a string, instead of an integer index.
UserDefaults.standard.set(selectorLabel.titleForSegment(at: selectorLabel.selectedSegmentIndex), forKey: "stateSelected")
And then to retrieve:
UserDefaults.standard.string(forKey: "stateSelected")
EDIT: #rmaddy is correct above - you should ideally be storing an index value like you're already doing, and then using an array to determine which title the index refers to (cleaner than just using a title as a reference). You could make this array global so you can access from anywhere, if you must.
segmentedControlTitles: [String] = ["LBs & INs", "KGs & CMs"]
And then call by
let index = UserDefaults.standard.integer(forKey: "stateSelected")
let title = segmentedControlTitles[index]
Related
I'm having difficulty finding the use of NSDiffableDataSourceSnapshot reloadItems(_:):
If the item I ask to reload is not equatable to an item that is already present in the data source, I crash with:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempted to reload item identifier that does not exist in the snapshot: ProjectName.ClassName
But if the item is equatable to an item that is already present in the data source, then what's the point of "reloading" it?
You might think the answer to the second point is: well, there might be some other aspect of the item identifier object that is not part of its equatability but does reflect into the cell interface. But what I find is that that's not true; after calling reloadItems, the table view does not reflect the change.
So when I want to change an item, what I end up doing with the snapshot is an insert after the item to be replaced and then a delete of the original item. There is no snapshot replace method, which is what I was hoping reloadItems would turn out to be.
(I did a Stack Overflow search on those terms and found very little — mostly just a couple of questions that puzzled over particular uses of reloadItems, such as How to update a table cell using diffable UITableView. So I'm asking in a more generalized form, what practical use has anyone found for this method?)
Well, there's nothing like having a minimal reproducible example to play with, so here is one.
Make a plain vanilla iOS project with its template ViewController, and add this code to the ViewController.
I'll take it piece by piece. First, we have a struct that will serve as our item identifier. The UUID is the unique part, so equatability and hashability depend upon it alone:
struct UniBool : Hashable {
let uuid : UUID
var bool : Bool
// equatability and hashability agree, only the UUID matters
func hash(into hasher: inout Hasher) {
hasher.combine(uuid)
}
static func ==(lhs:Self, rhs:Self) -> Bool {
lhs.uuid == rhs.uuid
}
}
Next, the (fake) table view and the diffable data source:
let tableView = UITableView(frame: .zero, style: .plain)
var datasource : UITableViewDiffableDataSource<String,UniBool>!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.datasource = UITableViewDiffableDataSource<String,UniBool>(tableView: self.tableView) { tv, ip, isOn in
let cell = tv.dequeueReusableCell(withIdentifier: "cell", for: ip)
return cell
}
var snap = NSDiffableDataSourceSnapshot<String,UniBool>()
snap.appendSections(["Dummy"])
snap.appendItems([UniBool(uuid: UUID(), bool: true)])
self.datasource.apply(snap, animatingDifferences: false)
}
So there is just one UniBool in our diffable data source and its bool is true. So now set up a button to call this action method which tries to toggle the bool value by using reloadItems:
#IBAction func testReload() {
if let unibool = self.datasource.itemIdentifier(for: IndexPath(row: 0, section: 0)) {
var snap = self.datasource.snapshot()
var unibool = unibool
unibool.bool = !unibool.bool
snap.reloadItems([unibool]) // this is the key line I'm trying to test!
print("this object's isOn is", unibool.bool)
print("but looking right at the snapshot, isOn is", snap.itemIdentifiers[0].bool)
delay(0.3) {
self.datasource.apply(snap, animatingDifferences: false)
}
}
}
So here's the thing. I said to reloadItems with an item whose UUID is a match, but whose bool is toggled: "this object's isON is false". But when I ask the snapshot, okay, what have you got? it tells me that its sole item identifier's bool is still true.
And that is what I'm asking about. If the snapshot is not going to pick up the new value of bool, what is reloadItems for in the first place?
Obviously I could just substitute a different UniBool, i.e. one with a different UUID. But then I cannot call reloadItems; we crash because that UniBool is not already in the data. I can work around that by calling insert followed by remove, and that is exactly how I do work around it.
But my question is: so what is reloadItems for, if not for this very thing?
(I've filed a bug on the behavior demonstrated in the question, because I don't think it's good behavior. But, as things stand, I think I can provide a guess as to what the idea is intended to be.)
When you tell a snapshot to reload a certain item, it does not read in the data of the item you supply! It simply looks at the item, as a way of identifying what item, already in the data source, you are asking to reload.
(So, if the item you supply is Equatable to but not 100% identical to the item already in the data source, the "difference" between the item you supply and the item already in the data source will not matter at all; the data source will never be told that anything is different.)
When you then apply that snapshot to the data source, the data source tells the table view to reload the corresponding cell. This results in the data source's cell provider function being called again.
OK, so the data source's cell provider function is called, with the usual three parameters — the table view, the index path, and the data from the data source. But we've just said that the data from the data source has not changed. So what is the point of reloading at all?
The answer is, apparently, that the cell provider function is expected to look elsewhere to get (at least some of) the new data to be displayed in the newly dequeued cell. You are expected to have some sort of "backing store" that the cell provider looks at. For example, you might be maintaining a dictionary where the key is the cell identifier type and the value is the extra information that might be reloaded.
This must be legal, because by definition the cell identifier type is Hashable and can therefore serve as a dictionary key, and moreover the cell identifiers must be unique within the data, or the data source would reject the data (by crashing). And the lookup will be instant, because this is a dictionary.
Here's a complete working example you can just copy and paste right into a project. The table portrays three names along with a star that the user can tap to make star be filled or empty, indicating favorite or not-favorite. The names are stored in the diffable data source, but the favorite status is stored in the external backing store.
extension UIResponder {
func next<T:UIResponder>(ofType: T.Type) -> T? {
let r = self.next
if let r = r as? T ?? r?.next(ofType: T.self) {
return r
} else {
return nil
}
}
}
class TableViewController: UITableViewController {
var backingStore = [String:Bool]()
var datasource : UITableViewDiffableDataSource<String,String>!
override func viewDidLoad() {
super.viewDidLoad()
let cellID = "cell"
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
self.datasource = UITableViewDiffableDataSource<String,String>(tableView:self.tableView) {
tableView, indexPath, name in
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)
var config = cell.defaultContentConfiguration()
config.text = name
cell.contentConfiguration = config
var accImageView = cell.accessoryView as? UIImageView
if accImageView == nil {
let iv = UIImageView()
iv.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(self.starTapped))
iv.addGestureRecognizer(tap)
cell.accessoryView = iv
accImageView = iv
}
let starred = self.backingStore[name, default:false]
accImageView?.image = UIImage(systemName: starred ? "star.fill" : "star")
accImageView?.sizeToFit()
return cell
}
var snap = NSDiffableDataSourceSnapshot<String,String>()
snap.appendSections(["Dummy"])
let names = ["Manny", "Moe", "Jack"]
snap.appendItems(names)
self.datasource.apply(snap, animatingDifferences: false)
names.forEach {
self.backingStore[$0] = false
}
}
#objc func starTapped(_ gr:UIGestureRecognizer) {
guard let cell = gr.view?.next(ofType: UITableViewCell.self) else {return}
guard let ip = self.tableView.indexPath(for: cell) else {return}
guard let name = self.datasource.itemIdentifier(for: ip) else {return}
guard let isFavorite = self.backingStore[name] else {return}
self.backingStore[name] = !isFavorite
var snap = self.datasource.snapshot()
snap.reloadItems([name])
self.datasource.apply(snap, animatingDifferences: false)
}
}
Based on your new example code, I agree, it looks like a bug. When you add a reloadItems to a snapshot it correctly triggers the datasource closure to request an updated cell, but the IdentifierType item that is passed to the closure is the original, not the new value that was provided with the reloadItems call.
If I changed your UniBool struct to a class so that it is a reference rather than a value type, then things worked as expected (since there is now a single instance of a UniBool rather than a new one with the same identifier).
It seems at the moment there are a couple of possible work-arounds:
Use a reference rather than a value type for the IdentifierType
Use an additional backing store, such as an array, and access it via indexPath in the datasource closure.
I don't think that either of these are ideal.
Interestingly, after I changed UniBool to a class, I tried creating a new instance of UniBool that had the same uuid as the existing instance and reloading that; The code crashed with an exception stating Invalid item identifier specified for reload; This doesn't sound right to me; Only the hashValue should matter, not the actual object reference. Both the original and the new objects had the same hashValue and == returned true.
Original answer
reloadItems works, but there are two important points:
You must start with the datasource's current snapshot and call reloadItems on that. You can't create a new snapshot.
You can't rely on the item passed to the CellProvider closure for anything other than the identifier - It doesn't represent the most recent data from your backing model (array).
Point 2 means that you need to use the provided indexPath or item.id to obtain your updated object from your model.
I created a simple example that displays the current time in a table row; This is the data source struct:
struct RowData: Hashable {
var id: UUID = UUID()
var name: String
private let possibleColors: [UIColor] = [.yellow,.orange,.cyan]
var timeStamp = Date()
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
static func ==(lhs: RowData, rhs: RowData) -> Bool {
return lhs.id == rhs.id
}
}
Note that despite the hash function only using the id property it is also necessary to override == or you will get a crash with an invalid identifier when you attempt to reload the row.
Each second a random selection of rows are reloaded. When you run the code you see that the time is updated on those randomly selected rows.
This is the code that uses reloadItems:
self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
guard let datasource = self.tableview.dataSource as? UITableViewDiffableDataSource<Section,RowData> else {
return
}
var snapshot = datasource.snapshot()
var rowIdentifers = Set<RowData>()
for _ in 0...Int.random(in: 1...self.arrItems.count) {
let randomIndex = Int.random(in: 0...self.arrItems.count-1)
self.arrItems[randomIndex].timeStamp = Date()
rowIdentifers.insert(self.arrItems[randomIndex])
}
snapshot.reloadItems(Array(rowIdentifers))
datasource.apply(snapshot)
}
I posted the same question, not realising. I got this working by firstly converting my model to classes. Then calling 'applySnapshot' after calling 'reloadItems'.
func toggleSelectedStateForItem(at indexPath: IndexPath, animate: Bool = true) {
let item = dataSource.itemIdentifier(for: indexPath)!
var snapshot = dataSource.snapshot()
item.isSelected = !item.isSelected
snapshot.reloadItems([item])
dataSource.apply(snapshot)
}
I found (via Swift Senpai) that the way you update these diffabledatasource depends on if your model is a class (pass by reference) or struct (pass by value). In the pass by reference you can take the item, update it, then reload the item:
// Model is a class compliant with Hasable and Equatable, name String property
guard let selectedItem = dataSource.itemIdentifier(for: indexPath) else { return}
// modify item
selectedItem.name = "new name"
// update the snapshot
var newSnapShot = dataSource.snapshot()
newSnapshot.reloadItems([selectedItem])
dataSource.apply(newSnapshot)
So the above code will work with a model that is a class (the class needs to explicitly implement hast(into:) and ==(lhs:rhs:)).
On the other hand, a struct requires you to copy the item, update it, then insert the updated item and delete the old item from the snapshot.
// Model is a struct with name String property
guard let selectedItem = dataSource.itemIdentifier(for: indexPath) else { return}
// update the item
var updatedSelectedItem = selectedItem
updatedSelectedItem.name = "new name"
// update snapshot
var newSnapShot = dataSource.snapshot()
newSnapshot.insertItems([updatedSelectedItem], beforeItem: selectedItem)
newSnapshot.deleteItems([selectedItem])
dataSource.apply(newSnapshot)
These worked for me.
I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.
I have a custom cell for a collectionView and inside I have 3 didSet variables. My problem is that I need to use 1 preference from each didSet to display on a label but I can't figure out a way to do so. Any suggestions?
class VideosCell: UICollectionViewCell {
var playlistDataFromAPI: playListsnippet? {
didSet{
guard let pubDate = playlistDataFromAPI?.snippet.publishedAt else {return}
}
}
var videoSnippet: videoSnippet? {
didSet{
guard let duration = videoSnippet?.contentDetails.duration else {return}
}
}
var channelData: channelSnippet? {
didSet{
guard let channelName = channelData?.snippet.title else {return}
}
}
Now I want to use pubDate, duration, and channelName all together to display on a label such as: label.text = channelName, duration, pubDate
Thank you very much for your help!
In your UIView or UIViewController subclass, I recommend declaring a nested struct called Content that has properties for any values you need to display.
Then have an instance var of type Content, and you can access its properties from anywhere in the class.
Bundling all such properties together has several nice benefits, such as being able to export all the current stuff the user is seeing in one line of code to JSON for logging (if you make it Codable), cleaner testing, etc.
I have an app where I time myself and see how long it takes me to complete a bunch of questions. I have the time transferred to another VC and displayed in a label. I have it being stored by pressing a button but when i have a new variable(time) it replaces it. How do i store an Array of values and that can be displayed in a label?
Button to save the value:
#IBAction func saveScore(_ sender: Any) {
scoreLabel.text = label.text
UserDefaults.standard.set(scoreLabel.text, forKey: "score")
}
The code that permanently holds the data:
override func viewDidAppear(_ animated: Bool) {
if let x = UserDefaults.standard.object(forKey: "score") as? String {
scoreLabel.text = x
}
}
My scoreLabel displays all my scores and label shows the time you just got.
Use the following extentions on UserDefaults to store an array of times:
extension UserDefaults {
var times: [String] {
get {
if let times = UserDefaults.standard.object(forKey: "times") as? [String] {
return times
} else {
return []
}
}
set {
UserDefaults.standard.set(newValue, forKey: "times")
}
}
}
While you don't need to extend UserDefaults, using extension can simplify a bit working with persisted values and it makes the code cleaner.
Then at the point where you show the data, use the following line to access the array:
let arrayOfTimes = UserDefaults.standard.times
scoreLabel.text = "\(arrayOfTimes)" // or any other formatting you'd like
And instead of setting the times to persist a new score, just add the new score to the array, e.g.:
// This will not only add the scoreLabel.text to the array, but also persists it
UserDefaults.standard.times.append(scoreLabel.text)
In Swift 4,
To save an array to User Defaults you would do:
let defaults = UserDefaults.standard
let array = [25, 50]
defaults.set(array, forKey: "Scores")
And to access the array from User Defaults:
let defaults = UserDefaults.standard
let retrievedArray = defaults.array(forKey: "Scores") as? [Int] ?? []
And if you were to display a score of your array in a label then, you would just do:
scoreLabel.text = String(describing: retrievedArray[0])
If you are using integers for your scoring system, I would suggest you
storing your scores as Int in User Defaults.
If you prefer using Strings though, please note that you can use the User Defaults' stringArray(forKey:) method directly, instead of the array(forKey:) method, and therefore, in that case, you wouldn't need to type cast your array:
let someStringArray = defaults.stringArray(forKey: "ArrayOfStrings")
Note: To answer your question, I will consider that you are using Int scores, but feel free to use whichever you prefer.
If you want to store your array to the same key in User Defaults every time you get a new score, you could do it easily like this:
let defaults = UserDefaults.standard
// Your new score:
let newScore = 75
// Get your current scores list from User Defaults:
var currentArray = defaults.array(forKey: "Scores") as? [Int] ?? []
// Append your new score to the current array:
let updatedArray = currentArray.append(newScore)
// And save your updated array to User Defaults:
defaults.set(updatedArray, forKey: "Scores")
// In this example, your User Defaults now contains the updated array [25, 50, 75]
And that's it :).
Please note that there is no need to use an extension for that..
UPDATE: Also, if you want to add something inside your viewDidAppear method, don't forget to add super.viewDidAppear(animated). The same goes for viewDidLoad, etc.
The documentation states:
You can override this method to perform additional tasks associated
with presenting the view. If you override this method, you must call
super at some point in your implementation.
So you would have:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let retrievedArray = defaults.array(forKey: "Scores") as? [Int] {
print(retrievedArray)
// You can access your scores array safely here
}
}
Before I start, just wanted to say I'm very new to app development in general, I've only been at this for a month, so feel free to dumb it down to me as much as possible haha.
Ok, so I'm working on a quote app and so I've created an array that I can access from any view controller. This that will contain "liked" quotes, which is added from another another view.
Here is my global "likedArray". It resides in its own swift file.
import Foundation
struct Globals {
static var likedArray: [String] = ["Touch 'Liked' To Continue..."]
}
Quotes are added to likedArray by this method, from another view controller file.
#IBAction func likedbuttonPressed(sender: AnyObject) {
Globals.likedArray.append(quoteLabel.text)
}
And "liked" quotes are shown in another view via this method
// Like button method to move forward in array
#IBAction func likedButtonTouched(sender: AnyObject) {
self.favouritesLabel.fadeOut(completion: {
(finished: Bool) -> Void in
self.currentlikedArrayIndex++
if self.currentlikedArrayIndex < Globals.likedArray.count {
self.favouritesLabel.text = Globals.likedArray[self.currentlikedArrayIndex]
} else {
self.currentlikedArrayIndex--
}
self.favouritesLabel.fadeIn()
})
}
And this all works fine, except that when I quit the app, all liked quotes are gone.
So, my question is how do I save this data?
If you need any more information about my code, please let me know. Thanks in advance.
The most hassle free way is probably to use NSUserDefaults, can follow this tutorial to find out how exactly
the jist of the tutorial:
//for writing to storage
let defaults = NSUserDefaults.standardUserDefaults()
let array = ["Hello", "World"]
defaults.setObject(array, forKey: "SavedArray")
//for reading
let array = defaults.objectForKey("SavedArray") as? [String] ?? [String]()
So basically, you should make a setter method for your global array so each time the array is set, it will write to the NSUserDefaults, then on app launch it should populate the array with whats in the NSUserDefaults
update: (just did this off the top of my head)
struct Globals {
static var likedArray: [String] = ["Touch 'Liked' To Continue..."]
func addLikedString(likedString: String) {
self.likedArray.append(likedString)
NSUserDefaults.standardUserDefaults().setObject(self.likedArray, forKey: "SavedArray")
}
func getLikedStringAtIndex(index:Int) -> Int {
return self.likedArray[index]
}
}
//inside your app delegate, or somewhere appropriate, to load the array when the app starts
Globals.likedArray = NSUserDefaults.standardUserDefaults().objectForKey("SavedArray") as? [String] ?? [String]()