How to embed a UITableView in a custom view - ios

Goal
I want to create a custom view that has a UITableView as a subview.
The custom view creates the table view programmatically. To the outside world (i.e., the ViewController), though, the custom view itself would appear to be a table view.
What I've tried
import UIKit
class CustomTableView: UIView {
// Do I make outlets?
//#IBOutlet var dataSource: UITableViewDataSource?
//#IBOutlet var delegate: UITableViewDelegate?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect){
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func layoutSubviews() {
super.layoutSubviews()
var tableView: UITableView!
tableView = UITableView(frame: self.bounds)
// I'm not sure how to set the delegate and dataSource
// tableView.dataSource = ???
// tableView.delegate = ???
self.addSubview(tableView)
}
}
After creating the UITableView programmatically and adding it as a subview to the custom view parent, I can't figure out how to get the custom view to act like it is the table view. That is, I don't know how to get the custom view to do the communication between the View Controller and the table view for the delegate and dataSource.
What I've read
Designing Your Data Source and Delegate
Create a static UITableView without Storyboards
These articles seemed good, but I got a little lost.
How do I make the custom view act like it's own table subview with regard to delegate and data source?

The solution is to make the tableView a property of the custom class (as #BaseZen suggested). Then provide properties and methods in the custom class to mimic and pass along the properties and methods needed from tableView.
import UIKit
#IBDesignable class UICustomTableView: UIView {
private var myTableView: UITableView
required init(coder aDecoder: NSCoder) {
myTableView = UITableView()
super.init(coder: aDecoder)
}
override init(frame: CGRect){
myTableView = UITableView()
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
}
// TODO: #IBOutlet still can't be set in IB
#IBOutlet weak var delegate: UITableViewDelegate? {
get {
return myTableView.delegate
}
set {
myTableView.delegate = newValue
}
}
// TODO: #IBOutlet still can't be set in IB
#IBOutlet weak var dataSource: UITableViewDataSource? {
get {
return myTableView.dataSource
}
set {
myTableView.dataSource = newValue
}
}
func registerClass(cellClass: AnyClass?, forCellReuseIdentifier identifier: String) {
myTableView.registerClass(cellClass, forCellReuseIdentifier: identifier)
}
func dequeueReusableCellWithIdentifier(identifier: String) -> UITableViewCell? {
return myTableView.dequeueReusableCellWithIdentifier(identifier)
}
override func layoutSubviews() {
super.layoutSubviews()
// ...
// setup UITableView
myTableView.frame = self.bounds
self.addSubview(myTableView)
// ...
}
}
Then it can be used like a normal UITableView:
import UIKit
class TableViewDemoVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var customTableView: UICustomTableView!
var items: [String] = ["One", "Two", "Three"]
override func viewDidLoad() {
super.viewDidLoad()
// setup the table view from the IB reference
customTableView.delegate = self
customTableView.dataSource = self
customTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = self.customTableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell!
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
// ...
}

It's not the most elegant, but jump out a level:
class MyCustomViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
myCustomView = CustomTableView()
myCustomView.frame = ... /* layout programatically */
/* Alternatively to the above 2 lines,
lay out myCustomView in StoryBoard,
and capture it as an #IBOutlet. It will then be ready here
to muck with, well before it gets displayed and needs the data */
myCustomView.tableView.delegate = self
myCustomView.tableView.dataSource = self
}
/* Now implement all your dataSource and delegate methods */
}
* IMPORTANT *
The key that you're missing is that tableView must be a stored property of your custom view. It's important and needs to be promoted from a silly local! It should also be initialized in the awakeFromNib() function, even if you don't know the frame size. Then reset its frame at layout time.
At a higher level, I don't know "What You're Really Trying To Do." It may not actually be the right implementation technique to be embedding a UITableView within a custom view; consider just laying out the UITableView in the main view. Then if you need decoration around it, lay those out as separate views in StoryBoard.

Related

Registering custom CollectionViewCells to one CollectionView inside a View component

I'm trying to create a component that has a CollectionView inside. I'm using this view inside a class as an IBOutlet as below.
class MyClass {
#IBOutlet weak var myCustomView: MyCustomView!
}
Inside MyCustomView I have a function which register custom cells.
class MyCustomView: UIView {
#IBOutlet var collectionView: UICollectionView! {
didSet {
collectionView.delegate = self
collectionView.dataSource = self
}
}
func configureCollectionView(identifier: String, cell: AnyObject.Type) {
self.collectionView.register(cell, forCellWithReuseIdentifier: identifier)
}
}
This function is being called inside MyClass with a custom collectionViewCell as below. What I try to achieve is registering different cells to a custom CollectionViewCell.
class MyClass {
#IBOutlet weak var myCustomView: MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
initializeCustomView()
}
private func initializeCustomView() {
myCustomView.configureCollectionView(identifier: String, cell: MyCustomCell.Type)
}
}
When I do this, IBOutlets of my custom collectionViewCells are getting nil with the error below. Why this happens? or Is this kind of structure possible?
Unexpectedly found nil while implicitly unwrapping an Optional value

