In the detail title bar, show “Picture X of Y” - ios

I'm having issues on 1 out of 3 challenges.
1st challenge
Use Interface Builder to select the text label inside your table view cell and adjust its font size to something larger – experiment and see what looks good. (Done)
2nd challenge
In your main table view, show the image names in sorted order (Done)
The 3rd challenge is
"Rather than show image names in the detail title bar, show “Picture X of Y”, where Y is the total number of images and X is the selected picture’s position in the array. Make sure you count from 1 rather than 0."
Im creating a simple table view row with cells, with images and when you click on it it will show you the picture.
I have an idea but I'm struggling. So I know that the title will use string interpolation and I have already created
var selectedPictureNumber = 0
var totalPictures = 0
Now I have tried creating
var selectedPictureNumber = pictures[indexPath.row]
or
var selectedPictureNumber = indexPath.row
But I get error messages
Heres what I got on mainViewController.swift
class ViewController: UITableViewController {
var pictures = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Adds a title to the navegation bar.
title = "Storm Viewer"
//This makes the title larger as well.
navigationController?.navigationBar.prefersLargeTitles = true
// THis code manages the images from the files saved locally to xcode
let fm = FileManager.default
let path = Bundle.main.resourcePath!
let items = try! fm.contentsOfDirectory(atPath: path)
for item in items {
if item.hasPrefix("nssl"){
pictures.append(item)
}
}
// This pictures.sort makes the arrays sort in order.
pictures.sort()
print(pictures)
}
// This code creates the table view rows , adds the table cells and adds the pictures to each table view cell.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pictures.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "picture", for: indexPath)
cell.textLabel?.text = pictures[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController{
vc.selectedImage = pictures[indexPath.row]
navigationController?.pushViewController(vc, animated: true)
}
}
}
Heres what I have on my detailViewController.swift
class DetailViewController: UIViewController {
// Outlets
#IBOutlet var imageView: UIImageView!
// Vars
var selectedImage : String?
var selectedPictureNumber = 0
var totalPictures = 0
override func viewDidLoad(){
title = selectedImage
super.viewDidLoad()
// THis code makes the title of the navegation bar on the detailViewcontroller the name of the img.
title = selectedImage
// This line will make it so that the title on the navegation bar will not be large.
navigationItem.largeTitleDisplayMode = .never
//This code lets the images that are on the detailViewController
if let imageToLoad = selectedImage{
imageView.image = UIImage(named: imageToLoad)
}
}
// This code will make the Navigation bar appear & Dissapear with a touch on the screen.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.hidesBarsOnTap = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.hidesBarsOnTap = false
}
}

I have got your question, just update your code in mainViewController.swift as follows:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController{
vc.selectedImage = pictures[indexPath.row]
vc.selectedPictureNumber = indexPath.row + 1
vc.totalPictures = pictures.count
navigationController?.pushViewController(vc, animated: true)
}
}
You have to add property of selectedPictureNumber and totalPictures to DetailViewController
var selectedPictureNumber : Int?
var totalPictures : Int?
Then in detailViewController.swift file, Inside viewDidLoad method add following code.
self.title = "Picture \(selectedPictureNumber!) of \(totalPictures!)"
To avoid warnings you need to add "!"
I hope this is solution which are you looking for.

Related

Saving multiple TextViews in a tableView format

