TableViewCell Nib error: loaded the nib but the view outlet was not set - swift

Trying to create a tableView cell xib, but am stuck with the error "loaded the nib but the view outlet was not set"
I have read many posts and most say to drag the "view" from File's Owner to the IB view.
However, if you notice from the images, I do not have a "view" as an option. I have recreated a xib with the same results. Maybe new in Swift 4.2.
Any help would be huge.
class VersionTVCell: UITableViewCell {
#IBOutlet weak var versionNumber: UILabel!
#IBOutlet weak var versionDetail: UILabel!
#IBOutlet weak var versionDate: UILabel!
}
class VersionTVC: UITableViewController {
fileprivate var versions: Array<VersionModel> = [VersionModel]()
override func viewDidLoad() {
super.viewDidLoad()
self.versions = AppDelegate.getRLDatabase().getVersion()
tableView.register(UINib(nibName: "VersionTVCell", bundle: nil), forCellReuseIdentifier: "VersionCell")
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 70
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "VersionCell", for: indexPath) as! VersionTVCell
cell.versionNumber.text = self.versions[(indexPath as NSIndexPath).row].versionNumber
cell.versionDetail.text = self.versions[(indexPath as NSIndexPath).row].versionDetail
cell.versionDate.text = self.versions[(indexPath as NSIndexPath).row].versionDate
return cell
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.versions.count
}
}
Below are two screen shots, first from the cell and second from the File's Owner.
I have tried linking from File's Owner to the labels as well. Did not help.
EDIT:
Adding my addSubView func:
#objc func loadVersions() {
let newSubView = AddSubView.toVersions.getView()
let blackBackground = UIView(frame: CGRect(x: 0, y: 65, width: self.view.frame.width, height: self.view.frame.height))
blackBackground.backgroundColor = UIColor.black.withAlphaComponent( 0.7)
UIView.transition(with: self.view, duration: 0.3, options: UIView.AnimationOptions.transitionCrossDissolve,
animations: {self.view.addSubview(self.backgroundView)}, completion: nil)
newSubView.frame = CGRect(x: 0, y: (self.view.frame.height) * 0.1, width: (self.view.frame.width) * 0.9, height: (self.view.frame.height) * 0.8)
newSubView.layer.borderWidth = 1
newSubView.layer.borderColor = UIColor.defaultDialogBorderColor().cgColor
newSubView.layer.cornerRadius = 8.0
newSubView.clipsToBounds = true
self.view.addSubview(newSubView)
}

I have tried your code and it's just working fine.
Make sure you do not assign VersionTVCell of UITableViewCell class to file owner and not connecting #IBOutlet to xib
File Owner connections Inspector should look alike below image
Also, try deleting and reconnecting #IBOutlet
Tested example

As per my understanding, all is correct in Code so Apply some step.
1) Make Sure Cell's class name assign with correct Name.
2) clean code with this command Shift + option + command + k.
3) If you not got any success with above step. Remove delegate and dataSource Outlets from the tableView UI and write manually after this line.
tableView.register(UINib(nibName: "VersionTVCell", bundle: nil), forCellReuseIdentifier: "VersionCell")
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()

Related

TableView cell not changing height while animating dropdown UIView

