Preserving Images In an IBOutletCollection - ios

Here is my code so far:
#IBOutlet var Boxes: [UIImageView]!
#IBAction func Success(_ sender: Any) {
if "" == defaults.value(forKey: "frequency") as? String{
for box in Boxes{
if box.image == UIImage(named:"blank.png"){
box.image = UIImage(named:"Arm.png")
break
}
}
}
else if "Twice" == defaults.value(forKey: "frequency") as? String{
for box in Boxes{
if box.image == UIImage(named:"Arm1.png"){
box.image = UIImage(named:"Arm.png")
break
}
else if box.image == UIImage(named:"blank.png"){
box.image = UIImage(named:"Arm1.png")
break
}
}
}
}
The defaults.value(forKey: frequency) are used to determine how often the user wants to change the grid. How would I create UserDefaults that would restore the images in the grid on app close? (double tap home and swipe up). Are UserDefaults even the right way to do this? I feel like it would be easier, but way messier to create an individual outlet for each UIImageView and saving the state for each one through a giant tree of code. Thank you!

If your images are stored in the bundle (manually added by you and won't be changed) you can save names to the userdefaults instead of saving images.
Otherwise I would use a database. There is very nice framework Realm. Try it. Also, to store images, it is better to convert them to the Data

I hacked my way through this.
I ended up looping through the list, and if my conditions were not satisfied, I added one to a variable (starting with 1) to index the list. If I changed the condition, I took the variable and added it to the end of the userDefault. Add a comment if you need any help.

Related

Create a favorite button that connects to a favorite tableview in swift

I was wondering how to create a favorite button in swift that when that button is pressed the object that is "favorited" becomes part of a tableview and stays that way. I hear that core data is a way to do this, but I am still struggling to have the button change when pressed.
When I was creating a button all I get is a button that only changes the first time it presses. An example is I have an empty star button, which means that it is not a favorite, and when i pressed it it changes to a filled heart, which means that it is favorited. When I press the button a second time, when it is currently a filled star, it doesn't change and still remains a filled star.
The other problem that I am having is sending the information from the object to a favorite tableview. I am not sure how to keep something in a tableview as long as the favorite button is switched to filled star.
What I am seeing a lot of is this topic but it is all about making a favorite button in table views. I am not looking for tableviews. An example of what I am trying to do is like a favorite book app. The app opens as a tableview full of books, and when a cell is pressed it opens a new view controller with that book's information, and at the top is a favorite button that is normally an empty star. If I like that book I would like to press that button and the empty star becomes a filled star button, indicating that I like it. The book's information is then sent to a new table view that holds all of the liked books i have done before. If I no longer like that book i would like to press the filled star button again and it is removed from the favorite tableview list.
I am some experience with mysql in regards to swift, and I know that someone people have made comments about using some kind of persistence to save the state of the button.
My biggest problem is that I don't even know where to start. Every attempt I make seems to end the same way, so i don't really have any source code for others to see. I have looked online and through github but the closest thing I could find was a cocoapod named DoButton, but it hasn't been updated in a long time and I am worried that it won't last with another swift update. If someone could lead me in the right path or know a good tutorial, I would greatly appreciate it. If there are any questions I can answer i will answer them to the best of my ability.
Another Update: I managed to get the button to work. It connects to core data and saves the state of the button even when it is quit. Now all that is left is creating a favorite tableView to store the favorites.
Here is the code:
class ViewController: UIViewController {
var buttonIsSelected = false
var favorites = [Favorite]()
#IBOutlet var onOffButton: UIButton!
let image1 = UIImage(named: "empty") as UIImage?
let image2 = UIImage(named: "filled") as UIImage?
override func viewDidLoad() {
super.viewDidLoad()
print("buttonIsSelected: \(buttonIsSelected)")
let fetchedRequest: NSFetchRequest<Favorite> = Favorite.fetchRequest()
do {
let favorite = try PersistenceService.context.fetch(fetchedRequest)
for fav in favorite {
resetAllRecord(entity: fav.favorite)
buttonIsSelected = fav.favorite
print("fav.favorite: \(fav.favorite)")
print("button: \(buttonIsSelected)")
if fav.favorite == true {
onOffButton.setImage(image2, for: .normal)
}
}
} catch {
}
}
//button Action
#IBAction func onOffButtonPressed(_ sender: Any) {
buttonIsSelected = !buttonIsSelected
if buttonIsSelected == true {
onOffButton.setImage(image2, for: .normal)
} else if buttonIsSelected == false {
onOffButton.setImage(image1, for: .normal)
}
saveBool(bool: buttonIsSelected)
}
//save to core data
func saveBool(bool: Bool) {
if bool == true {
print("favorite")
print("buttonIsSelected \(buttonIsSelected)")
let liked = Favorite(context: PersistenceService.context)
liked.favorite = bool
PersistenceService.saveContext()
favorites.append(liked)
} else if bool == false {
print("unfavorite")
print("buttonIsSelected \(buttonIsSelected)")
let liked = Favorite(context: PersistenceService.context)
liked.favorite = bool
PersistenceService.saveContext()
favorites.append(liked)
}
}
//clears core data so it doens't get full
func resetAllRecord(entity: Bool) {
let context = PersistenceService.persistentContainer.viewContext
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Favorite")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do
{
try context.execute(deleteRequest)
try context.save()
}
catch
{
print ("There was an error")
}
}
}
In the object class create a bool parameter called isFavorite and default set it to false. When the favorite button is pressed the bool should change from false to true. When you have to access only the "favorite" objects, with a for loop iterate through your array of objects and through an if statement check if the isFavorite parameter is true. If it is, the element must be appended to a new object array maybe called favoriteObjects, and there you have all your favorite objects. The rest is pretty straightforward.

