I am trying to display the contents of an array in table view cells.
I created the arrays (I have one array of images and three arrays of strings).
I managed to display the contents of the arrays without any issues.
Here is my viewController.swift :
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var shopOpen: Bool = false
var openSign: UIImage = UIImage(named: "open")!
var closedSign: UIImage = UIImage(named: "closed")!
var logos = [UIImage(named: "shop1"), UIImage(named: "shop2"), UIImage(named: "shop3")]
var programWorkingDays = ["Luni-Vineri:09:00-20:00", "Luni-Vineri::10:00-21:00", "Luni-Vineri:09:30-19:30"]
var programSambata = ["Sambata:10:00-16:00","Sambata:10:30-13:00" ,"Sambata: 09:00-13:00"]
var programDuminica = ["Duminica:10:00-15:00","Duminica:09:00-14:00","Duminica:10:30-15:00"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell
cell.logo.image = logos[indexPath.row]
cell.programWorkingDays.text = programWorkingDays[indexPath.row]
cell.programSambata.text = programSambata[indexPath.row]
cell.programDuminica.text = programDuminica[indexPath.row]
return cell
}
}
My next goal is to display one image or another based on a true/false value of a variable (shopOpen)
If the variable is true i want to have picture 1, if it's false i want to have picture 2.
The image view is placed in the main.storyboard and the connection is made in the cell class.
The images will be storet in UIImage variables (given the fact that i only have two images for this part, i don't think it makes any sense to put them inside an array)
Where should i write the if statement and how do i refresh the cells to display the correct image ?
First of all, rather than multiple arrays for the parameters use a struct including an boolean open property
struct Shop {
var workingDays : String
var sambata : String
var duminica : String
var logo : UIImage?
var open = false
}
In the ViewController class declare a variable shops as an empty Shop array
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let openSign: UIImage = UIImage(named: "open")!
let closedSign: UIImage = UIImage(named: "closed")!
var shops = [Shop]()
In viewDidLoad populate the shops array with appropriate Shop instances
override func viewDidLoad() {
super.viewDidLoad()
shops.append(Shop(workingDays: "09:00-20:00", sambata: "10:00-16:00", duminica: "10:00-15:00", logo: UIImage(named: "shop1"), open: true))
shops.append(Shop(workingDays: "10:00-21:00", sambata: "10:30-13:00", duminica: "09:00-14:00", logo: UIImage(named: "shop2"), open: false))
shops.append(Shop(workingDays: "09:30-19:30", sambata: "09:00-13:00", duminica: "10:30-15:00", logo: UIImage(named: "shop3"), open: true))
}
In numberOfRowsInSection return the number of shops rather than a hard-coded integer
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shops.count
}
In cellForRowAtIndexPath display the values from the Shop instances. Assuming there is an image view openImage the open or close image is displayed depending on the state of the open property.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell
let shop = shops[indexPath.row]
cell.logo.image = shop.logo
cell.programWorkingDays.text = "Luni-Vineri: \(shop.workingDays)"
cell.programSambata.text = "Sambata: \(shop.sambata)"
cell.programDuminica.text = "Duminica: \(shop.duminica)"
cell.openImage.image = shop.open ? openSign : closedSign
return cell
}
}
Related
I am passing data from one view controller to another. If I print the text to the console it will show. However when I try to add the text to my label on the view controller I get an error about Nil being found when unwrapping the optional.
Here is my code from the initial View Controller
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedLady = DataService.instance.getLadies()[indexPath.row]
performSegue(withIdentifier: "LadyView", sender: selectedLady)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let ladyView = segue.destination as? LadyView {
assert(sender as? Lady != nil)
ladyView.initLadies(selectedLady: sender as! Lady)
//ladyView.selectedLadyName = selectedLady["name"]
}
}
And this is the code that is waiting in the second controller
func initLadies(selectedLady: Lady) {
print(selectedLady.name)
//This is the line that says it's Nil
LadyName.text = selectedLadyName
}
This is my lady struct
struct Lady {
private(set) public var name: String
private(set) public var imageName: String
private(set) public var subTitle: String
private(set) public var body: String
init(name: String, imageName: String, subTitle: String, body: String) {
self.name = name
self.imageName = imageName
self.subTitle = subTitle
self.body = body
}
}
Thank you for your help.
I have looked at the other answer in this post Passing data from table view to view controller and although it's similar his answer seems to be slightly different.
func initLadies(selectedLady: Lady) {
print(selectedLady.name)
LadyName.text = selectedLadyName.name
}
try this #CL Maciel
I want to implement a collection view for images in an array and when I will select the image in collection view cell it should take me to other view controller with an image view in it and should show the enlarged image of the selected on.
What should be the code written for this in did select Item so that the selected image will be displayed in the other view controller?
After you have created a DetailViewController with an imageView (with the frame you prefer) and a variable called for example var selectedImage: String?, in your collectionView delegate function didSelectItemAt you have to instantiate your DetailViewController and before presenting it you can set its selected image var.
Example:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedImage: String = imagesArray[indexPath.row]
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let detailVC = storyboard.instantiateViewController(withIdentifier: "Your identifier") as? DetailViewController
detailVC?.selectedImage = selectedImage
//present or push your detailViewController
}
Then in your DetailViewController:
override func viewDidLoad() {
super.viewDidLoad()
if let image = selectedImage {
myImageView.image = UIImage(named: image)
}
}
I found an error with my code recently, and after doing everything I could think of to trouble shoot it, I was able to figure out what I believe to be happening. In my app, I have it changing the background color if a user selects that cell, this way you can have multiple cells selected. But if the first cell is selected it will deselect (change the background color) of all the other ones. But it turns out I get the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
when I try to change the background color of a cell that isn't visible on the screen. I'm assuming this is because only the visible cells are loaded in memory and all the others will only get loaded when they are visible again.
So How would I manually load a cell that I know the IndexPath for but also know it is outside of the view. Also, is there a way to know if cell at an IndexPath is loaded or not?
Here is my current code.
func selectAnyList(tableView: UITableView) {
let sections = frc!.sections!
let currentSection = sections[0]
let numRows = currentSection.numberOfObjects
for i in 0...(numRows - 1) {
let indexPath = IndexPath(row: i, section: 0)
let cell = tableView.cellForRow(at: indexPath) as! ItemListTVCell //This is where the error happens if the cell is not visible
if (i == 0) {
cell.backgroundColor = UIColor.activeAqua.withAlphaComponent(0.85)
} else {
cell.backgroundColor = UIColor.white.withAlphaComponent(0.85)
}
}
}
EDIT: Adding how cell is loaded
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let list: Ent_Lists = frc!.object(at: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: "itemListCell", for: indexPath) as! ItemListTVCell
if (list == Ent_Lists.anyList) {
self.anyCell = cell
}
cell.listName.text = list.name
cell.listImage.image = list.image!.uiImage
if ((indexPath as NSIndexPath).row != 0 && listSet!.contains(list)) {
numLists += 1
}
return cell
}
Edit2: Added Image of Ent_List in Coredata.
I have created a small sample file, where you can dynamically set the background color, and every time the one you select will have a different bg color.
import UIKit
// This is your core data entity
struct Entity {
let title: String
}
// This is a basic viewModel, holding the selected property and the core data entity
class EntityViewModel {
let entity: Entity
// Set the selected property to false
var selected: Bool = false
init(entity: Entity) {
self.entity = entity
}
}
class TableViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
lazy fileprivate var entityViewModels: [EntityViewModel] = self.makeEntityViewModels(count: 20)
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
}
extension TableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return entityViewModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
// Work with viewModels, instead of the CoreData entities directly
let viewModel = entityViewModels[indexPath.row]
// Set the title of the cell
cell.textLabel?.text = viewModel.entity.title
// Based on the selected property, set the background color
cell.backgroundColor = viewModel.selected ? .black : .yellow
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Reset the selection for all viewModels
entityViewModels.forEach {
$0.selected = false
}
// Get the viewModel at the right indexPath
let viewModel = entityViewModels[indexPath.row]
// Set its selected state to true
viewModel.selected = true
// Reload the tableView -> this will trigger `cellForRowAt`, and your background colors will be up to date
self.tableView.reloadData()
}
}
extension TableViewController {
// This is where you read up from core data
private func makeEntities(count: Int) -> [Entity] {
var entities: [Entity] = []
for index in 0..<count {
entities.append(Entity(title: "\(index) Some title"))
}
return entities
}
// Just wrap the core data objects into viewModels
func makeEntityViewModels(count: Int) -> [EntityViewModel] {
let entities = makeEntities(count: count)
var viewModels: [EntityViewModel] = []
for (index, entity) in entities.enumerated() {
let viewModel = EntityViewModel(entity: entity)
// Intialy, set the first viewModel to selected
if index == 0 {
viewModel.selected = true
}
viewModels.append(viewModel)
}
return viewModels
}
}
I'm working on creating a 'Rate' feature in my app where it pulls the users to rate from an external API. I then create a TableView which adds cells that contain a section name (the name of the User) and a UISlider.
What I'm trying to do is gather the data from the UISliders along with the User ID associated with the User who's section it is so that I can call a POST action to the API to send the rating data too.
Here's the code I have: (For the record, first time with Swift, the comments are me trying to figure out how to do this). Any ideas?
import UIKit
import Alamofire
class RateViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var submitButton: UIButton!
#IBOutlet weak var activeSurveyLabel: UILabel!
private var myTableView: UITableView!
var usersToRate = [User]()
var activeSurveyId: String?
var rateData = [String: Int]()
var sectionIdToUserId = [Int: String]()
var userIdSection = [Int: String]()
override func viewDidLoad() {
super.viewDidLoad()
submitButton.isHidden = true
submitButton.addTarget(self, action: #selector(pressSubmitButton(button:)), for: UIControlEvents.touchUpInside)
activeSurveyLabel.isHidden = false
self.loadUsersToRate()
Alamofire.request(RateRouter.getSurvey()).responseJSON { response in
debugPrint(response)
if let string = response.result.value as? [[String: Any]]{
if string.count == 0 {
return
}
self.submitButton.isHidden = false
self.activeSurveyLabel.isHidden = true
for survey in string {
if let survey = Survey(json: survey) {
self.activeSurveyId = survey._id!
print(survey._id!)
}
}
//if there's a result, show slider bars for each person on the team with rating scales (except for the person logged in)
//have a submit button with a post request for the votes
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = barHeight * CGFloat(self.usersToRate.count * 5)
self.myTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
self.myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
self.myTableView.dataSource = self
self.myTableView.delegate = self
self.view.addSubview(self.myTableView)
} else {
print(response.result.error!)
let label = UILabel()
label.center = self.view.center
label.text = "No Active Surveys"
self.view.addSubview(label)
}
}
// Do any additional setup after loading the view.
}
func loadUsersToRate() {
Alamofire.request(RateRouter.getUsers()).responseJSON { response in
guard let jsonArray = response.result.value as? [[String: Any]] else {
print("didn't get array, yo")
return
}
for item in jsonArray {
if let user = User(json: item) {
self.usersToRate.append(user)
self.rateData.updateValue(5, forKey: user.userId!) //initialize user average data at 5 in case they don't change it
print (self.rateData)
}
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Num: \(indexPath.row)")
print("Value: \(usersToRate[indexPath.row])")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
self.sectionIdToUserId.updateValue(usersToRate[section].userId!, forKey: section)
print(self.sectionIdToUserId)
return "\(usersToRate[section].name!)"
}
func numberOfSections(in tableView: UITableView) -> Int {
return usersToRate.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
let slider = UISlider(frame: CGRect(x: 0, y:0, width: 400, height: 44))
slider.isUserInteractionEnabled = true
slider.minimumValue = 1
slider.maximumValue = 10
slider.value = 5
slider.tag = indexPath.section
slider.addTarget(self, action: #selector(sliderValueChange(sender:)), for: UIControlEvents.valueChanged)
cell.addSubview(slider)
return cell
}
func sliderValueChange(sender: UISlider) {
//get slider value
var currentValue = Int(sender.value)
print(currentValue)
//self.rateData.updateValue(currentValue, forKey: self.sectionIdToUserId.values[sender.])
//get user id for that value
//self.rateData.updateValue(currentValue, rateData[section])
// self.rateData.updateValue(currentValue, forKey: 0)
}
func pressSubmitButton(button: UIButton) {
for user in usersToRate {
// Alamofire.request(RateRouter.vote(voteFor: user.userId!, survey: activeSurveyId, rating:))
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
To answer my own question, what I did was create a different model called 'Votes' that stored the information for each vote.
Then to fill that array I created a tag for the slider in the tableView based on the section ID and used that to update the current value.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
let slider = UISlider(frame: CGRect(x: 0, y:0, width: cell.frame.width , height: 44))
slider.tag = indexPath.section
slider.addTarget(self, action: #selector(sliderValueChange(sender:)), for: UIControlEvents.valueChanged)
cell.addSubview(slider)
return cell
}
func sliderValueChange(sender: UISlider) {
//get slider value
var currentValue = Int(sender.value)
print(currentValue)
voteData[sender.tag].rating = currentValue
}
I have researched this issue and I'm almost certain my code is correct, but clearly there is something wrong. I have two controllers. One has a TableView and the other is a view controller with a label. I want the value of the cell text label in the tableview cell selected by the user to be sent to the second view controller. Pretty simple. Here are my controllers:
import UIKit
import CoreData
class FavoritesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var selectedFavorite = ""
var specifiedFavorite:String!
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var passages = [NSManagedObject]()
//This code creates the number of cells equal the number of items in the object
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return passages.count
}
// The code below displays the passage reference as the cell title
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// This creates the cell for the table. The dequeueReusableCell option ensures the table is scrollable
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
// this creates a variable for the record defined at the index that is numbered by the indexPath.row.
let favorite = passages[indexPath.row]
// We then find the value of the key we want to set for the cell label value
cell.textLabel!.text = favorite.value(forKey: "reference") as? String
return cell
}
// This code detects the cell selected and captures a variable that is passed in the segue
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = tableView.cellForRow(at: indexPath) as UITableViewCell!
selectedFavorite = (selectedCell?.textLabel?.text)!
print("The text in the selected cell is \(selectedFavorite)")
performSegue(withIdentifier: "toFavoritesDetailsViewController", sender: nil)
print("Segue performed")
}
//This code is the segue that passes the variable values to the next view
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toFavoritesDetailsViewController" {
let favoritesDetailsViewController = segue.destination as! FavoritesDetailsViewController
favoritesDetailsViewController.specifiedFavorite = selectedFavorite
print("The variable for selectedFavorite prior to segue is: \(selectedFavorite)")
print("The variable for favoritesDetailsViewController.specifiedFavorite prior to segue is: \(favoritesDetailsViewController.specifiedFavorite)")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//This code loads the Core Data Entity into the view
// this is working as evidenced by the tableview being populated with data
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"Passages")
//
do {
let results = try managedContext.fetch(fetchRequest)
passages = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error)")
}
}
}
Here is the code for my second controller called FavoritesDetailsViewController:
import UIKit
import CoreData
class FavoritesDetailsViewController: UIViewController {
#IBOutlet weak var referenceLabel: UILabel!
#IBOutlet weak var passageText: UITextView!
var specifiedFavorite : String = ""
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
referenceLabel.text? = specifiedFavorite
print("The viewDidLoad")
// SMC LEFT OFF HERE
// Can't get selectedFavorite to load from previous controller
print("The Value of variable specifiedFavorite sent from segue is: \(specifiedFavorite)")
print(specifiedFavorite)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Passages")
request.predicate = NSPredicate(format: "reference = %#", specifiedFavorite)
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
print("fetch request was successful")
if results.count > 0 {
for result in results as! [NSManagedObject] {
// print("We got results!")
if let returnedText = result.value(forKey: "passagetext") as? String {
print("This is the value of returnedText: \(returnedText)")
passageText.text? = returnedText
print("This is the text of the selectedFavorite after segue is: \(passageText)")
}
}
}
} catch {
print("Couldn't fetch results")
}
}
override func viewDidLoad() {
super.viewDidLoad()
print("The viewDidLoad")
print("The Value of variable specifiedFavorite sent from segue is: \(specifiedFavorite)")
print(specifiedFavorite)
}
}
When I run the app, the logs indicate that the value of variable "specifiedFavorite" is set prior to segue.
The variable for selectedFavorite prior to segue is:
The variable for favoritesDetailsViewController.specifiedFavorite prior to segue is:
The viewDidLoad
The Value of variable specifiedFavorite sent from segue is:
The text in the selected cell is John 3:16
The variable for selectedFavorite prior to segue is: John 3:16
The variable for favoritesDetailsViewController.specifiedFavorite prior to segue is: John 3:16
2016-12-10 12:43:54.624 Logos to Mind[4036:770173] <UIView: 0x7ff0bf918010; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x600000421fa0>>'s window is not equal to <Logos_to_Mind.FavoritesDetailsViewController: 0x7ff0bf9047f0>'s view's window!
Segue performed
The FavoritesDetailsViewController viewDidLoad
The Value of variable specifiedFavorite sent from segue is:
fetch request was successful
Note the log message:
The Value of variable specifiedFavorite sent from segue is:
is empty.
This is my problem. I don't see what errors exist in my code that is failing to set that "specifiedFavorite" variable in the FavoritesDetailsViewController. I've hit a brick wall here. Would appreciate some insight.
Your segue is wired from your prototype cell. Don't bother with didSelectRowAt because it is called after prepare(for:sender). Do all of your work in prepare(for:sender:). The sender is the cell that triggered the segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toFavoritesDetailsViewController" {
let favoritesDetailsViewController = segue.destination as! FavoritesDetailsViewController
if let selectedCell = sender as? UITableViewCell {
let selectedFavorite = (selectedCell.textLabel?.text)!
favoritesDetailsViewController.specifiedFavorite = selectedFavorite
print("The variable for selectedFavorite prior to segue is: \(selectedFavorite)")
print("The variable for favoritesDetailsViewController.specifiedFavorite prior to segue is: \(favoritesDetailsViewController.specifiedFavorite)")
}
}
}