I have a UIView animating dropdown while tapping. what I want to achieve is, while UIView height changing when I tap the tableView cell automatically follow the height too. this is my code
class SubjectCell: BaseCell {
lazy var tableView: UITableView = {
let tv = UITableView()
tv.delegate = self
tv.dataSource = self
tv.allowsSelection = false
return tv
}()
extension SubjectCell: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
// This is the view I want to animate
#IBOutlet weak var mainContainer: UIView!
#IBOutlet weak var calendarView: JKCalendar!
#IBOutlet weak var arrowImageView: UIImageView!
#IBOutlet weak var mainContainerHeightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
mainContainer.addGestureRecognizer(tapGesture)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#objc func handleTap(gesture: UITapGestureRecognizer) {
if mainContainerHeightConstraint.constant == 75 {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.arrowImageView.image = #imageLiteral(resourceName: "up-chevron")
self.mainContainerHeightConstraint.constant = 370
self.calendarView.alpha = 1
})
} else {
defaultConstraint()
}
}
fileprivate func defaultConstraint() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.arrowImageView.image = #imageLiteral(resourceName: "down-chevron")
self.mainContainerHeightConstraint.constant = 75
self.calendarView.alpha = 0
})
}
this is my configuration for the uiview.
I set my mainContainerView top, leading, and trailing = 0. and height = 370.
inside mainContainerView there is a stackView containing another 2 view, and set my stack view top, trailing, bottom, and leading = 0 in mainContainerView
this the result in my simulator
I still can't get the idea how to setup tableViewCell so it will automatically changing. Please help me :)
You need to set the bottom constraint of mainContainerView so that the row height will be calculated based on you constraints and remove the heightForRowAt function. And another is every time you change the height of your mainContainerView you need to call
tableView.beginUpdates()
tableView.endUpdates()
For it to recalculate the new height of your row and it will apply.

Swift - show a complex View in UITableViewController with empty list through Storyboard

I'd need to show a View with some UIImageView, texts and links in my TableViewController if list controller is empty.
I know you can insert a label through this code:
func numberOfSections(in tableView: UITableView) -> Int
{
var numOfSections: Int = 0
if youHaveData
{
tableView.separatorStyle = .singleLine
numOfSections = 1
tableView.backgroundView = nil
}
else
{
let noDataLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
noDataLabel.text = "No data available"
noDataLabel.textColor = UIColor.black
noDataLabel.textAlignment = .center
tableView.backgroundView = noDataLabel
tableView.separatorStyle = .none
}
return numOfSections
}
But I'd need to show a complex View designed in the Storyboard in a different ViewController.
I tried with this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let emptyViewController = storyboard.instantiateViewController(withIdentifier :"empty_controller") as! EmptyViewController
tableView.backgroundView = emptyViewController.emptyView
Where emptyView is defined in this way
class EmptyViewController: UIViewController {
#IBOutlet weak var emptyView: UIView?
...
}
But it doesn't work, I can't see that view in the table if the list is empty.
How can I do it?
Thank you a lot
Best bet is to use two containers view one with tableview and one with emptyVC, then in parent of that container you can hide/unhide or remove/add view depending upon your logic
EmptyViewController has its own view, why you have to create another emptyView?
Remote emptyView property and use this code:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let emptyViewController = storyboard.instantiateViewController(withIdentifier :"empty_controller") as! EmptyViewController
tableView.backgroundView = emptyViewController.view
Set you view controller to inherit from UITableViewDelegate and set the tableView.delegate to point to your VC.
class VC : UITableViewController, UITableViewDelegate {
override func viewDidLoad() {
...
tableView.delegate = self
}
}
Then override the following two functions
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
}
This'll add a header to your tableview section. You can edit what this header is based on the contents of the table

Swift 3: Segue and pass in data from one class to another