iOS label does not update text even from main thread

I've spent a fun couple of hours trying all sorts of different combinations to have a label properly update its title after a Firebase async download. It's the same issue raised here and here. Seems like a clear fix, but I'm doing something wrong and would appreciate any help pointing me in the right direction.
The basic flow is view loads, data is downloaded from Firebase, some labels are updated accordingly with downloaded data. One representative iteration I have tried is as follows:
// Query Firebase.
let detailsRef = self.ref.child("eventDetails")
detailsRef.queryOrdered(byChild: "UNIQUE_ID_EVENT_NUMBER").queryEqual(toValue: eventID).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("error")
}
else {
var tempDict = [NSDictionary]()
for child in snapshot.children {
let data = child as! FIRDataSnapshot
let dict = data.value as! NSDictionary as! [String:Any]
tempDict.append(dict as NSDictionary)
}
self.dictionaryOfRecoDetails = tempDict
self.ParseFirebaseData()
DispatchQueue.main.async {
// This is the function that updates labels and button text in format like self.websiteLabel.titleLabel?.text = "Appropriate String"
self.loadDataForView()
}
}
})
func loadDataForView() {
// Example of the label update that happens within this function.
// Do not show website button if there is no website.
if self.recommendation.recommendationWebsiteUrl == "" || self.recommendation.recommendationWebsiteUrl == nil || self.recommendation.recommendationWebsiteUrl == "NA" {
self.websiteLabel.titleLabel?.text = ""
self.websiteHeight.constant = 0
self.websiteBottom.constant = 0
}
else {
self.websiteLabel.titleLabel?.text = "Go to Website"
}
}
EDIT UPDATE: The call to the code above is coming from viewDidAppear(). It doesn't update if I call it from viewDidLayoutSubviews() either.
From debugging I know the label update is getting called, but nothing is changing. Feels like something simple I'm missing, but I'm stuck. Thanks for your ideas.
I'm pretty sure you don't need the DispatchQueue.main.async bit. Just try calling self.loadDataFromView() and see if that helps.
This ended up being a lesson in mis-labeling causing confusion. The label being changed actually isn't a label, but a button. Shouldn't have been named websiteLabel! Once the title was changed with self.websiteLabel.setTitle("Go to Website", for: .normal) then everything worked as expected.