Not able to create custom view

I wanted to create a custom view. So I designed my view like so..it's called TagResolutionView.xib
This is how I designed my view
And the TagResolutionView.swift file for that looks like this
import UIKit
#IBDesignable class TagResolutionView: UIView {
#IBOutlet var tagResolutionView: UIView!
#IBOutlet var tableview: UITableView!
required init?(coder: NSCoder) {
super.init(coder: coder)
tableview = UITableView()
}
override init(frame: CGRect) {
tableview = UITableView()
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBOutlet weak var delegate: UITableViewDelegate? {
get {
return tableview.delegate
}
set {
tableview.delegate = newValue
}
}
#IBOutlet weak var dataSource: UITableViewDataSource? {
get {
return tableview.dataSource
}
set {
tableview.dataSource = newValue
}
}
func registerClass(cellClass: AnyClass?, forCellReuseIdentifier identifier: String) {
tableview.register(cellClass, forCellReuseIdentifier: "cellClass")
}
func dequeueReusableCellWithIdentifier(identifier: String) -> UITableViewCell? {
return tableview.dequeueReusableCell(withIdentifier: identifier)
}
override func layoutSubviews() {
super.layoutSubviews()
tableview.frame = self.bounds
self.addSubview(tableview)
}
}
This is how I inserted the view in my main viewcontroller where I want the custom view to appear..
This is the view
This is how I configured it in the viewcontroller..
In the viewDidLoad..
stdProcedViewHeight.constant = 533 //Since the view I took above (as given in the pic) is less than the height of the actual TagResolutionView.xib, I increased its height here programatically.
standardsProceduresView.delegate = self
standardsProceduresView.dataSource = self
standardsProceduresView.registerClass(cellClass: UpdatingListTableViewCell.self, forCellReuseIdentifier: "cellClass") //I have also made .xib & .swift files for UpdatingListTableViewCell
Also added these methods..
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UpdatingListTableViewCell = self.standardsProceduresView.dequeueReusableCellWithIdentifier(identifier: "cellClass") as! UpdatingListTableViewCell
// cell.nameLbl.text = "MyName"
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
But I'm not able to get the desired output by doing all of this...All I get is just an empty tableview of the set height(height of 533 here) and nothing else of the design...
The reason why your table view doesn't show up is that you're resetting all of its content by calling tableView = UITableView() in your code.
It seems like you're using Storyboards, so assuming you properly connected your UITableView to the property outlet #IBOutlet var tableview: UITableView!, your table view will appear just fine and you don't need to initialize it via code.
I can also see that you're setting the frame of your table view and adding it to the view hierarchy in the layoutSubviews() method. That's definitely not something you need/want to do, first because you're using Storyboards and it means that your table view will already be part of the view hierarchy at this point.
I'd also mention that you should generally never add a subview in the layoutSubviews() method, because this method may be called multiple times and it means your view would be added multiple times to the view hierarchy, which would end up wasting memory.

