I have been trying to create a custom view loaded through xib that contains a button and tableview. The table view is shown or hidden when button is pressed.
This interaction works and the table is created / shown. The problem I have is that I can not click on the table rows.
I have been looking all over and haven't found a solution that works.
I made sure that delegate and dataSource are set. I also do not have a GestureRecognizer for the ViewController it is used in that could absorb the touch.
Does anybody have an idea what I am missing?
Here is the code of this custom view:
class SubUnitSpinner : UIView, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var spinnerTableView: UITableView!
let subUnitNames: [String] = ["World", "Add/Remove"]
override init( frame: CGRect ) {
super.init(frame: frame)
loadViewFromNib()
setupTableView()
}
required init?( coder aDecoder: NSCoder ) {
super.init(coder: aDecoder)
loadViewFromNib()
setupTableView()
}
func setupTableView() {
spinnerTableView.delegate = self
spinnerTableView.dataSource = self
spinnerTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
spinnerTableView.rowHeight = 30
spinnerTableView.userInteractionEnabled = true
spinnerTableView.allowsSelection = true
spinnerTableView.hidden = true
}
func loadViewFromNib() {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "SubUnitSpinner", bundle: bundle)
let xibView = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
xibView.frame = bounds
xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(xibView)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = spinnerTableView.dequeueReusableCellWithIdentifier("Cell")! as UITableViewCell;
cell.userInteractionEnabled = true
cell.textLabel?.text = subUnitNames[indexPath.row]
cell.tag = indexPath.row
return cell;
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Interaction
print("cell with path: \(indexPath.row)")
}
#IBAction func spinnerTabbed(sender: AnyObject) {
spinnerTableView.hidden = !spinnerTableView.hidden
} }
Update:
View Layout creation:
The xib view layout has been defined in the "storyboard" and the File's Owner set to SubUnitSpinner. The IBOutlet and IBAction where created by ctrl drag and drop.
Usage in UIViewController:
I use it as part of a UIViewController which also has been defined in the storyboard. I added a UIView and declared the Custom Class to be SubUnitSpinner.
The SubUnitSpinner with layout as defined in xib shows up when running it and the button is clickable, the UITableView is shown / hidden when button is shown. The only thing not working is clicking on the tableView cells.
Is something wrong with the setup?
I just took your code and added to a dummy project to check. I did this for your method
required init?( coder aDecoder: NSCoder ) {
super.init(coder: aDecoder)
// removed the load from nib and setup tableview
}
In my case I did the following to add the SubUnitSpinner view to my parent view. Hope you did the same
let aTestView = SubUnitSpinner(frame: CGRectMake(50, 200, 200, 200))
view.addSubview(aTestView)
Also double check if you have connected any delegate and datasource from xib. All in all, your code looks fine and I was able to click on it properly. The below are my results that I achieved.
Related
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.
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 created a UICollectionView in an .xib file like so:
Based on this post: Can´t add items to UICollectionView inside UIView xib
I understand that you cannot directly add a UICollectionViewCell within a UICollectionView that is contained in a .xib, but instead have to create another .xib with only the UICollectionViewCell, which is what I did:
I create a GridViewCell class and add it as the Custom Class for the UICollectionViewCell:
class GridViewCell: UICollectionViewCell
{
#IBOutlet var clothingImageView: UIImageView!
override func awakeFromNib()
{
super.awakeFromNib()
// Initialization code
}
}
I also created a GridViewGallery class and associated with my UICollectionView .xib file:
extension GridViewGallery: UICollectionViewDelegate
{
}
extension GridViewGallery: UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return clothingImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GridViewCell", for: indexPath) as! GridViewCell
cell.clothingImageView.image = clothingImages[indexPath.row].image
return cell
}
}
class GridViewGallery: UIView
{
#IBOutlet var gridLayoutCollectionView: UICollectionView!
var clothingImages = [INSPhotoViewable]()
override init(frame: CGRect)
{
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
let gridViewCell = UINib(nibName: "GridViewCell", bundle: nil)
gridLayoutCollectionView.register(gridViewCell, forCellWithReuseIdentifier: "GridViewCell")
}
func storeGridViewImages(photos: [INSPhotoViewable])
{
clothingImages = photos
gridLayoutCollectionView.dataSource = self
gridLayoutCollectionView.delegate = self
}
}
However, I get a unexpectedly found nil while unwrapping an Optional value error when I tried to load the XIB file to a UIView in another class:
#IBAction func gridViewLayout(_ sender: UIBarButtonItem)
{
if let gridView = Bundle.main.loadNibNamed("GridViewGallery", owner: self, options: nil)?.first as? GridViewGallery
{
print("GRID VIEW FOUND")
self.addSubview(gridView)
}
}
The GridViewGallery File Owner's Custom Class is empty, as I set the Custom Class on the UIView property instead as seen in the first screenshot:
However, if I instead set the File Owner's Custom Class to GridViewGallery and leave the Custom Class as blank from the UIView itself, then I get a crash with error this class is not key value coding-compliant for the key gridLayoutCollectionView.
I'm not sure I understand what is causing this, and which way I should be setting the Custom Class.
Can someone assist me?
Thanks.
Objective
I wanna place my (BusinessViewTableHeader: UIView) as tableView header:
tableView.tableHeaderView = BusinessViewTableHeader.instanceFromNib() as! BusinessViewTableHeader
Inside BusinessViewTableHeader there is a UICollectionView which are supposed to display images when swiped, much like the Tinder app.
This is my UIView subclass:
class BusinessViewTableHeader: UIView {
#IBOutlet var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.registerNib(UINib(nibName: "BusinessImageCollectionCell", bundle: nil), forCellWithReuseIdentifier: "BusinessImageCollectionCell")
}
class func instanceFromNib() -> UIView {
return UINib(nibName: "BusinessViewTableHeader", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
....
}
extension BusinessViewTableHeader: UICollectionViewDelegate, UICollectionViewDataSource {
....
}
Problem
I have a custom UIView xib containing a UICollectionView. The problem is that I can´t add any cells (items) to the UICollectionView. I can add items to my other UICollectionView which are placed inside a UIViewController. The first image is showing the properties for the UICollectionView inside a UIViewController, the second image is showing the UICollectionView inside a UIView xib.
[![UICollectionView in UIViewController][1]][1]
[1]: http://i.stack.imgur.com/zFCeG.png
[![UICollectionView in UIView xib][2][2]
[2]: http://i.stack.imgur.com/jKU6z.png
Question
Why am I not able to add items to the UICollectionView inside the UIView xib? How?
You can't have UICollectionViewCell when the UICollectionView is on a Nib. What you need to do is to create the UICollectionViewCell as another nib and get it registered in the class that you are using for your CollectionView.
Create a new nib, drag a UICollectionViewCell inside it, and do something like this in the class that works with your UICollectionView.
override func awakeFromNib() {
let nibName = UINib(nibName: "ClassCollectionCell", bundle:nil)
collectionView.registerNib(nibName, forCellWithReuseIdentifier: "collectionCell")
}
Remember you can add a custom class to the UICollectionViewCell so you can pass dynamic data to it.
Adding cells in a xib is not supported. If you must use a xib file, then you will need a separate xib which contains the UICollectionView cell. Storyboards may be a better solution.
It is not clear what you are trying to achieve. UICollectionView has specific means for creating headers which uses the datasource and delegate. Collection views are good for displaying items in a grid layout or other complex arrangements.
If all you need is to display a list of rows, then a UITableViewController might be an easier alternative.
Whatever the case, it is probably better to use a storyboard instead of a xib, and to subclass the UICollectionViewController or UITableViewController, rather than a subview.
Your custom class name can be entered in the identity inspector for the UIViewController or UIView:
In Swift 3.0 register nib with following method-
let nibName = UINib(nibName: "FruitCell", bundle:nil)
collectionView.register(nibName, forCellWithReuseIdentifier: "CellIdentifier")
In Swift 3.0
first Create Xibfile of UIView
import UIKit
class SubCatagoryListView:UIView , UICollectionViewDelegate , UICollectionViewDataSource
{
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var btnClose: UIButton!
#IBOutlet weak var subCategoryListCollectionView: UICollectionView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
subCategoryListCollectionView.register(UINib(nibName: "SubcatagoryListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "SubcatagoryListCollectionViewCell")
mainView.layer.cornerRadius = 10.0
mainView.clipsToBounds = true
}
static func subCatagoryListView() -> SubCatagoryListView? {
let arr = Bundle.main.loadNibNamed("SubCatagoryListView", owner: self, options: nil)
if arr != nil {
if arr!.count > 0 {
if let view = arr![0] as? SubCatagoryListView {
return view;
}
}
}
return nil;
}
#IBAction func btnBackgroundTapped(_ sender: UIButton)
{
self.removeFromSuperview()
}
#IBAction func btnCloseTapped(_ sender: UIButton)
{
self.removeFromSuperview()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SubcatagoryListCollectionViewCell", for: indexPath) as! SubcatagoryListCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let cellsize = CGSize(width: (subCategoryListCollectionView.bounds.size.width/3) - 10, height: 50)
return cellsize
}
}
After Create new CollectionViewCell Xib file
import UIKit
class SubcatagoryListCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
after create both file i am load xib on my storybord
var subcatagoryXib:SubCatagoryListView?
override func awakeFromNib() {
super.awakeFromNib()
if let subcategoryView = SubCatagoryListView.subCatagoryListView()
{
subcatagoryXib = subcategoryView
}
}
#IBAction func btnAddInterestTapped(_ sender: UIButton) {
if subcatagoryXib != nil
{
self.subcatagoryXib!.frame = CGRect(x:0, y: 0, width: self.view.frame.width , height: self.view.frame.height)
self.view.addSubview(self.subcatagoryXib!)
}
}
Same as #Pato's answer, but here is a more thorough tutorial for how to add a customized UICollectionViewCell inside a Xib file from #aestusLabs on Medium. It's a 3-5 min reading, I personally find it very helpful. It basically tells you to create another customized UICollectionViewCell with .xib, and register it in your "level 1" cell's awakeFromNib().
https://medium.com/#aestusLabs/adding-a-uicollectionviews-to-a-custom-uitableviewcell-xib-tutorial-swift-4-xcode-9-2-1ec9ce4095d3
I have a view in my app called JournalViewController that I'm presenting over my PastSessionsViewController. PastSessions has a table view that the user can tap to edit and bring up the journal.
When the user edits an entry and saves it (saving to CoreData), dismissing JournalViewController I'd like for the table view in PastSessions to reflect those changes and show the updated table cell.
I'm calling tableView.reloadData() in PastSessionsViewController viewDidLoad() but that doesn't seem to be working. I've also added a delegate for JournalViewController to interact with PastSessionsViewController ahead of dismissViewController
Here's some code to look at:
In PastSessionsViewController:
class PastSessionsViewController: UIViewController, UITableViewDelegate, JournalVCDelegate {
weak var tableView: UITableView?
weak var backButton: UIButton?
let pastSessionsDataSource: PastSessionsDataSource
init() {
pastSessionsDataSource = PastSessionsDataSource()
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
tableView.backgroundColor = nil
tableView.delegate = self
tableView.dataSource = pastSessionsDataSource
tableView.registerClass(EntryCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)
self.tableView = tableView
}
override func viewDidAppear(animated: Bool) {
tableView?.reloadData()
}
func didFinishJournalVC(controller: JournalViewController) {
var newDataSource = PastSessionsDataSource()
tableView?.dataSource = newDataSource
// tried this ^, but it's causing the app to crash
// tableView?.reloadData() <- this isn't doing the trick either
dismissViewControllerAnimated(true, completion: nil)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let editJournalVC = JournalViewController(label: "Edit your thoughts")
editJournalVC.delegate = self
presentViewController(editJournalVC, animated: true, completion: nil)
}
}
In JournalViewController:
protocol JournalVCDelegate {
func didFinishJournalVC(controller: JournalViewController)
}
class JournalViewController: UIViewController, UITextViewDelegate {
var delegate: JournalVCDelegate! = nil
func doneJournalEntry(sender: UIButton) {
journalEntryTextArea?.resignFirstResponder()
... do some core data saving ...
delegate.didFinishJournalVC(self)
}
}
In PastSessionsDataSource:
import UIKit
import CoreData
class PastSessionsDataSource: NSObject {
var arrayOfEntries = [Entry]()
var coreDataReturn: [Meditation]?
func prepareEntries() {
// gets stuff from coredata and formats it appropriately
}
override init() {
super.init()
prepareEntries()
}
}
extension PastSessionsDataSource: UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfEntries.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! EntryCell
... set up the labels in the cell ...
return cell
}
}
Thanks for looking!
viewDidLoad is called when the view controller load its view at the first time, so basically it will only be called once during the view controller's whole life cycle.
One quick solution is to put tableView.reloadData() in PastSessionsViewController viewWillAppear() or viewDidAppear().
However I do not like this quick solution as every time you dismiss JournalViewController, the table view will be reloaded, even the user has not changed anything on JournalViewController (for example, cancel the edit). So I suggest to use delegate approach between PastSessionsViewController and JournalViewController, when the user actually edit the data on JournalViewController then inform PastSessionsViewController to refresh the table.
You are currently prepare entries only on init of PastSessionsDataSource, but not after you did CoreData changes. So each time when you reloadData for tableView you work with the same data set loaded initially. As a quick hack you can try to updated viewDidAppear in a following way:
override func viewDidAppear(animated: Bool) {
if let tableView = tableView {
let dataSource = tableView.dataSource! as PastSessionsDataSource
dataSource.prepareEntries()
tableView.reloadData()
}
}
Your tableView property is probably nil in viewDidAppear, based on your listed code. The reason is that in viewDidLoad you construct a UITableView as tableView, and that is a local variable. You need to assign that variable to the property:
self.tableView = tableView