How can I show my whole array in DetailView - arrays

I am working on a ToDo List application. Currently, the ToDo list shows in Table View as a list and the Detail View shows only the first item, a completed switch and the date completed. I want my Detail View to show all of the items and dates completed, not just the first item.
My app delegate looks like this:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers.first as! UINavigationController
let masterViewController = navigationController.topViewController as! MasterViewController
let detailViewController = splitViewController.viewControllers.last as! DetailViewController
let firstItem = masterViewController.listManager.getFirstItem()
detailViewController.toDoItem = firstItem
return true
}
Now, I've tried to change firstItem to toDoItem to show my whole array but I am getting an error:
Cannot assign value of type '[ToDoItem]' to type 'ToDoItem!'
Also, what to I create in Detail View to accommodate an every changing array?
This is my current Detail View:
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var dateAddedLabel: UILabel!
#IBOutlet weak var completedSwitch: UISwitch!
var toDoItem: ToDoItem! {
didSet(newItem) {
self.refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
refreshUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func refreshUI() {
if(toDoItem == nil) {
return
}
descriptionLabel?.text = toDoItem.itemDescription
completedSwitch?.setOn(toDoItem.completed, animated: true)
dateAddedLabel?.text = toDoItem.getDateAdded()
}

Related

Why does the console print out the Name of my App/Module?

im currently building an app where you can choose a time and save this data. I built a struct with called Session with this time as an property. When the user save the data i want this new session to be appended to an array of sessions. However when i print out the array there is always the name of my XCode project in front of the struct inside they array, kind of like a path. Do you know how i can fix this, or how i can remove the module name in front of the initialized struct ?
Heres a picture of my console:
Heres my code:
struct Session {
let chosenTime: Int
}
class FirstViewController: UIViewController {
#IBOutlet weak var fiveMinButton: UIButton!
#IBOutlet weak var tenMinButton: UIButton!
#IBOutlet weak var saveDataButton: UIButton!
var sessions: [Session] = []
var selectedTime: Int = 0
#IBAction func timeButtonPressed(_ sender: UIButton) {
selectedTime = sender.tag
print(selectedTime)
}
#IBAction func saveDataPressed(_ sender: UIButton) {
let newSession = Session(chosenTime: selectedTime)
sessions.append(newSession)
print(sessions)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}

Change values in dictionary based on which cell selected in TableView

When clicking a cell from the tableView, the cells data (fetched from an array) gets passed on to the 'Detail View Controller' to be displayed in labels. When pressing edit on the Detailview, data gets passed to the 'Edit View Controller' textfields.
When editing those textfields and pressing "Save" I want this data to overwrite the current data in the arrays dictionary based on which cell that was pressed in the tableView.
What would be the best approach to this? Right now data gets passed all the way to the 'EditViewController', but not back to the corresponding dictionary in array when saved.
Main ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // Set up Delegate and Data Source for Table View
#IBOutlet weak var tradeTableView: UITableView!
var tradesList = TradeList()
// Go to detail view of trade when pressing its tableview cell
#IBSegueAction func showDetailView(_ coder: NSCoder) -> DetailViewController? {
guard let indexPath = tradeTableView.indexPathForSelectedRow
else { fatalError("Nothing selected!")}
let trade = tradesList.trades[indexPath.row]
return DetailViewController(coder: coder, trade: trade)
}
override func viewDidLoad() {
super.viewDidLoad()
// Set the table view as the delegate and data source
tradeTableView.dataSource = self
tradeTableView.delegate = self
}
// Delegating functions for Table View
func numberOfSections(in tableView: UITableView) -> Int {
1
}
// Delegating functions for Table View
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
tradesList.trades.count
}
// Delegating functions for Table View
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "\(TradeCell.self)", for: indexPath) as? TradeCell
else { fatalError("Could not create TradeCell")}
let trade = tradesList.trades[indexPath.row]
// Text to display in cells 'ticker' and 'name' label
cell.tickerLabel?.text = trade.ticker
cell.nameLabel?.text = trade.name
return cell
}
}
DetailViewController:
class DetailViewController: UIViewController {
let trade: Trade
#IBOutlet var tickerLabel: UILabel!
#IBOutlet var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Changes labels according to which cell was pressed in ViewController
tickerLabel.text = trade.ticker
nameLabel.text = trade.name
}
// Initializers
required init?(coder: NSCoder) { fatalError("This should never be called!")}
required init?(coder: NSCoder, trade: Trade) {
self.trade = trade
super.init(coder: coder)
}
// Edit button tapped
#IBAction func editTapped(_ sender: Any) {
self.performSegue(withIdentifier: "DetailVCToEditVC", sender: self)
}
// Prepare data to pass to 'EditViewController'
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "DetailVCToEditVC"){
let displayVC = segue.destination as! EditViewController
displayVC.editTitle = tickerLabel.text
displayVC.editPrice = nameLabel.text
}
}
}
EditViewController:
class EditViewController: UIViewController {
// Variables recieving passed data from 'DetailViewController'
var editTitle: String?
var editPrice: String?
#IBOutlet weak var editTitleField: UITextField!
#IBOutlet weak var editPriceField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Setting the textfields text according to the passed data from 'DetailViewController'.
editTitleField.text = editTitle
editPriceField.text = editPrice
}
#IBAction func editSaveButton(_ sender: UIButton) {
// Dismisses edit screen overlay
self.dismiss(animated: true, completion: nil);
}
}
My array is as follows in another swift file:
struct TradeList {
let trades: [Trade] = [
Trade(ticker: "AAPL", name: "Apple"),
Trade(ticker: "AMD", name: "Advanced Micro Devices")
]
}
Use singleton pattern to update data. You don't need to pass data to view controllers. It will update automatically. Here is how your trade list struct should be
struct TradeList {
static let shared = TradeList(trades: [
Trade(ticker: "AAPL", name: "Apple"),
Trade(ticker: "AMD", name: "Advanced Micro Devices")
])
var trades: [Trade] = []
}
U can use it as following anywhere
for getting values
print(TradeList.shared.trades)
for updating values
TradeList.shared.trades = [...]//Any value here