How is better to manage data details (Swift)

I want to do a little list of data in TableView, and a DetailView.User tap on cell and goes to DetailView. In DetailView I have to check, what kind of data user selected in TableView. I pass name of data (title of cell) and Index(by prepareForSegue method). I can check name of data by two ways: by index, or by name (by switch). Which Way is better. My code for IndexCheck is something like this:
let Details = ["first","second","third"]
var PassedIndex:Int
override func ViewDidLoad () {
super.ViewDidLoad()
self.DetailLabel.text = Details[PassedIndex]
}
And a check by name is something like this:
var PassedName:String?
var DetailText:String
switch PassedName {
case "NumberOne":
DetailText = "#1"
case "NumberTwo":
DetailText = "#2"
case "NumberThree":
DetailText = "#3"
default:
DetailText = "Unknown number"
}
override func ViewDidLoad () {
super.ViewDidLoad()
self.DetailLabel.text = DetailText
}
Which way is the best? Which makes system working faster? I am just a beginner, so I need an advice from more experienced programmer than I
The best way of your two methods to check Details is check-by-index method. Why? Because it takes O(1) to take value that you need. Check by String takes O(n) in the worst case. But i suggest you to create an Enum that contains your Details and pass exactly detail that you need.

Swift update UILabel with dispatch_async not working

Why doesn't this work?
dispatch_async(dispatch_get_main_queue(), {
self.timeStringLabel.text = "\(self.timeStringSelected)"
println(self.timeStringLabel.text)
})
I'm trying to update a label in Swift but the UI for the label never changes. I keep googling it, but I can't find any responses that don't use dispatch_async. What am I doing wrong?
1st Edit: I was mistaken. I'm not printing the updated text. The text never changes. It always prints out Optional("0") if that helps. The default value is 0 as defined in the Storyboard.
I have tried it with and without dispatch_async without any success. I also tried adding
self.timeStringLabel.setNeedsDisplay()
Immediately after updating the text, but that also doesn't work.
Edit 2: Here's the complete function + UILabel declaration
#IBOutlet weak var timeNumberLabel: UILabel!
#IBAction func timeNumberButtonPressed(sender: UIButton) {
println("Number Selected. Tag \(sender.tag)")
dispatch_async(dispatch_get_main_queue()) {
self.timeNumberOneButton.selected = false
self.timeNumberTwoButton.selected = false
self.timeNumberThreeButton.selected = false
self.timeNumberFourButton.selected = false
if sender.tag == 0{
self.timeNumberSelected = 0
} else if sender.tag == 1 {
self.timeNumberSelected == 5
} else if sender.tag == 2 {
self.timeNumberSelected == 10
} else {
self.timeNumberSelected == 24
}
sender.selected = true
self.timeNumberLabel.text = "\(self.timeNumberSelected)"
self.timeNumberLabel.setNeedsDisplay()
println(self.timeNumberLabel.text)
}
}
The label is clearly visible as shown in this picture. I didn't think it would be this hard to implement, but I was very wrong. I'm willing to bet it's something really simple that I'm missing.
Try adding the line
self.timeStringLabel.setNeedsDisplay()
(after the label change)
Because the code is run asynchronously, the interface-updating methods may miss the change and not display it (especially if a time-consuming bit of code occurs before the label change). Adding this code forces the interface to check for and display any changes it may have missed.
This should be used after any asynchronous task that changes the interface, as the task's running may overlap with the interface methods, resulting in a missed change.
*Thanks to the iOS Development Journal

Problems with storing UISwitch state