Simple app but it's kicking my backside! I have a tableView with 4 notes, when user clicks on one, detailView Screen pops up allowing users to enter text. The challenge is to save each text that the user enters. I used UserDefaults as you'll see but I am not quite getting it right. I want to save each individual note. However, what keeps happening is that anytime I click on the row, and enter text, and save, that text gets saved into all 4 notes. I think my issue is coming from the didSelectRowAt() method but I am not sure. Please take a look and tell me what I need to do to access each individual note's textView in order to save them individually. My notes are contained in an array called notes, inside the main ViewController.swift.
here's my ViewController.swift
import UIKit
class ViewController: UITableViewController {
var notes = ["Note1", "Note2", "Note3", "Note4"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = notes[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController{
vc.selectedNote = notes[indexPath.row]
navigationController?.pushViewController(vc, animated: true)
}
}
}
and here's my detailViewController
import UIKit
class DetailViewController: UIViewController {
#IBOutlet var textView: UITextView!
var selectedNote: String?
var notesTwo = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let otherVC = ViewController()
notesTwo = otherVC.notes
let defaults = UserDefaults.standard
if let noteText = defaults.string(forKey: "noteText"){
textView.text = noteText
}
textView.font = UIFont(name: "Helvetica-Bold", size: 18)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let noteText = textView.text{
let defaults = UserDefaults.standard
defaults.set(noteText, forKey: "noteText")
}
}
}
If you use userdefault to save text, it will override previous data.
Option 1:
You should use core data / sqlite to save notes.
1)Create Entity(Notes) with two attribute createdDate and noteText
Save notes to from detail vc to core data with combination of created/modified date and notesText
Use fetchController to firstVc to update data
Option 2:
Create model with two instance variable and save model directly to user default
And and refresh firstVC ui in viewWillAppear
https://cocoacasts.com/ud-5-how-to-store-a-custom-object-in-user-defaults-in-swift
if you need to do this using user defaults, instead of notes you have to keep notes key that can use as user default key, then you can access them everywhere when you need.
here is the way of your code should be change:
class ViewController: UITableViewController {
var noteKeys = ["Note1", "Note2", "Note3", "Note4"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
//after your detail view controller dismiss time you have to reload your table view data again
self.tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return noteKeys.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let savedText: String = UserDefaults.standard.value(forKey: noteKeys[indexPath.item]) as? String ?? "empty"
cell.textLabel?.text = savedText
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController{
vc.selectedNoteKey = noteKeys[indexPath.row]
navigationController?.pushViewController(vc, animated: true)
}
}
}
and inside DetailViewController code should be change like this:
class DetailViewController: UIViewController {
#IBOutlet var textView: UITextView!
var selectedNoteKey: String!
override func viewDidLoad() {
super.viewDidLoad()
textView.font = UIFont(name: "Helvetica-Bold", size: 18)
let defaults = UserDefaults.standard
if let noteText = defaults.string(forKey: self.selectedNoteKey){
textView.text = noteText
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let noteText = textView.text{
let defaults = UserDefaults.standard
defaults.set(noteText, forKey: self.selectedNoteKey)
defaults.synchronize()
}
}
}

UITableViewCell data not showing up in UITableViewController

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).

How to press on a tableview cell to present a view controller with the text in navigation controller

Essentially I have a view controller called FirstViewController, this view controller contains a table view within it called listTableView.
I would like to tap on one of the cells in the table view listTableView and present whatever text was in the cell as the navigation controller title.
The navigation controller that appears when the cell is tapped is called showDetailsViewController.
How can this be done?
The following is what I have written in the FirstViewController
import UIKit
import AudioToolbox
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) {
print("reload pressed")
listTableView.reloadData()
viewDidLoad()
_ = AudioServicesPlaySystemSound(1519)
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
print("item feed loaded")
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
myCell.textLabel?.font = .boldSystemFont(ofSize: 18)
// 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
}
}
UPDATE:
What is the issue with this code:
NOTE:
The restoration id of the tableview is scheduleTable
var homeworkIdentifierFromTableViewCell = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
homeworkIdentifierFromTableViewCell = feedItems[indexPath.row].myCell
self.performSegue(withIdentifier: "scheduleTable", sender: self)
listTableView.deselectRow(at: indexPath, animated: true)
}
UPDATE 2
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item: StockModel = feedItems[indexPath.row] as! StockModel
let titleStr = [item.customer].compactMap { $0 }.joined(separator: "-")
print(titleStr)
}
You can use the didSelectRowAt to notice what cell was clicked and store what the text in the cell was (homeworkArray is the list of cells from a struct. Homeworkidentifier is a value in the struct).
var homeworkIdentifierFromTableViewCell = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
homeworkIdentifierFromTableViewCell = homeworkArray[indexPath.row].homeworkIdentifier
self.performSegue(withIdentifier: "homeworktoExpandHomework", sender: self)
homeworkTableView.deselectRow(at: indexPath, animated: true)
}
Then, you could use a prepare for a segue function to pass the text of the table view cell to the next view controller. You do this by creating a variable in the other view controller (the one that you are going to pass data to) and later accessing it from the other view controller and changing its value.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "reportBug" {
let destinationViewController = segue.destination as! WebViewController
destinationViewController.reason = "reportBug"
}
else if segue.identifier == "provideFeedback" {
let destinationViewController = segue.destination as! WebViewController
destinationViewController.reason = "provideFeedback"
}
}
Here is more about passing data between viewcontrollers : Passing data between View Controllers in Swift (From TableView to DetailViewController)
Hope this helps
EDIT:
Here is the struct I am using :
struct homeworkTableViewCellData {
let homeworkName : String!
let className : String!
let dateName : String!
let colorImage : UIImage!
let homeworkIdentifier : String!
}
I have initialized my homeworkArray with this struct. When I am calling a value from the cell, I am picking one from in the struct.
To set the table view with a struct is more organized. This is a good video that teaches you how to set it up (if you are want to do that) : https://www.youtube.com/watch?v=zAWO9rldyUE&list=LL--UalPCi7F16WzDFhMEg7w&index=20&t=921s

