I'm currently developing an application for IOS that has 2 tabs. A home view, which consists of a table of images with actions when you click on them, and a favourites view which allows users to view their favourite images and actions. I'm currently trying to figure out a way to save this favourites data once the application has been closed. It seems to simple for core data, yet I can't seem to get my head around UserDefaults.
I have a favourite button inside of a view that appears when the user clicks the favourite button, it adds the image title to a list and reloads the table using that list inside of the favourites tab. This is working fine with a normal array variable. When I try implementing UserDefaults, it doesn't seem to reload the table when I click on the favourites tab, however when I close the app and restart it using multitasking (swipe up) and restart the app the table forms and all the data is remembered and set up. Is there any way for the data to be remembered and stored in the UserDefaults variable when the app closes and when the app restarts use that variable, and while the app is running use the normal variable to reload the table from?
Here's my favourites tab code for the table:
import UIKit
var favRow = 0
class FavouritesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let favouritesavedimages = userDefaults.object(forKey: "Data") as? [String] ?? [String]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
tableView.reloadData()
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return favouritesavedimages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell2") as? FavouritesImageCellTableViewCell {
cell.configureCellFavourites(image: UIImage(named: favouritesavedimages[indexPath.row])!)
return cell
} else {
return FavouritesImageCellTableViewCell()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
favRow = indexPath.row
}
override func viewWillAppear(_ animated: Bool) {
let favouritesavedimages = userDefaults.object(forKey: "Data") as? [String] ?? [String]()
super.viewWillAppear(animated)
tableView.reloadData()
}
}
Here's my code for the page with the favourite button:
import UIKit
let userDefaults = UserDefaults.standard
var favouriteImages: [String] = []
var isFavourite = [false,false,false,false,false,false,false,false,false,false,false,false,false,f alse,false,false,false,false,false,false,false,false,false,false,false]
class SecondViewController: UIViewController {
let favourites = userDefaults.object(forKey: "FavData") as? [Bool] ?? [Bool]()
override func viewDidLoad() {
super.viewDidLoad()
if !isFavourite[row]
{
favouriteButton.image = UIImage(named: "favourite")
} else {
favouriteButton.image = UIImage(named: "defavourite")
}
secondImageView.image = secondImages[row]
secondTitle.text = tutorialTitles[row]
secondTutorialText.text = tutorialText[row]
}
#IBAction func favouriteButtonTapped(_ sender: Any) {
if !isFavourite[row] {
setOnFavourite()
} else {
setOnDeFavourite()
}
}
#IBOutlet weak var favouriteButton: UIBarButtonItem!
#IBOutlet weak var secondTutorialText: UITextView!
#IBOutlet weak var secondTitle: UILabel!
#IBOutlet weak var secondImageView: UIImageView!
func setOnFavourite()
{
isFavourite[row] = true
favouriteButton.image = UIImage(named: "defavourite")
favouriteImages.append(String(row + 1))
userDefaults.set(favouriteImages, forKey: "Data")
userDefaults.set(isFavourite, forKey: "FavData")
}
func setOnDeFavourite()
{
isFavourite[row] = false
favouriteButton.image = UIImage(named: "favourite")
favouriteImages = favouriteImages.filter{$0 != String(row)}
userDefaults.set(favouriteImages, forKey: "Data")
userDefaults.set(isFavourite, forKey: "FavData")
}
}
Thanks, any help would be appreciated.
Related
I am having trouble debugging why my UITableview cell data isn't showing up in the UITableview. The UITableview currently displays blank when the user navigates to it. Data is correctly going into the cellForRowAt and into the function that sets the cell data.
Setting the cell data
class EventInboxTableViewCell: UITableViewCell {
#IBOutlet weak var eventNameLabel: UILabel!
#IBOutlet weak var eventCoverImageView: UIImageView!
#IBOutlet weak var eventStartLabel: UILabel!
#IBOutlet weak var eventEndLabel: UILabel!
var eventStartString = String()
var eventEndString = String()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setEvent(_ event:Event) {
eventNameLabel?.text = event.eventName
if event.eventStart != nil {
let eventStartTS = event.eventStart
let eventStartDate = eventStartTS?.dateValue()
self.eventStartString = AppWideService.dateToStringShort(date: eventStartDate!)
}
if event.eventEnd != nil {
let eventEndTS = event.eventEnd
let eventEndDate = eventEndTS?.dateValue()
self.eventEndString = AppWideService.dateToStringShort(date: eventEndDate!)
}
print("Event inbox event \(eventStartString)")
print("Event inbox event \(eventEndString)")
eventStartLabel?.text = self.eventStartString
eventEndLabel?.text = self.eventEndString
guard let urlString = event.eventCoverUrl as? String else { return }
let url = URL(string: urlString)
guard url != nil else {
//Couldn't create url object
return
}
eventCoverImageView?.sd_setImage(with: url) { (image, error, cacheType, url) in
self.eventCoverImageView?.image = image
}}}
For some reason when I remove the ? from setting the label text it says the values like eventName or eventStartString etc are nil, but I have print statements that ensure they are not.
UITableView Datasource
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return retrievedEvents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventInboxTableViewCell", for: indexPath) as! EventInboxTableViewCell
let event = retrievedEvents[indexPath.row]
cell.setEvent(event)
return cell
}
Registered the cell in viewDidLoad
tableView.register(EventInboxTableViewCell.self, forCellReuseIdentifier: "EventInboxTableViewCell")
The problem is the way the table view controller was being used.
If you design a View Controller (of any type) in Storyboard, and you want to use it, you cannot simply say:
let vc = EventInboxTableViewController()
you have to instantiate it from the storyboard:
if let vc = storyboard?.instantiateViewController(withIdentifier: "EventInboxTableViewController") as? EventInboxTableViewController {
navigationController?.pushViewControllerFromLeft(controller: vc)
}
So, in Storyboard, assign your custom class to your UITableViewController, and make sure to fill in the Storyboard ID field (with the string you are using in code as the Identifier).
Im trying to pass data from one View controller to another using a delegate
right now im struggling to pass data from the CartVC to ModifyVC when pressing the modifyButton in the CartCell. This is modeled similar to a previous question that I asked before(see link below). Im just struggling to pass data to the ModifyVC since I keep getting an error saying Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value and closes out the simulator
the modifybtn passes cell data for the cel that is selected in the CartVC
I dont want to use didSelectRowAt to pass the cell data since im using the modifyBtn in the CartCell to pass the data using the ModifyDelegate
I know that im close to my solution to making this work. Im just getting that one error that is preventing me from passing the data to the ModifyVC
How pass data from button in TableViewCell to View Controller?
class CartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var selectedProduct: Items!
var modifyItems: Cart?
var cart: [Cart] = []
var groupedItems: [String: [Cart]] = [:]
var brands: [String] = []
#IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { //segue code for delegate
if let vc = segue.destination as? ModifyViewController {
vc.modifyItems = self.modifyItems
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return brands.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let brand = brands[section]
return groupedCartItems[brand]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartCell = tableView.dequeueReusableCell(withIdentifier: "CartCell") as! CartCell
let brand = brands[indexPath.section]
let itemsToDisplay = groupedItems[brand]![indexPath.row]
cartCell.configure(withCartItems: itemsToDisplay.cart)
cartCell.modifyDelegate = self
cartCell.modifyItems = self.modifyItems
return cartCell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cartHeader = tableView.dequeueReusableCell(withIdentifier: "CartHeader") as! CartHeader
let headerTitle = brands[section]
cartHeader.brandName.text = "Brand: \(headerTitle)"
return cartHeader
}
}
class ModifyViewController: UIViewController {
private var counterValue = 1
var lastSelectedWeightButton = RoundButton()
var modifyItems: Cart!
#IBOutlet weak var price1: UILabel!
#IBOutlet weak var price2: UILabel!
#IBOutlet weak var price3: UILabel!
#IBOutlet weak var weight1: UILabel!
#IBOutlet weak var weight2: UILabel!
#IBOutlet weak var weight3: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.numberStyle = .decimal
price1.text = "$\(formatter.string(for: modifyItems.cart.price1)!)" // getting the error right here that causes the simulator to close out and prevents me from viewing the modify VC
price2.text = "$\(formatter.string(for: modifyItems.cart.price2)!)"
price3.text = "$\(formatter.string(for: modifyItems.cart.price3)!)"
weight1.text = modifyItems.cart.weight1
weight2.text = modifyItems.cart.weight2
weight3.text = modifyItems.cart.weight3
}
}
side note: The CartVC cells data is populated from the HomeVc when an item is selected it posted as a cell in the CartVC. the Items class populates the cells in the HomeVC.
Update following line in cellForRowAt function:
cartCell.modifyItems = self.modifyItems
to this:
cartCell.modifyItems = itemsToDisplay
I have a tableview inside a viewcontroller. When pressing a button in the navigation bar I would like the table view to reload.
The view controller is called FirstViewController, the Tableview is called listTableView and the refresh button is called refreshButton
In the code below I have linked the refreshButton action but cannot seem to figure out which function to put inside it to trigger the refresh when pressed.
The following is my code:
import UIKit
class FirstViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FeedModelProtocol {
var feedItems: NSArray = NSArray()
var selectedStock : StockModel = StockModel()
let tableView = UITableView()
#IBOutlet weak var listTableView: UITableView!
#IBOutlet weak var refreshButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
//set delegates and initialize FeedModel
self.listTableView.delegate = self
self.listTableView.dataSource = self
let feedModel = FeedModel()
feedModel.delegate = self
feedModel.downloadItems()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
#IBAction func reloadData(_ sender: Any) {
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "stockCell"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
myCell.textLabel?.textAlignment = .center
// Get the stock to be shown
let item: StockModel = feedItems[indexPath.row] as! StockModel
// Configure our cell title made up of name and price
let titleStr = [item.customer].compactMap { $0 }.joined(separator: "-")
print(titleStr)
// Get references to labels of cell
myCell.textLabel!.text = titleStr
return myCell
}
}
Use yourUITableView.reloadData() for this. Check Apple's Developer page for more info.
I'm doing a project in swift 3.0 and I have two UIViewControllers and one of them has three text fields. In that UIViewController once the data is entered in the textfields and when the save button is pressed the data will be saved to core data. In my core data module, the name of my entity is "Task" and it has got four attributes namely: amount, age, name and isImportant(all are strings except the last one). The code is shown below
import UIKit
class AddTaskViewController: UIViewController {
var isRowTapped :Bool?
#IBOutlet weak var textFiels: UITextField!
#IBOutlet weak var firstTextField: UITextField!
#IBOutlet weak var secondTextField: UITextField!
#IBOutlet weak var isImp: UISwitch!
override func viewDidLoad() {
}
#IBAction func buttonTabbed(_ sender: AnyObject) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context)
task.name = textFiels.text
task.age = firstTextField.text
task.amount = secondTextField.text
task.isImportant = isImp.isOn
(UIApplication.shared.delegate as! AppDelegate).saveContext()
print("Data saved successfully")
navigationController!.popViewController(animated: true)
}
}
Even though three values will be saved to core data, only the attribute called "name" will be taken out from core data and will be printed on cells in the second view controller where i have my table view (it is more like an identity name). What I want to know is since I'm entering three values initially to core data in the initial view controller, once a particular row is selected in the second view controller how do I get all the three values to correspond to that name attribute and re-assign them to my text fields in the initial view controller so that I could edit them and save again. The code of my second view controller is as below (tableviewViewController).
import UIKit
class DispalyViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
var tasks : [Task] = []
var isRowSelected :Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
loadData()
self.tableview.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
tasks = try context.fetch(Task.fetchRequest())
print("fetch sucess")
}catch{
print("fetch failed")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("count is : \(tasks.count)")
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell ()
print("table view loaded")
let task = tasks[indexPath.row]
if task.isImportant{
cell.textLabel?.text = "😌 \(task.name!)"
}else{
cell.textLabel?.text = task.name
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "ShowNxtVC", sender: nil)
//isRowSelected = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let selectedRow = segue.destination as! AddTaskViewController
//selectedRow.isRowTapped = isRowSelected
}
//For swipe access allow
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
let task = tasks [indexPath.row]
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
tasks = try context.fetch(Task.fetchRequest())
}catch{
print("fetchinh failed")
}
}
tableview.reloadData()
}
}
I want the user to save the current date (like dd.mm.yyyy) to the NSUserDefaults by clicking a button and load it back in another ViewController to display it as a cell in UITableView. But it won't work. I don't know how to get the date right but this is what I tried:
#IBAction func saveButtonTapped (sender:AnyObject){
let userDefaults = NSUserDefaults.standardUserDefaults()
if var timeList = userDefaults.objectForKey("timeList") as? [NSDate]
{
timeList.append(NSDate())
userDefaults.setObject(timeList, forKey: "timeList")
}
else
{
userDefaults.setObject([NSDate()], forKey: "timeList")
}
userDefaults.synchronize()
}
...
#IBOutlet weak var tableView: UITableView!
var time:NSMutableArray = NSMutableArray();
override func viewDidLoad() {
super.viewDidLoad()
var timecopy = NSMutableArray(array: time)
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var timeListFromUserDefaults:NSMutableArray? = userDefaults.objectForKey("timeList") as? NSMutableArray
if ((timeListFromUserDefaults) != nil){
time = timeListFromUserDefaults!
}
self.tableView.reloadData()
}
There you go.
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let userDefaults = NSUserDefaults.standardUserDefaults()
let timeListKey = "TimeList" // Key in user defaults
var timeList: [NSDate] = [NSDate]() {
didSet {
userDefaults.setObject(self.timeList, forKey: timeListKey)
self.tableView.reloadData()
}
}
//MARK: Actions
#IBAction func saveButtonTapped(sender: UIButton) {
self.timeList.append(NSDate())
}
//MARK: TableView DataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.timeList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "TimeListCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "\(timeList[indexPath.row])"
return cell
}
//MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Get NSArray from userDefaults
let timeListFromUserDefaults = userDefaults.objectForKey(timeListKey) as? NSArray
// Check if it is an array of NSDate, else set timeList to empty array
self.timeList = (timeListFromUserDefaults as? [NSDate]) ?? [NSDate]()
}
}
Everything you put in NSUserDefaults is saved as immutable. Thus you will get back an NSArray not an NSMutableArray. This may cause your conditional casting to fail and return nil.
By the way, consider that NSUserDefaults is not intended to be some sort of DataBase for your app, but just a small PropertyList of preferenses and stuff like that.
If you are going to have lot of dates to save, delete etc., you should probably consider CoreData or other solutions.