I have a setting in my app that I am having issues retaining the state of. Basically, the UISwitch is on by default, however when updating my app to a newer version, it seems to switch off. I figured out that if the user has opened the settings menu in version 1.0, then this isn't an issue, however if they have never changed a setting, or even opened the settings menu, then it's a problem. Therefore, it must be an issue somewhere in viewWillAppear() but I can't figure out what it is. The code I have for storing the switches state seems really overcomplicated.
This is the code I have in my settings menu:
#IBAction func dupOffOnSwitch(sender: AnyObject) {
if dupSwitch.on == true {
autoAdjust = true
println(autoAdjust)
} else {
autoAdjust = false
println(autoAdjust)
}
NSUserDefaults.standardUserDefaults().setBool(autoAdjust, forKey: "autoAdjustSettings")
}
override func viewWillAppear(animated: Bool) {
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
if autoAdjust == true {
dupSwitch.on = true
} else {
dupSwitch.on = false
}
}
if userReturnedAuto == false {
dupSwitch.on = true
themeSwitch.on = false
userReturnedAuto = true
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "userReturnedAuto")
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "autoAdjustSettings")
}
I am declaring the bool 'autoAdjust' in a different view controller as a global variable. In that same view controller in viewWillAppear() I have this code:
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
Can anyone suggest a better way of storing the switches state while also having it on by default when the app is first launched? Or a fix to my current solution.
NSUserDefaults.standardUserDefaults().boolForKey("key") defaults to false. Therefore, as in your case, if the user never sets the key, then it will be false and the switch will default to off.
Instead, rename the key and use it as the negative. So instead, call it autoAdjustSettingsOff which will default to false and the switch will default to on. Don't forget to switch around the true/false settings in your conditional blocks.
Not sure if this is over kill(or just stupid). But why not use CoreData to save all the users settings. Then when the app becomes active(in your appDelegate) you can set the NSUserDefaults from these stored values. Just a thought :)
For shortness I will only make an example for one switch, you can then fill out and add the rest. I will also assume you will be updating the NSUserDefaults when the switch changes state. We will only update/save the core data when the app goes to in-active and load settings once when app becomes active.
Also if anyone sees something I missed or could be done better please let me know and I will update the answer.
First off you need to make sure you import the CodeData libraries into your view controller:
import CoreData
class fooBarViewController: UIViewController {
You need to create and entity in core data: lets assume it is called "Settings"
then once you have created the entity, you need to add some attributes. Press the "+" symbol.
Now add the attribute and set the name to "dupSwitch" and type to "Boolean". Add an attribute for each setting you need to store.
Next: You need to create a new swift file. Type NSObject. name it something like "dataObjects".
Inside you you will use the following code: (first remove all current code - make it blank)
import UIKit
import CoreData
#objc(settingObj)
class settingObj: NSManagedObject {
#NSManaged var dupSwitch:NSNumber //do this for each setting
}
Next you need to go back to your CoreData dataModel and do the following.
1. click on "Default" under the section "Configuration"
2. You will then get a list of all your "entities" select "Settings"
3. click on class and edit the field to "settingObj"
Now that you have you CoreData set up. You can now save/edit/delete data as need be.
For example when you need to load all saved user settings when app goes will become active.
in your AppDelegate.swift file: import the CoreData libraries.
import CoreData
then add this function if it is not already present
func applicationWillEnterForeground(application: UIApplication) {
}
inside "applicationWillEnterForeground" you will load any settings that have been stored in CoreData
var results:[settingObj] = []
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "Settings")
let tapResults = context.executeFetchRequest(req, error: nil)
if let presentResults = tapResults {
if let castedResults = presentResults as? [settingObj] {
for item in castedResults {
let dupSwitch = item.dupSwitch
let settingSwitch = item.whateverElseYouNeedExample
//now based on this we can set the NSDefault
NSUserDefaults.standardUserDefaults().setBool(dupSwitch, forKey: "autoAdjustSettings")
NSUserDefaults.standardUserDefaults().setBool(settingSwitch, forKey: "whateverElseYouNeedExample")
}
}
}
Now when you want to save the users settings. I would would just it when app goes in-active.
First we will check if the settings have already been saved. If so, then we will just perform
an update. If not, we will save a new entry into yuor CoreData Entity.
add the following func to your appDelegate(if not already there):
func applicationDidEnterBackground(application: UIApplication) {
}
EDIT I HAVE TO GO TO WORK WILL COMPLETE REST OF ANWSER WHEN I GET BACK HOME. REALLY SORRY.

Resources