I am trying to create a custom cell for my UITableView but I am having some difficulty.
First off I cannot use the Interface Builder, as I am experiencing a variation on this bug in Xcode. Every time I click on an element in the Interface Builder everything in that view gets a height and width of zero and gets repositioned outside of the view. Besides, I would like to learn how to do this programmatically.
Secondly I am using the Swift language for my project. I have been trying to follow this demonstration, and doing my best to convert the Objective C code over to Swift, but whenever I run into problems I end up being stuck. I presume this is because I am not converting the code over correctly.
Thirdly I found this video but despite being fairly difficult to follow (lots of the code is just copied and pasted without much explanation to what it does or why), it still ends up using the Interface Builder to change various parts.
I have a basic UITableView set up fine. I just want to be able to add a custom cell to that table view.
Can this be done using pure programming, or do I need to use the Interface Builder?
Can anyone point me in the right direction or help me out in creating a custom cell programmatically in Swift?
Many thanks.
In general: Everything is possible in pure programming ;-)
Create a custom class for your tableView cell and there setup all the elements, properties and the visual layout. Implement the required methods init(style,reuseidentifier)
In your custom class for the UITableViewController register the custom cell class using registerClass(forCellReuseIdentifier)
Setup your delegate and datasource for the custom tableViewController
Finally, you create the cells in cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myReuseIdentifier", forIndexPath: indexPath) as MyCustomTableViewCell
// configure the cell using its properties
return cell
}
This should be the basic steps.
If you're looking for more code, here is an example of a custom cell that I created:
// File: vDataEntryCell.swift
import UIKit
class vDataEntryCell: UITableViewCell
{
//-----------------
// MARK: PROPERTIES
//-----------------
//Locals
var textField : UITextField = UITextField()
//-----------------
// MARK: VIEW FUNCTIONS
//-----------------
///------------
//Method: Init with Style
//Purpose:
//Notes: This will NOT get called unless you call "registerClass, forCellReuseIdentifier" on your tableview
///------------
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
//First Call Super
super.init(style: style, reuseIdentifier: reuseIdentifier)
//Initialize Text Field
self.textField = UITextField(frame: CGRect(x: 119.00, y: 9, width: 216.00, height: 31.00));
//Add TextField to SubView
self.addSubview(self.textField)
}
///------------
//Method: Init with Coder
//Purpose:
//Notes: This function is apparently required; gets called by default if you don't call "registerClass, forCellReuseIdentifier" on your tableview
///------------
required init(coder aDecoder: NSCoder)
{
//Just Call Super
super.init(coder: aDecoder)
}
}
Then in my UITableViewController class I did the following:
// File: vcESDEnterCityState.swift
import UIKit
class vcESDEnterCityState: UITableViewController
{
//-----------------
// MARK: VC FUNCTIONS
//-----------------
///------------
//Method: View Will Appear
//Purpose:
//Notes:
///------------
override func viewWillAppear(animated: Bool)
{
//First Call Super
super.viewWillAppear(animated)
//Register the Custom DataCell
tvCityStateForm.registerClass(vDataEntryCell.classForCoder(), forCellReuseIdentifier: "cell")
}
//-----------------
// MARK: UITABLEVIEW DELEGATES
//-----------------
///------------
//Method: Cell for Row at Index Path of TableView
//Purpose:
//Notes:
///------------
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
//Get Reference to Cell
var cell : vDataEntryCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as vDataEntryCell
//...Do Stuff
//Return Cell
return cell
}
}
Related
When we are creating customView, we set the view File's owner to custom class and we instantiate it with initWithFrame or initWithCode.
When we are creating customUITableViewCell, we set the view's class to custom class, instead File's owner's. And then register all the nibs so on.
İn this way, we always need to register the xibs to UIViewController and
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)so on.
What I find is that I don't want to register nibs all the time where I want to use customUITableViewCell. So I want to initialize xib inside my customUITableCell like the same process of creating customUIView. And I succeed. Here are the steps.
My question is what is the preferred way of creating customUITableCell?
With this method there is no need to register nibs and we can call customCell where we want to without loading/registering nib.
Set the view's File's Owner of xib to customUITableCell class. Not the view's class set to customClass, just File's Owner.
Image 1
My custom class called myView: UITableViewCell
import UIKit
class myView: UITableViewCell {
var subView: UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSubviews()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initSubviews()
}
func initSubviews(){
subView = Bundle.main.loadNibNamed("TableViewCell", owner: self, options: nil)?.first as! UIView
subView.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue)))
self.addSubview(subView)
}
}
İnside UIVivController, I did't register nibs and use
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
Instead, I did this.
let cell = myView(style: .default , reuseIdentifier: "TableViewCell")
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableStyle: UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableStyle.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
tableStyle.delegate = self
tableStyle.dataSource = self
view.addSubview(tableStyle)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.00
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myView(style: .default , reuseIdentifier: "TableViewCell")
return cell
}
}
Here is the result.
Image 4
THANKS FOR YOUR TIME!!!
Your approach means every single time the UITableView requests a new cell, you're creating a brand new cell from scratch. That it means it has to:
find the nib
load the nib
parse it to find the views
make the views
update the cell
This is no better than having a long scroll view with custom views for it's entire length.
The beauty of UITableView is it optimizes much of this process and re-uses cells, massively cutting down the performance cost of having more cells than fit on your screen. With the traditional (correct) approach, steps 1-4 only have to happen once.
To expand on the differences in the xib:
When creating a cell with UITableView, you only give it the nib, and the system looks in the nib to find a UITableViewCell. A simple UIView will not work.
You actually can subclass the UIView in your xib with your custom class. It just happens that the norm is to use fileOwner, largely because that's the norm when using nibs with UIViewControllers as was required in the pre-storyboard era
An addition to the accepted answer:
If your only problem with the "classic" approach is that you need to register the nib and call dequeueReusableCell, you can simplify the calls with a nice protocol extension as discussed in this article:
protocol ReuseIdentifying {
static var reuseIdentifier: String { get }
}
extension ReuseIdentifying {
static var reuseIdentifier: String {
return String(describing: Self.self)
}
}
extension UITableViewCell: ReuseIdentifying {}
To register you just call
self.tableView.register(UINib(nibName: MyTableViewCell.reuseIdentifier, bundle: nil), forCellReuseIdentifier: MyTableViewCell. reuseIdentifier)
And to create it you call
let cell = self.tableView.dequeueReusableCell(withIdentifier: MyTableViewCell. reuseIdentifier, for: indexPath) as! MyTableViewCell
(Of course this only works if class, xib and reuse identifier all have the same name)
i'm trying to implement the following design with MapBox and a TableView.
I've been thinking about it and I wanted to use a UITableView for the results, but as far as I know, it's only possible to have data and detail on the left & right side. Is there an alternative to UITableView ?
If not, i'm also facing the problem that my "root"-View is a MapView (from MapBox) and that I can't use the MapViewController as UITableViewController nor as UITableViewDelegate/UITableViewDataSource. Is it possible to Embed the MapView in another View ?
If you need any more information, just let me know. And thank you in advance.
but as far as I know, it's only possible to have data and detail on the left & right side. Is there an alternative
You know wrong. You can include any interface you want in a table view cell. Just make this a custom cell and design it as desired.
Assuming that you have a UIViewController in your storyboard or xib called ViewController which has both a UITableView and an MKMapView (or whatever you are using) correctly connected to the two outlets in the code below:
import UIKit
import MapKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Now tell the system that we are going to reference our
// hand-made table cell from a xib called "MyCell"
self.tableView.register(UINib(nibName: "MyCell", bundle: nil), forCellReuseIdentifier: "MyCell")
// These next you can do here, or in IB...
self.tableView.dataSource = self
self.tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: - TableViewDataSource
// ANY ViewController can do this, if we register the class as conforming
// to the `UITableViewDataSource` protocol
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get a reference to an instance of our very own UITableViewCell subclass,
// Which we registered in `viewDidLoad`
let c = self.tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
// Whatever controls/outlets we have put in our cell subclass,
// we need to populate with data now... (I just did a label)
c.cellNumber?.text = "\(indexPath.row)"
return c
}
//MARK: - TableViewDelegate
//... implement whatever funcs you need ...
//MARK: - MKMapViewDelegate
//... implement whatever funcs you need ...
}
You then need to create the following code, AND a stand-alone xib (called in this case "MyCell.xib"). The xib should contain all of the controls you want in your table cell. In my example it has only one control, the UILabel referenced as cellNumber.
To make the xib, choose "File->New File->User Interface->Empty" from the Xcode menu, then drop a UITableViewCell into the xib from the palette. Make sure you change the class of the cell from UITableViewCell to MyCell. Add whatever controls (and constraints between them) that you need. Obviously, connect all of your controls to relevant #IBOutlets in this class.
class MyCell: UITableViewCell {
// Create `#IBOutlet weak var ...` for all of the controls in your cell here
#IBOutlet weak var cellNumber: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.configureCell()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.configureCell()
}
func configureCell() {
// Your stuff to load up the IBOutlet controls of your cell with defaults.
// You will be able to override these when the instantiated cell is passed to
// `tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell`
// in the `UITableViewDataSource`
}
}
I have been stuck for hours on this issue. I have created a custom cell inside a tableview which is inside a UIVC. The custom cell has it's own class file and is all linked up as well as having the buttons inside it connected to the UIVC via delegates.
Inside the custom cell there consists of a UITextView as well as some buttons.
Tapping the tableview adds a new custom cell. The user can type whatever their beautiful heart desires, eventually resigning the first responder of the textview. Here is my issue(s).
MAIN. First off. Overtime, the cells start mixing up the text that was typed in by the user, and starts reusing it. It becomes a mess.
Due to the fact I have a custom cell, I do not need to register the class in the viewDidLoad of the UIVC. A stack overflow answer stated.
Optional. Second. Eventually, after a certain point the keyboard blocks the cell view. I have no idea how to scroll the cell to the top of the tableview.
Optional. Third. This is just a bonus. How would one be able to save the data from a cell with a decent storage method. I heard NSUserDefaults is only good for small memory storage files. Basically. Transferring the data in the textview inside the custom cell, into another area (UIVC?) where the data can be saved. Most likely using an array and saving the data on that array.
Code. As requested.
UIVC
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let reminderCell = reminder_tableView.dequeueReusableCellWithIdentifier("cellReminder", forIndexPath:indexPath) as! addReminderCell
reminderCell.selectionStyle = .None
reminderCell.heightForReminderTextDelegate = self
reminderCell.delegate = self
reminderCell.reminder_textview.becomeFirstResponder()
return reminderCell
}
Custom Cell Code. Not all of it though.
import UIKit
import AVFoundation
//MARK: Height For Reminder Delegate
protocol HeightForReminderTextView
{
func heightOfTextView(height: CGFloat)
}
//MARK: Beginning of Class
class addReminderCell: UITableViewCell, UITextViewDelegate {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: nil)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//MARK: Delegate Declarations
var heightForReminderTextDelegate :HeightForReminderTextView?
//MARK: Element IBOutlets
#IBOutlet var reminder_textview: UITextView!
#IBOutlet var reminder_cell_uiview: UIView!
//MARK: Button
#IBAction func non_priority_button(sender: AnyObject) {
println("pressed")
}
//MARK: Awake From Nib Starts Here
override func awakeFromNib() {
super.awakeFromNib()
reminder_textview.delegate = self
}
MAIN: When you dequeue a cell it means that cell may be reused it does not create a new cell everytime. For that UITableViewCell has a method named prepareForReuse() which you can override, where you can reset all your cell content. Ex:
override func prepareForReuse() {
myTextView.text = ""
}
Due to the fact I have a custom cell...
If you have only *.swift file for your custom cell you must registerClass(:forCellReuseIdentifier:)
If you also have *.xib file you should registerNib(:forCellReuseIdentifier:) in the viewDidLoad
Optional
You can use scrollToRowAtIndexPath(_:atScrollPosition:animated:) to scroll desired cell to top of tableView
Optional
You can use CoreData, JSON, some SQLite wrappers to save data. In NSUserDefaults you can use small amount of data, like some settings.
I recommend to use NSJSONSerialization for now. You can create NSData from a Dictionary and write data as *.json to Documents directory using NSFileManager. In your app you should keep an array or dictionary with all the data and pass to another view controller that data.
when user types text, you should save it to some array or dictionary accoding to your requirment and that would helpyou with resusing problem.
Now, second problem of yours is scrolling I answered this question hope this will help. this is in objective C although.
and third for transferring data from OneVc to another you can use Segues
Hope this helps
I'm creating a productivity app in Swift. I'm not using a prototype cell in the Storyboard as most of it has been written in code already. I'd like to a checkbox button.
How would I go about doing that?
While the answer from Tim is technically correct, I would not advise on doing this. Because the UITableView uses a dequeuing mechanism, you could actually receive a reused cell which already has a button on it (because you added it earlier). So your code is actually adding a 2nd button to it (and a 3rd, 4th, etc).
What you want to do, is create a subclass from the UITableViewCell which adds a button to itself, while it is being instantiated. Then you can just dequeue that cell from your UITableView and it will automatically have your button on it, without the need to do it in the cellForRowAtIndexPath method.
Something like this:
class MyCustomCellWithButton: UITableViewCell {
var clickButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton;
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier);
self.contentView.addSubview(self.clickButton);
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
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
}
}
And then you can actually dequeue it in the cellForRowAtIndexPath like this.
var cell = tableView.dequeueReusableCellWithIdentifier("my-cell-identifier") as? MyCustomCellWithButton;
if (cell == nil) {
cell = MyCustomCellWithButton(style: UITableViewCellStyle.Default, reuseIdentifier: "my-cell-identifier");
}
return cell!;
Well, first of all your cellForRowAtIndexPath should probably use the dequeue mechanism so you aren't recreating cells every time when virtualizing.
But that aside, all you need to do is create the button, and add it as a sub view to the cell.
cell.addSubview(newButton)
But of course then you will have to manage the sizing and layout as appropriate.
A UITableViewCell also has a selected state and a didSelect and didDeselect method available that listens to taps on the whole cell. Perhaps that's a bit more practical since you seem to want to check/uncheck checkboxes, which is more or less the same as selecting. You could set the cell in selected state right after you dequeued it.
I'm trying to use a custom TableViewCell Class in my programmatically created UITableView and i can't figure out why this don't get to work. I've used custom TableViewCell Classes a few times without any problems (set the Classname in Storyboard) but here i have no Storyboard to set the Class which should be used to generate the Cells.
I'm doing this:
override func viewDidLoad() {
...
self.popupTableView.registerClass(CustomTableViewCellPopup.self, forCellReuseIdentifier: "popupTableViewCell")
...
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("popupTableViewCell") as CustomTableViewCellPopup
...
return cell
}
class CustomTableViewCellPopup: UITableViewCell {
var message: UILabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init(coder decoder: NSCoder) {
super.init(coder: decoder)
}
override func awakeFromNib() {
super.awakeFromNib()
println("I'm running!")
self.message.frame = CGRectMake(0, 0, 10, 10);
self.message.backgroundColor = UIColor.brownColor()
self.addSubview(self.message)
}
}
The println() Output never appears. The TableView gets rendered without the additional UILabel (message). Just like an out of the box UITableViewCell.
The same way (without registerClass()) i'm running a TableView with custom Cells from the Storyboard (the Classname directly defined in the Storyboard Inspector) without any problems.
Have you an idea why the custom Class don't get used?
awakeFromNib won't get called unless the cell is loaded from a xib or storyboard. Move your custom code from awakeFromNib into initWithStyle:reuseIdentifier: because that's the initializer used by the tableView when you call its dequeueReusableCellWithIdentifier: method
You're not seeing the println output because that's in awakeFromNib, which is only called if you cell comes from a storyboard or XIB. Here you're just registering the class with the table, so there's no Nib loading going on. Try making a call to configure the cell from tableView:cellForRowAtIndexPath:, rather than configuring in awakeFromNib.
initWithCoder: won't be called for the same reason - try overriding initWithStyle:reuseIdentifier:.