How to make sure array data isn't reset in a ios quiz app during segue?

Im creating a quiz app to where once you answer a question correctly it segues to another view controller where there is a label that says "correct answer!" and a contains a button that label reads "Continue?". One you press the button it sends you back to the original view controller where the questions are asked. How ever once you segue from the viewcontroller with the "Continue?" button back to the view controller that asks the questions the data is reset and the user is asked questions they have already answered. How could I make it to where the "continue?" button view controller keeps track of the completed questions so they are not asked again in the question view controller? I am new to swift so would I use a delegate or protocol? Or are those things the same? Ive correctly made it segue from the question view controller to the "continue" view controller however I want the data to stay updated once I segue back to the question view controller so no questions that have already been answered are asked again.
Here is my code from the question view controller:
Import UIKit
class ViewController: UIViewController {
var questionList = [String]()
func updateCounter() {
counter -= 1
questionTimer.text = String(counter)
if counter == 0 {
timer.invalidate()
wrongSeg()
counter = 15
}
}
func randomQuestion() {
//random question
if questionList.isEmpty {
questionList = Array(QADictionary.keys)
questionTimer.text = String(counter)
}
let rand = Int(arc4random_uniform(UInt32(questionList.count)))
questionLabel.text = questionList[rand]
//matching answer values to go with question keys
var choices = QADictionary[questionList[rand]]!
questionList.remove(at: rand)
//create button
var button:UIButton = UIButton()
//variables
var x = 1
rightAnswerBox = arc4random_uniform(4)+1
for index in 1...4
{
button = view.viewWithTag(index) as! UIButton
if (index == Int(rightAnswerBox))
{
button.setTitle(choices[0], for: .normal)
}
else {
button.setTitle(choices[x], for: .normal)
x += 1
}
randomImage()
}
}
let QADictionary = ["Who is Thor's brother?" : ["Atum", "Loki", "Red Norvell", "Kevin Masterson"], "What is the name of Thor's hammer?" : ["Mjolinr", "Uru", "Stormbreaker", "Thundara"], "Who is the father of Thor?" : ["Odin", "Sif", "Heimdall", "Balder"]]
//wrong view segue
func wrongSeg() {
performSegue(withIdentifier: "wrongViewSegue", sender: self)
}
//proceed screen
func continueSeg() {
performSegue(withIdentifier: "continueSeg", sender: self)
}
//variables
var rightAnswerBox:UInt32 = 0
var index = 0
//Question Label
#IBOutlet weak var questionLabel: UILabel!
//Answer Button
#IBAction func buttonAction(_ sender: AnyObject) {
if (sender.tag == Int(rightAnswerBox))
{
counter = 15
questionTimer.text = String(counter)
print ("Correct!")
continueSeg()
}
else if (sender.tag != Int(rightAnswerBox)) {
wrongSeg()
print ("Wrong!")
timer.invalidate()
questionList = []
}
randomQuestion()
}
override func viewDidAppear(_ animated: Bool)
{
randomQuestion()
}
//variables
var counter = 15
var timer = Timer()
#IBOutlet weak var questionTimer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
timer = Timer.scheduledTimer(timeInterval: 1, target:self, selector: #selector(ViewController.updateCounter), userInfo: nil, repeats: true)
}
Here is my code for the "continue" view controller (all it contains right now is a segue back to the question view controller but I want it to keep track of which questions have already been answered by storing the data from the array of questions in the question view controller):
import UIKit
class ContinueView: UIViewController {
//correct answer label
#IBOutlet weak var correctLbl: UILabel!
//background photo
#IBOutlet weak var backgroundImage: UIImageView!
func backToQuiz() {
performSegue(withIdentifier: "continueSeg", sender: self)
}
#IBAction func `continue`(_ sender: Any) {
backToQuiz()
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Move your call to randomQuestion from viewDidAppear to viewDidLoad. The viewDidLoad only called once, but viewDidAppear will always be called every time the controller is shown.
Also take note that Apple says
If a view controller is presented by a view controller inside of a popover, this method is not invoked on the presenting view controller after the presented controller is dismissed.
Take a look on discussion about the differences

How do i refresh a TableView after some data will changed?

I want to change the list of users in only one class, called Network. And i don't understand how to make a TableView update after the userList has changed. I'll show you an example and detailed question in code below.
// Network.swift
class Network {
var userList: [User] = []
// Next functions may change userList array
// For example, the data came from the server, and update the userList with new data
}
// App delegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var network: Network = Network()
..
}
// File TableViewController.swift
class TableViewController: UITableViewController {
…
var userList: [User] = [] // Here I want to have a full copy of the array from Network class
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.userList = appDelegate.network.userList // Just copy an array
// And I want that after each update appDelegate.network.userList I updated the table, how to do it better?
self.tableView.reloadData()
}
}
As #JDM mentioned in comment your architecture is messed.
Try to do this delegation using protocols:
// Network.swift
protocol UsersUpdatedProtocol {
func usersListUpdated(list: [User])
}
class Network {
var userList: [User] = [] {
didSet {
delegate?.usersListUpdated(list: userList)
}
}
var delegate: UsersUpdatedProtocol?
init(delegate d: UsersUpdatedProtocol) {
super.init()
delegate = d
}
}
// File TableViewController.swift
class TableViewController: UITableViewController, UsersUpdatedProtocol {
var userList: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
let _ = Network(delegate: self)
}
func usersListUpdated(list: [User]) {
self.userList = list
self.tableView.reloadData()
}
}
You could use a notification. Whenever the userlist gets updated, post a notification like this:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "UserlistUpdate"), object: nil)
Then, in viewDidLoad add:
NotificationCenter.default.addObserver(self, selector: #selector(TableViewController.reloadData), name: NSNotification.Name(rawValue: "UserlistUpdate"), object: nil)
P.S. regarding your architecture so far, I would make the TableViewController hold a variable for Network rather than hold its own user array. Then, in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let network = Network()
// Access the TableViewController and set its network variable
let tableViewController = window!.rootViewController as! TableViewController
tableViewController.network = network

Permanent Data and Prepare For Segue

I am trying to use Permanent Data and Prepare For Segue together. This is what I have so far in: (View Controller 1)
override func viewDidAppear(_ animated: Bool) {
let itemsObject = UserDefaults.standard.object(forKey: "items")
if let tempItems = itemsObject as? [String] {
items = tempItems
}
in (View Controller 2):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toSecondViewController" {
let itemsObject = UserDefaults.standard.object(forKey: "items")
var items:[String]
if let tempItems = itemsObject as? [String] {
items = tempItems
items.append(textField.text!)
print(items)
} else {
items = [textField.text!]
}
UserDefaults.standard.set(items, forKey: "items")
}
}
I am attempting to add an item to an array on VC2. Then I want to transfer the array to VC1 whilst also storing it permanently. Then every time I shut down and reload the app I can print the array. The error message states "Use of unresolved identifier items". I am using Xcode 8.0 and Swift 3.0.
Ok so because you said you want to persist the data over app starts you will need the User Defaults to store your items. As Dan already suggested you are basically doing it right. You just want to set a variable that was not declared before. But I will show you this in the following code. I will also attach a second approach in which the items are passed to next view controller while performing the segue.
First example: Imagine we have two view controllers like in your example. The first view controller contains a UITextField to do user text input. Whenever we switch from the first view controller to the second view controller with the help of a storyboard segue (e.g. when pressing a button) we take the existing texts from previous segues from the User Defaults and add the current user input and then persist it back to the User Defaults. This happens in the first view controller:
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "toSecondViewController" {
let itemsFromDefaults = UserDefaults.standard.object(forKey: "items")
var items: [String]
if let tempItems = itemsFromDefaults as? [String]
{
items = tempItems
items.append(textField.text!)
}
else
{
items = [textField.text!]
}
print(items)
UserDefaults.standard.set(items, forKey: "items")
}
}
}
This first view controller looks pretty similar to your code but I wanted to add it for completeness.
Then in the second view controller we just grab the items from the user defaults and store it directly in a instance variable of this view controller. With the help of this we can do what we want in other methods within the view controller and process the items further. As I said what you were missing was the instance variable declaration to store the items in.
class ViewController2: UIViewController {
private var items: [String]? // This is only accessible privately
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.items = UserDefaults.standard.object(forKey: "items") as? [String]
}
}
Second Example: You could also declare a internal/public variable in ViewController2 so that you can set it directly from the first view controller in perform segue. Than you wouldn´t need to grab the items from the User Defaults in ViewController2. For that you can access the destination view controller of the segue then cast it to ViewController2 and directly set the items of it.
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "toSecondViewController" {
let itemsFromDefaults = UserDefaults.standard.object(forKey: "items")
var items: [String]
// [...] Do the stuff to get the items and add current input like before [...]
let destinationViewController = segue.destination as! ViewController2
destinationViewController.items = items
}
}
}
class ViewController2: UIViewController {
var items: [String]? // This is accessible from outside now
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(items) // We can now print this because it is set in prepareForSegue
}
}
I really hope I could help you with that and explained it understandable. Feel free to leave a comment if you have any questions.
You forget to write "let" or "var" in viewDidAppear. Try this:
override func viewDidAppear(_ animated: Bool) {
let itemsObject = UserDefaults.standard.object(forKey: "items")
if let tempItems = itemsObject as? [String] {
let items = tempItems
}
}
If you want to use items after if statement, then you must declare variable before if statement:
override func viewDidAppear(_ animated: Bool) {
let itemsObject = UserDefaults.standard.object(forKey: "items")
var items: [String]
if let tempItems = itemsObject as? [String] {
items = tempItems
}
}

Resources