How do you make your datasource a string in Swift? - uitableview

I'm working with a UITableView and thus UITableViewDataSource. My dataSource is
theTableView.dataSource = self
but I would like to have the UITableView get its data from a variable called 'phone' which is a String. Doing so currently errors to "String is not identical to UITableViewDataSource".
How do I get the UITableView to take its data from a string variable?
Thank you - hope this ends up being of use to others as well.
(For some background that's immaterial to others looking for the same solution: var 'phone' is a phone number being pulled from the user's contacts via the Contacts framework. I'm trying to get the UITableView to take in new phone numbers each time they're selected).

Well, you need to set the tableView's data source to a class that conforms to the UITableViewDataSource protocol. And then define the tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) function.
Example:
import Foundation
class Example : UITableView, UITableViewDataSource, UITableViewDelegate
{
var dataSource = [ "hello", "how", "are", "you" ]
// MARK: Lifecycle
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.dataSource = self
self.delegate = self
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: Data Source / Delegates
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cellId")
cell.textLabel?.text = self.dataSource[indexPath.row]
return cell
}
}

Related

Why does UITableView cache cells when reloading data?

If you were to call tableView.reloadData() on a table with custom UITableViewCell, then it will cache those cells when you reload data. (It is discussed here: https://github.com/nicklockwood/FXForms/issues/92)
If you continue reloading data, after a while, there could be 30/40 cells that are cached and retained. My question is: why is that?
In particular:
1) What possible usage could there be to retain cells that have since been lost after reloading the table?
2) What could be done to prevent/reduce the memory cache storing the cells?
3) Most importantly, why does this happen? Why would cells be retained when reloadData() is called?
Example
To give you a basic example in code, click on one of the cells, then, when you see the objects in memory, there are more retained cells:
ViewController.Swift
import UIKit
class ViewController: UIViewController {
let tableViewController = QuizTableViewController(style: .grouped)
override func viewDidLoad() {
super.viewDidLoad()
self.setupTableViewController()
}
private func setupTableViewController() {
self.view.addSubview(tableViewController.view)
let topAnchor = self.tableViewController.view.topAnchor.constraint(equalTo: self.view.topAnchor)
let leadingAnchor = self.tableViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor)
let trailingAnchor = self.tableViewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
let bottomAnchor = self.tableViewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
NSLayoutConstraint.activate([topAnchor, leadingAnchor, trailingAnchor, bottomAnchor])
}
}
QuizTableViewController.Swift
import UIKit
class QuizTableViewController: UITableViewController {
override init(style: UITableViewStyle) {
super.init(style: style)
self.view.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = QuizCell(subText: "HELLO WORLD")
cell.textLabel?.text = "THIS IS WHAT IS DISPLAYED"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.reloadData()
}
}
QuizCell.Swift
import UIKit
class QuizCell: UITableViewCell {
weak var subText: UILabel?
init(subText: String) {
self.subText = UILabel()
self.subText?.text = subText
super.init(style: .default, reuseIdentifier: "QuizCell")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("This is called after the cache is full.")
}
}
The problem is that you are not using the proper approach to creating table cells. You are explicitly creating a new instance of QuizCell in the cellForRowAt. What you should be doing is dequeueing a reusable cell using dequeueReusableCell. Then your issue goes away because the table will only keep the minimum number of cells needed.
Please find any one of the countless tutorials on using table views for proper examples.
Like #rmaddy mentioned, the problem is how you are dequeuing the cell in cellForRowAt function. Instead, change it to something similar to:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuizCell", for: indexPath) as! QuizCell
cell.textLabel?.text = "THIS IS WHAT IS DISPLAYED"
return cell
}

Dynamic Table View within Static Table View Cell