How to reload a view-controller after data has been fetched from a network request?

I have a problem and can't seem to fix it after looking at tutorials online and other SO questions with a similar problem, which leaves me to think I've done something wrong/bad practice related in my code.
I have 2 table view controllers.
The first TableViewController is populated from a database, all this works fine. When I click one of the cells it segues to a second TableViewController which also should be populated from a database (depending on what you select in the first VC).
Currently if I click a cell in TVC1 it goes to TVC2 and it's empty, then it I click back within my navigation controller and select something else, it goes back to TVC2 and shows me my first selection. This indicates that TVC2 is being loaded before the network has returned its data from the database.... so, I tried using tableView.reloadData() in various places like viewDidLoad and viewDidAppear, but i just can't seem to get it to work.
Below is both TVC's. I've stuck with MVC design pattern and haven't included the model and severConnection code for each TVC because I don't want to over complicate the post, however if you'd like to see either I will update.
Thanks in advance for any help.
TableViewController1
class MenuTypeTableViewController: UITableViewController, MenuTypeServerProtocol {
//Properties
var cellItems: NSArray = NSArray()
var selectedItem = String()
override func viewDidLoad() {
super.viewDidLoad()
let menuTypeServer = MenuTypeServer()
menuTypeServer.delegate = self
menuTypeServer.downloadItems()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "cellType"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
let item: MenuTypeModel = cellItems[indexPath.row] as! MenuTypeModel
myCell.textLabel?.text = item.type
return myCell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = tableView.cellForRow(at: indexPath)
selectedItem = (selectedCell?.textLabel?.text)!
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "typeItems" {
let destinationVC = segue.destination as? TypeItemsTableViewController
destinationVC?.selectedItem = self.selectedItem
}
}
}
TableViewController2:
class TypeItemsTableViewController: UITableViewController, TypeItemsServerProtocol {
//Properties
var cellItems: NSArray = NSArray()
var selectedItem: String = String()
let typeItemsServer = TypeItemsServer()
override func viewDidLoad() {
super.viewDidLoad()
typeItemsServer.delegate = self
self.typeItemsServer.foodType = self.selectedItem
self.typeItemsServer.downloadItems()
self.tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "cellTypeItem"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
let item: TypeItemsModel = cellItems[indexPath.row] as! TypeItemsModel
myCell.textLabel?.text = item.name!
return myCell
}
}
Try adding this to TypeItemsTableViewController
override func viewDidLoad() {
super.viewDidLoad()
cellItems = NSArray()//make sure you have the empty array at the start
typeItemsServer.delegate = self
self.typeItemsServer.foodType = self.selectedItem
self.typeItemsServer.downloadItems()
self.tableView.reloadData()
}
and
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
typeItemsServer.delegate = nil
}
Add this at the top
var cellItems: NSArray = NSArray() {
didSet {
tableview.reloadData()
}
}
Now you can remove other tableview.reloadData() calls since it will automatically be called once cellItems are set...
I think you have a timing problem. You're reloading right after your async data call. You reload but your data isn't in place at that time. Try using functions with escaping or use "didSet" on your data like:
var dataArray: [type] {
didSet {
tableview.reloadData()
}
}

Blank Table View Cell

I am trying to post a picture in a table view cell, but it keeps coming back blank. I have 3 files that I've created for this.
First is a data file with the picture information:
import Foundation
class Data {
class Entry {
let filename : String
let heading : String
init(fname : String, heading : String) {
self.heading = heading
self.filename = fname
}
}
let pic = [
Entry(fname: "Picture1.jpg", heading: "Heading 1"),
]
}
Then I have my table view controller
import UIKit
class TableViewController: UITableViewController {
let data = Data()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.pic.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell
let entry = data.pic[indexPath.row]
let image = UIImage(named: entry.filename)
cell.pic.image = image
return cell
}
}
The third is the cell itself
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet var selfie: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
I have no clue why the cell is coming back blank. I have the image file stored in Supporting Files.
Can anyone help me?
Updated my answer:
Make sure that you registered your UITableViewCell subclass before calling the dequeueReusableCellWithIdentifier:forIndexPath: method. Just add the following line into your viewDidLoad method:
self.tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: "Cell")
You have to assign the image to the UITableViewCell's imageView
Updated Try:
cell.selfie.image = image
instead of cell.pic.image = image
2 possible reason:
First one is, your UITableViewCell contains an image view named selfie but you are setting pic.
So, try cell.selfie.image = UIImage(named: entry.filename);
Second one is, are you sure you are getting the image form UIImage(named: entry.filename); ?
Hope this helps.. :)

Resources