Implementing UITableView with three "columns" in MapView [Swift]

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`
}
}

Custom UITableviewCell IBOutlet always nil

I have a custom UITableViewCell subclass with a simple IBOutlet setup for a UILabel.
class SegmentCell: UITableViewCell {
#IBOutlet weak var test: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
test.text = "Some Text"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Convinced I have everything set up correct have followed other answers, but the UILabel is always nil.
ViewController:
viewDidLoad:
self.tableView.registerClass(SegmentCell.self, forCellReuseIdentifier: "Cell")
cellForForAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! SegmentCell
return cell
}
Cell is set to Custom
Reuse identifier is correct
Cells class is SegmentCell
Tableview content is Dynamic Prototypes
What am I missing?
Since You are uisg Xib file you have to register with ,
tableView.register(UINib(nibName: "yourNib", bundle: nil), forCellReuseIdentifier: "CellFromNib")
Think, if only register with class ,system will not know the its Xib.This work for me.
Based on the way you register your cell, it is not going to be loading it from a storyboard or xib file. It will be invoking that init method only. Your init method does not create the label, so it will always be nil.
You should also use dequeueReusableCellWithIdentifier(_:forIndexPath:) instead of dequeueReusableCellWithIdentifier(_:). The latter predates storyboards and will return nil unless you have previously created a cell with that identifier and returned it.
Lastly, the init method that the tableView is calling is not the one you've implemented, or the app would crash on test.text = ... while trying to unwrap a nil optional.
UITableViewCell class and ContentView class can't be same.
You should register it first in the viewDidLoad function.
self.tableView.registerClass(CustomCell.self, forCellReuseIdentifier: "Cell")
I have same issue, after add custom cell class to cell in tableview placed in Storyboard.
I also register cell, like in documentation. But now I solve my issue.
"I remove registering and inspect cell again, all IBOutlets initialised"
I think that basically the outlets are not setup yet at init time. If you want to get to the outlets and manipulate them, then override didMoveToSuperview or similar and do it in there. For instance, this is what I had to do to get to the button outlet in my cell:
open class MyTableViewCell: UITableViewCell {
// Top stack view, not the inner one.
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var buttonView: UIButton?
open override func didMoveToSuperview() {
buttonView?.titleLabel?.adjustsFontForContentSizeCategory = true
}
func adjustForAccessibilityCategory(accessible: Bool) {
if accessible {
stackView.axis = .vertical
stackView.alignment = .leading
stackView.spacing = 2
} else {
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = 20
}
}
open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if #available(iOS 11.0, *) {
adjustForAccessibilityCategory(accessible: traitCollection.preferredContentSizeCategory.isAccessibilityCategory)
}
}
}

How do I instantiate a UIView / UITableView from a xib (nib) in a ViewController

I want to display a user interface that has a segmented control, and a different table view for each section of the segmented control; so, 2 table views (buddies and bunches) that can be switched between.
To implement this, I have done the following
Create a ViewController in Storyboard
Delete the View from the ViewController
Create a new UIViewController swift class with an associated xib file
Put the segmented control in the the main UIView in the xib
Put a inner UIView element inside of the main UIView to take the space where the table views where replace it
Created two subclasses of UITableView and corresponding xib files
Some options I have thought of:
I can set the class of the inner UIView in Interface Builder to be that of one of the table views, but I wouldn't know how to instantiate the other one in place of the initial one. If I created overlapping inner UIViews that each was associated with a table view and hiding one of them when I switch the segmented control, that actually kind of works, but the overlapping nature of views makes layout difficult and unintuitive.
What I want to know how to do: Instantiate the table views in place of the single main UIView element
Alternative: Have one UITableView subclass that has a condition based on the state of the segmented control for what data it displays. I don't like this as much because it will mix the code together for the table views. In this case, I wouldn't even need to use xibs anymore, I could do this in the storyboard with just one table view.
** ViewController Code **
#objc(BuddiesBunchesViewController) class BuddiesBunchesViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var tableView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Instantiate tableView here to BuddiesTableView
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func segmentedControlIndexChanged(sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex {
case 0: // Buddies
// Set tableview to the buddies table view
case 1: // Bunches
// Set tableview to the buddies table view
default:
break;
}
}
}
** Table View **
#IBDesignable class BuddiesTableView: UITableView, UITableViewDataSource, UITableViewDelegate {
var view: UIView!
var nibName: String = "BuddiesTableView"
//init
override init(frame: CGRect) {
// set properties
super.init(frame: frame)
// Set anything that uses the view or visible bounds
setup()
}
required init(coder aDecoder: NSCoder) {
//set properties
super.init(coder: aDecoder)
// Setup
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = self.bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
// MARK: - Table View
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
}
I personally wouldn't create two table views and swap them out. The only significant difference between the two would be that they're different instances, and you could test for that in the data source code. Would they point to different data sources? Why not just change the data source on-the-fly, rather than the table view?
Create one table view. Create two (or more) classes to act as data sources (and possibly delegates). They could be based on a common superclass if there are any behaviors they have in common.
When the segmented control value changes, just change the table view's dataSource property to point to the class with the code for that state. You may also need to follow that with a -reloadData call, if changing the data source doesn't reload it automatically.
#IBOutlet private weak var tableView: UITableView!
let moviesDataSource = MyMoviesDataSource()
let tvShowsDataSource = MyTVShowsDataSource()
#IBAction func segmentedControlTouched(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
self.tableView.dataSource = self.moviesDataSource
self.tableView.delegate = self.moviesDataSource
case 1:
self.tableView.dataSource = self.tvShowsDataSource
self.tableView.delegate = self.tvShowsDataSource
}
self.tableView.reloadData()
}
My solution will be as below.
You have a segment control having two segments, so Instead of having two table views you can have only one UITableView object and change the datasource based on segment control selectedSegmentIndex number and after that reload the table view. In storyboard put the table view on top of the UIVIewController and create an #IBOutlet for that.

Resources