I'm fairly new into Swift programming and right now I'm implementing a dynamic table view within a static table view's cell. I know there are plenty of solutions on stackoverflow already but I realised that most of them are in Obj-C which I'm not very familiar with it yet.
Basically, I have a TableView that is set as dynamic in one of the cell of a static table view which is part of the main table view controller. The problem I am having now is there doesn't seem to be a way to implement the data source functions without declaring them for the static table view. I have declared an #IBOutlet for the dynamic table (let's call it dynamicTableView in this scenario).
I have managed to get the override func numberOfSections(in tableView: UITableView) working by returning 1 if the tableView is not dynamicTableView as in the following code:
override func numberOfSections(in tableView: UITableView) -> Int {
if tableView == dynamicTableView {
return data.count
}
else {
return 1
}
}
However, the problem I am having now is implementing the override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath). I have no idea what to be returned if the tableView parameter is not dynamicTableView, but for the static table view.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == dynamicTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
}
}
Thanks!
Edit: What I meant was I can't seem to have a cellForRowAt data source function that does not affect my static table view.
If there is a value in numberForRows then you have to retutn a cell like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == bloggerReviewTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
let cell = tableView.dequeueReusableCell(withIdentifier: "other", for: indexPath) as! OtherTableCell
return cell
}
}
//
but if the return is zero then there is no need for the if statement inside cellForRowAt as it won't be called for the other table
If the static tableview cells are fairly distinct, they can be individually subclassed.
The dynamic tableview/collectionview can be added in required subclass of static tableview cell.
//class for static tableview
let reviewCellId = "reviewCell"
class StaticTableClass: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//register static cell classes
tableView.register(ReviewCell.self, forCellReuseIdentifier: reviewCellId)
//..
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reviewCellId, for: indexPath)
return cell
}
}
Create a separate ReviewCell class which will contain the dynamic UITableView like so.
This way one class will handle methods of only one tableview.
class ReviewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
lazy var dynamicTableView: UITableView = {
let tv = UITableView()
tv.delegate = self
tv.dataSource = self
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(dynamicTableView)
dynamicTableView.register(UITableViewCell.self, forCellReuseIdentifier: "dynamicCellId")
}
// add further tableview methods in here
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
}

Swift / How to populate UITableViewCells

I have a controller with a Table View on it. Have defined my cells in a view and then I bind it to the table view. (Please excuse the terminology). App builds and runs fine but none of the labels defined in the cell show anything. Entire table is blank except for the cell separators. I've followed numerous tutorials and searched SO extensively for reasons, how to create (Create a Table View - Apple docs), settings to check, all with the same results. I've also tried cleaning, restarting Xcode, etc. This is Xcode 8/Swift 3.
The view controller has a table view and I register the cell class like so in the viewDidLoad
self.tableView.registerCellClass(MenuTableViewCell.self)
UITableViewDataSource code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menusItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MenuTableViewCell.identifier, for: indexPath) as! MenuTableViewCell
let item = menusItems[indexPath.row]
cell.setData(item)
return cell
}
menuItems is an array of a struct I have that is created before anything else is done
BaseTableViewCell
open class BaseTableViewCell: UITableViewCell {
class var identifier: String { return String.className(self) }
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
open override func awakeFromNib() {
}
open func setup() {
}
open override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
open class func height() -> CGFloat {
return 48
}
open func setData(_ data: Any?) {
if let menuText = data as? String {
self.textLabel?.text = menuText
}
if let menuImage = data as? UIImage {
self.imageView?.image = menuImage
}
}
}
Table View Cell code - inherits the BaseTableViewCell
class MenuTableViewCell: BaseTableViewCell {
//MARK: Properties
#IBOutlet weak var name: UILabel!
#IBOutlet weak var icon: UIImageView!
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
}
override func setData(_ data: Any?) {
if let data = data as? MenuTableViewCellData {
NSLog("menu name - %#", data.text)
self.icon?.image = data.image
self.name?.text = data.text
}
}
}
All menu names are written to the debug area so the code is being executed.
I don't know if there is hook that is missing or if something else. Please let me know if anything further is also needed. Any help is appreciated.
Screenshots showing IBOutlets connected
Here is the delegate and data source from the controller
extension LeftViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return MenuTableViewCell.height()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let menu = LeftMenu(rawValue: indexPath.row) {
self.changeViewController(menu)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.tableView == scrollView {
}
}
}
extension LeftViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menusItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MenuTableViewCell.identifier, for: indexPath) as! MenuTableViewCell
let item = menusItems[indexPath.row]
cell.setData(item)
return cell
}
}
Thanks everyone for your help. I was able to get it working. I removed the XIB file, created the layout in the view and then wired it up from there (followed here for how to).
Verify that you've connected the IBOutlets for name and icon in MenuTableViewCell. Assuming what you say is true, that the NSLog in setData is called, chances are you haven't connected the IBOutlets for the table view cell subclass.
You may have 0-height rows. I see a height method in there - what is calling it? Do you have an implementation of
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
somewhere?
Are you setting the table view's rowHeight?
Can you check whether name and icon have proper constraints