I have a simple UICollectionViewController called HomeController that has a bunch of cells. These cells have a class of their own called PostCell. I also have a NSObject class that holds all the data for every Post (these posts are my cells). Inside my PostCell I have a UIView called profileSegueContainer. When I tap this UIView, I want to segue to a new controller. I am able to achieve this with a presentViewController method but I also want to pass in the info of that specific post. For example, if I tap a cell that has the uid of "1234", I want to be able to not only segue to the new controller but also pass this uid into it. Of course if I tap the next cell and the post.uid of that cell is "4567", then I want to also pass that in when I segue. I hope this makes sense... I want it to work similar to Instagrams "tapped on a user to get to their profile" feature. I hope this makes sense. Any help will be highly appreciated and of course, I will mark as answer. Thank you. All the relevant code is below:
class Post: NSObject {
var author: String?
var avatar_image_url: String?
var likes: Int?
var postImageUrl: String?
var uid: String?
var post_id: String?
var hashtags: [String]?
}
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var postCell: PostCell?
var userProfileController: UserProfileController?
var posts = [Post]()
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.backgroundColor = grayBackgroundColor
self.collectionView?.register(PostCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView?.contentInset = UIEdgeInsetsMake(16, 0, 16, 0)
self.collectionView?.alwaysBounceVertical = true
self.collectionView?.showsVerticalScrollIndicator = false
setupNavigationBarBranding()
checkIfUserIsLoggedIn()
fetchPosts()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frameWidth = self.view.frame.size.width
let width = frameWidth - (16 + 16)
let height = frameWidth - (16 / 9) + 50 + 50
return CGSize(width: width, height: height)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PostCell
cell.homeController = self
cell.post = posts[indexPath.item]
userProfileController?.post = posts[indexPath.item]
return cell
}
}
class PostCell: BaseCell {
var homeController: HomeController?
var userProfileController: UserProfileController?
var post: Post? {
didSet {
if let postDisplayName = post?.author {
displayName.text = postDisplayName
}
if let postImageUrl = post?.postImageUrl {
postImage.loadImageUsingCacheWithUrlString(urlString: postImageUrl)
}
if let postProfileImage = post?.avatar_image_url {
profileImage.loadImageUsingCacheWithUrlString(urlString: postProfileImage)
}
}
}
lazy var profileSegueContainer: UIView = {
let view = UIView()
view.isUserInteractionEnabled = true
return view
}()
func handleProfileSegue() {
let userProfile = UserProfileController()
let navigationUserProfile = UINavigationController(rootViewController: userProfile)
homeController?.present(navigationUserProfile, animated: true, completion: nil)
}
override func setupViews() {
super.setupViews()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleProfileSegue))
profileSegueContainer.addGestureRecognizer(tap)
addSubview(profileSegueContainer)
_ = profileSegueContainer.anchor(profileImage.topAnchor, left: profileImage.leftAnchor, bottom: profileImage.bottomAnchor, right: displayName.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
}
Segue has a very specific meaning, in the context of View Controllers. You probably want to use the phrase "push another view controller" instead.
First off, UINavigationController is typically only used once in a scene -- it's the view controller that keeps track of a stack of sub-View Controllers. You only create one of them, and you probably want to set your HomeController as the nav controller's rootViewController.
Then when the user taps the view you should forward this to a method you will add to HomeController; this will create your custom UserProfile class, configure it with the data it needs, and then call: navigationController?.pushViewController(userProfile, animating: true)
There are many fine tutorials around that show you how this all works.

Tab Bar Item hidden behind tableview / not being shown?