Swift remove data stored in UITableViewController from UITableViewCell

I have a custom class that extends UIViewController and contains a table view, with an array that stores data that populates the table view. For my custom cells, I have a button that when pressed, should remove that cell from the table view. However, I haven't been able to find a way to remove the data from the array in the table view controller and reload the data from the cell's class. Some of the answers I've seen on similar posts suggest notifications or delegation, but due to the structure of my app (tab controller) and that I already use notifications for another feature, the former is inefficient, and I don't know how to use the latter in this situation.
This is the code for the custom cell class:
class CustomCell: UITableViewCell {
#IBOutlet var label: UILabel!
#IBOutlet var removeButton: UIButton!
#IBAction func remove(sender: AnyObject) {
// don't know what to put here
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
And here is the table view controller class:
class CustomController: UIViewController, UITableViewDataSource {
#IBOutlet var table: UITableView!
var data: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
table.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CustomCell = table.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
// put content in
return cell
}
}
The array data is what I want to access from remove in CustomCell. The closest I've gotten is using self.superview.superview.superview in CustomCell, which returns the view that CustomController controls. However, I have no way to get an instance of CustomController without instantiating a new one. How do I modify a variable in CustomController from the CustomCell class?
In your Custom Cell add this
class CustomCell: UITableViewCell {
var customControllerReference: CustomController?
}
And in your Custom Controller in cellForRowAtIndexPath add this
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CustomCell = table.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
cell.customControllerReference = self
return cell
}

UILabels in Custom UITableViewCell not rendering on screen

Hello and thank you for your help. I have a custom UITableViewCell and the controller of the class is written in Swift. All of the init functions are being called on creation of the cell however on inspection in the debugger all of the lables and the one image view i have added to the content view have a frame of 0.
here is the file containing the tableView:
class SearchesMainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var contentTableView: UITableView?
var tableViewData = SearchesMainTableViewData()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
internal override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
override func viewDidLoad(){
super.viewDidLoad()
self.navigationItem.title = "Search"
var nib = UINib(nibName: "SingleSearchTableViewCell", bundle: nil)
contentTableView?.registerNib(nib, forCellReuseIdentifier: SingleSearchTableViewCellController.reuseID())
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return tableViewData.numberOfSectionsInTableView()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableViewData.numberOfRowsInTableSection(section)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell:SingleSearchTableViewCellController = tableView.dequeueReusableCellWithIdentifier(SingleSearchTableViewCellController.reuseID()) as SingleSearchTableViewCellController
cell.formatCell()
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 54.0
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return tableViewData.headerViewForSection(section)
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return tableViewData.heightForTableViewSectionHeaders()
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return tableViewData.heightForTableViewSectionFooters()
}
}
here is the controller for the cell:
class SingleSearchTableViewCellController : UITableViewCell {
#IBOutlet var dateMonthLable : UILabel?
#IBOutlet var dateDayLable : UILabel?
#IBOutlet var yearLable : UILabel?
#IBOutlet var makeModelLable : UILabel?
var search : Search = Search()
class func reuseID() -> String{
return "SingleSearchTableViewCell"
}
required init(coder aDecoder: NSCoder) {
super.init()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
init(previousSearch s : Search){
search = s
super.init()
}
func formatCell(){
formatDateMonthLable()
formatDateDayLable()
formatYearRangeLable()
formatMakeModelLable()
}
private func formatDateMonthLable(){
}
private func formatDateDayLable(){
}
private func formatYearRangeLable(){
}
private func formatMakeModelLable(){
}
}
and an image of the tableview cell in interface builder with all of the bindings:
Thanks a lot for any sugestions or help, I am new with both Swift and interface builder. Have a nice day :)
Edit
What is actualy built from this code
It seams like every other place I use nibs there is an initWithNib or something along those lines, and there is nothing like that for tableview cells. I think the linking of data and view may be off or missing something.
The problem is your init overrides in the cell class. I'm not that familiar with Swift, so I don't know why having those in there with nothing but calls to super causes your subview not to show. Since you're not doing anything in those methods (or awakeFromNib either), you should delete them. I think that will fix your problem (it did in my tests).

Resources