I have an empty view with a tab bar pictured below, when i load a routine a table appears containing the contents, however it seems to overlay the tab bar killing off app navigation. Its not sized in the storyboard to overlay it and its constraint locked to not do so, so im unsure why this is happening, pics of the issue and VC's code below:
VC Code:
import Foundation
import UIKit
import CoreData
class RoutineController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - DECLARATIONS
#IBAction func unwindToRoutine(segue: UIStoryboardSegue) {}
#IBOutlet weak var daysRoutineTable: UITableView!
#IBOutlet weak var columnHeaderBanner: UIView!
#IBOutlet weak var todaysRoutineNavBar: UINavigationBar!
#IBOutlet weak var addTOdaysRoutineLabel: UILabel!
let date = Date()
let dateFormatter = DateFormatter()
let segueEditUserExerciseViewController = "editExerciseInRoutineSegue"
//This is the selected routine passed from the previous VC
var selectedroutine : UserRoutine?
// MARK: - VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
setupView()
daysRoutineTable.delegate = self
daysRoutineTable.dataSource = self
view.backgroundColor = (UIColor.customBackgroundGraphite())
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateStr = dateFormatter.string(from: date)
todaysRoutineNavBar.topItem?.title = dateStr + " Routine"
}
// MARK: - VIEWDIDAPPEAR
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
}
// MARK: - TABLE UPDATE COMPONENTS
private func setupView() {
updateView()
}
// MARK: - TABLE SETUP
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = self.selectedroutine?.userexercises?.count
{
print("exercises: \(count)")
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
// MARK: - VIEW CONTROLER ELEMENTS VISIBILITY CONTROL
fileprivate func updateView() {
var hasUserExercises = false
if let UserExercise = self.selectedroutine?.userexercises {
hasUserExercises = UserExercise.count > 0
}
addTOdaysRoutineLabel.isHidden = hasUserExercises
columnHeaderBanner.isHidden = !hasUserExercises
daysRoutineTable.isHidden = !hasUserExercises
}
// MARK: - SETTING DATA FOR A TABLE CELL
func configure(_ cell: TodaysRoutineTableViewCell, at indexPath: IndexPath) {
if let userExercise = selectedroutine?.userexercises?.allObjects[indexPath.row]
{
print("\((userExercise as! UserExercise).name)")
cell.todaysExerciseNameLabel.text = (userExercise as! UserExercise).name
cell.todaysExerciseRepsLabel.text = String((userExercise as! UserExercise).reps)
cell.todaysExerciseSetsLabel.text = String((userExercise as! UserExercise).sets)
cell.todaysExerciseWeightLabel.text = String((userExercise as! UserExercise).weight)
}
}
}
requested table constraints
Debug hierarchy
The Segue that sends the user back to the view that looses its tab bar
if segue.identifier == "addToTodaySegue" {
let indexPath = workoutTemplateTable.indexPathForSelectedRow
let selectedRow = indexPath?.row
print("selected row\(selectedRow)")
if let selectedRoutine = self.fetchedResultsController.fetchedObjects?[selectedRow!]
{
if let todaysRoutineController = segue.destination as? RoutineController {
todaysRoutineController.selectedroutine = selectedRoutine
}
}
}
I also feel perhaps the viewDidAppear code may cause the issue, perhaps the super class?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
Updated storyboard image
I suspect you need to embed your viewController in a UINavigationController.
Consider the following setup:
I suspect your setup is like the upper one:
TapBar -> ViewController -show segue-> ViewController
Which results in a hidden tapbar, like in your description:
While the bottom setup:
TapBar -> NavigationCntroller -rootView-> ViewController -show segue-> ViewController
results in:
which is what you want, how I understood.
Update
It's hard to see. The screenshot of your Storyboard is in pretty low resulution, but the segues look wrong. Double check them. A Segue of type show (e.g push) looks like this:
Also clear project and derived data. Segue type changes sometime are ignored until doing so.
Try calling this self.view.bringSubviewToFront(YourTabControl).
The previous suggestion should work. But the content at the bottom part of tableview will not be visible as the tabbar comes over it. So set the bottom constraint of tableview as the height of tabbar.

UIView is always behind UITableView

I have a UIViewController (not a UITableViewController because I read that a view controller is best for this kind of behaviour) with a UITableView. In this view controller I want to add a floating UIView and place it above my tableview, to do so I wrote:
public override func viewDidLoad() {
super.viewDidLoad()
// add button
let fbv = liquidActionButtonInstance.addActionButton() // this is a UIView
self.view.addSubview(fbv)
liquidActionButtonInstance.delegate = self
// delegate
tableView.delegate = self
tableView.dataSource = self
}
However my floating view appears behind my UITableView, how can I add it as the first child of self.view? I've used
self.view.addSubview(fbv)
self.view.bringSubviewToFront(fbv)
Among others and none seems to work.
Edit:
I added some screenshots of my view's hierarchy.
Edit 2:
Here I put a little more code:
My ViewController without some unrelated code:
public class ActividadesTableViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var descripcionFiltrosLabel: UILabel!
#IBOutlet weak var filtrosLabelBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var filtrosLabelTopConstraint: NSLayoutConstraint!
#IBOutlet weak var tableViewTopConstraint: NSLayoutConstraint!
private var liquidActionButtonInstance: FloatingActionButton = FloatingActionButton()
public var viewModel : ActividadesTableViewModeling?
public override func viewDidLoad() {
super.viewDidLoad()
// Agregar action button
self.view.insertSubview(liquidActionButtonInstance.addActionButton(),aboveSubview: tableView)
liquidActionButtonInstance.delegate = self
// set row's height
tableView.estimatedRowHeight = 70
tableView.rowHeight = UITableViewAutomaticDimension
// delegate
tableView.delegate = self
tableView.dataSource = self
// load tableview data
if let viewModel = viewModel {
viewModel.loadActividades(withFilters: nil)
}
}
}
// MARK: FloatingButton
extension ActividadesTableViewController: FloatingActionButtonDelegate {
public func performSegueFromFloatingActionButton(segueName name: String) {
performSegueWithIdentifier(name, sender: self)
}
}
// MARK: TableView
extension ActividadesTableViewController: UITableViewDataSource, UITableViewDelegate {
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Omitting this code, just mentioning the methods
}
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ActividadCell", forIndexPath: indexPath) as! ActividadTableViewCell
if let viewModel = viewModel {
cell.viewModel = viewModel.cellModels.value[indexPath.row]
} else {
cell.viewModel = nil
}
return cell
}
public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
}
And this is how I add the button (in a different class):
func addActionButton() -> LiquidFloatingActionButton {
let createButton: (CGRect, LiquidFloatingActionButtonAnimateStyle) -> LiquidFloatingActionButton = { (frame, style) in
let floatingActionButton = LiquidFloatingActionButton(frame: frame)
floatingActionButton.animateStyle = style
floatingActionButton.dataSource = self
floatingActionButton.delegate = self
floatingActionButton.color = ColoresKairos.principal2
return floatingActionButton
}
let cellFactory: (String) -> LiquidFloatingCell = { (iconName) in
return LiquidFloatingCell(icon: UIImage(named: iconName)!)
}
cells.append(cellFactory("iphone-action-button-group"))
cells.append(cellFactory("iphone-action-button-notepad"))
cells.append(cellFactory("iphone-action-button-check-box"))
let floatingFrame = CGRect(x: UIScreen.mainScreen().bounds.width - 56 - 16, y: UIScreen.mainScreen().bounds.height - 56 - 16, width: 56, height: 56)
let bottomRightButton = createButton(floatingFrame, .Up)
//view.addSubview(bottomRightButton)
//return view
return bottomRightButton
}
You need to add the view using addSubview(_:) AND bringToFront(_:). You can also try sending the tableView to the back using sendToBack(_:)
In your document outline menu in your storyboard, you can place your floating view below your TableView in view hierarchy. Then, you can always see your floating view placed above your tableview.
It's gonna be like this.
▼ Your ViewController
Top Layout Guide
Bottom Layout Guide
▼View
▶︎ TableView
▶︎ Floating View
Below is the code that is working along with its screenshot, I suspect that your call liquidActionButtonInstance.addActionButton() returns an empty button?
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
private var liquidButton: LiquidFloatingActionButton?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Setting up the liquid button
liquidButton = createLiquidButton()
view.addSubview(liquidButton!)
}
func createLiquidButton() -> LiquidFloatingActionButton {
let frame = CGRect(x: UIScreen.mainScreen().bounds.width - 56 - 16, y: UIScreen.mainScreen().bounds.height - 56 - 16, width: 56, height: 56)
let button = LiquidFloatingActionButton(frame: frame)
button.animateStyle = .Up
button.color = UIColor.redColor()
return button
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
// UITableView Implementation, not included not relevent for the example.
}
The error here, after debugging the view hierarchy was that my tableView wasn't being added directly to the UIViewController's hierarchy, so it was added above everything else, and adding my FloatingButton as a subview of UIViewController always resulted on it being hidden by my tableView.
My tableView is added via storyboard, and as far as I know this is not the expected behaviour, but in my case just adding the following lines:
self.view.addSubview(tableView)
self.view.addSubview(liquidActionButtonInstance.addActionButton())
Solved my problem.