I create a TableView via StoryBoard. I connect all the element to a UITableViewCell class call PostTableCell. I already set identifier for the tableCell in StoryBoard. My code is as below:
class PopViewController: UIViewController ,IndicatorInfoProvider ,UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var popTableView: UITableView!
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
popTableView.dataSource = self
popTableView.delegate = self
fetchInitialPost()
}
func fetchInitialPost(){
Alamofire.request(MyURL, method: .get).responseJSON{
response in
switch response.result{
case .success(let result):
let json = JSON(result)
guard let feedArr = json["feed"].array else{
return
}
for post in feedArr {
if let post = post.dictionary,let feed = Post.init(dict: post){
self.posts.append(feed)
}
}
self.popTableView.reloadData()
break
case .failure(let error):
print(error)
}
}
}
func indicatorInfo(for pagerTabScripController : PagerTabStripViewController ) -> IndicatorInfo {
return IndicatorInfo (title : "Pop")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostTableCell", for: indexPath) as! PostTableCell
cell.posts = self.posts[indexPath.row]
return cell
}
}
I think I do all I should do in order to make it work already. But when I run the project I gives me this error, but I don't have any error in console. The app is just blank and this error below pops out.
So what cause this problem? And how to solve this?
This usually happens when there is a problem when loading the Storyboard.
I think (this has happend to me multiple times) you maybe have User Defined Runtime Attributes on popTableView in your Storyboard. If there are any that can not be set, because popTableView does not have the respective property, the debugger will show the behavior that you are seeing.
You can check if there are any User Defined Runtime Attributes by selecting the table view in Interface Builder an checking the Identity Inspector.
Try deleting any attributes that might cause problems.
Alternatively you forgot to connect the #IBOutlet in your Storyboard. You can check how to do that in the official documentation.
Related
Here's my DisruptionsViewController which has the tableView. the function setUp() is used in another ViewController class to set up the DisruptionsViewController.
public class DisruptionsInfoViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
public override func viewDidLoad() {
super.viewDidLoad()
self.setUp()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: "DisruptionInfoTableViewCell", bundle: nil), forCellReuseIdentifier: "disruptionsInfoTableViewCell")
}
private func loadFromNib() -> UIView? {
let nibName = String(describing: DisruptionsInfoViewController.self)
let nib = UINib(nibName: nibName, bundle: Bundle(for: type(of: self)))
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
public func setUp() {
guard let disruptionsInfoViewController = self.loadFromNib() else { return }
disruptionsInfoViewController.frame = self.view.bounds
}
}
extension DisruptionsInfoViewController: UITableViewDataSource, UITableViewDelegate {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "disruptionsInfoTableViewCell", for: indexPath) as? DisruptonInfoTableViewCell else { return UITableViewCell() }
return cell
}
}
Here's the tableViewCell class.
import UIKit
class DisruptonInfoTableViewCell: UITableViewCell {
#IBOutlet weak var testLabel: UILabel!
}
I can see the tableView in the view debugger, but unable to see it in the view as the tableViewCell is not registered for some reason.
Here's how I am using it in another controller's delegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let viewModel = presenter.headerViewModel(for: section) else { return nil }
let dateSummaryView = DateSummaryView(frame: .zero)
dateSummaryView.setup(with: viewModel)
let disruptionsViewController = DisruptionsInfoViewController()
return disruptionsViewController.view
}
Does anyone know where the problem could be?
I tried following tutorials from YouTube and other articles, they use the same approach but for some reason it doesn't work for me.
First, as mentioned in the comments, nothing in your setUp() is doing anything, so it can be removed.
The reason you are not seeing your table view rows in your other table's section header views is because here (I'll ignore the first three lines since they have nothing to do with this):
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//guard let viewModel = presenter.headerViewModel(for: section) else { return nil }
//let dateSummaryView = DateSummaryView(frame: .zero)
//dateSummaryView.setup(with: viewModel)
let disruptionsViewController = DisruptionsInfoViewController()
return disruptionsViewController.view
// as soon as we return, disruptionsViewController is released
// and no code it contains will be executed
}
You created an instance of DisruptionsInfoViewController, pulled out its view, and then tossed away the controller code.
If you want to use this approach (rather odd, but we have to assume you have a logical reason to do this), you need to keep a reference to DisruptionsInfoViewController so its code can be used:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let disruptionsViewController = DisruptionsInfoViewController()
// add disruptionsViewController as a child view controller
// this will "hold on to it" so its code can execute
addChild(disruptionsViewController)
disruptionsViewController.didMove(toParent: self)
return disruptionsViewController.view
}
Now, this is technically incorrect, as Apple's docs state:
Call the addChildViewController: method of your container view controller.
Add the child’s root view to your container’s view hierarchy.
Add any constraints for managing the size and position of the child’s root view.
Call the didMoveToParentViewController: method of the child view controller.
But, because we are returning the view for use as a section header view, we cannot perform 2. before calling didMove(toParent: self).
You didn't include in your post (or mention in your comments) how you're setting the Height of the section header, so, using:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150.0
}
We get this with your original code - section header view background color is blue, the "table view in header view" background color is green:
and we get this when using addChild():
Here is a complete example project: https://github.com/DonMag/Disrupt
While this will work, if you really want to embed a table view in another table view's section header(s), there are better ways to do it.
Evening, in my application I do not want to use RxCocoa and I'm trying to conforming to tableview data source and delegate but I'm having some issues.
I can't find any guide without using RxCocoa or RxDataSource.
In my ViewModel in have a lazy computed var myData: Observable<[MyData]> and I don't know how to get the number of rows.
I was thinking to convert the observable to a Bheaviour Subject and then get the value but I really don't know which is the best prating to do this
You need to create a class that conforms to UITableViewDataSource and also conforms to Observer. A quick and dirty version would look something like this:
class DataSource: NSObject, UITableViewDataSource, ObserverType {
init(tableView: UITableView) {
self.tableView = tableView
super.init()
tableView.dataSource = self
}
func on(_ event: Event<[MyData]>) {
switch event {
case .next(let newData):
data = newData
tableView.reloadData()
case .error(let error):
print("there was an error: \(error)")
case .completed:
data = []
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = data[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
// configure cell with item
return cell
}
let tableView: UITableView
var data: [MyData] = []
}
Make an instance of this class as a property of your view controller.
Bind your myData to it like:
self.myDataSource = DataSource(tableView: self.tableView)
self.myData
.bind(to: self.myDataSource)
.disposed(by: self.bag)
(I put all the selfs in the above to make things explicit.)
You could refine this to the point that you effectively re-implement RxCoca's data source, but what's the point in that?
I'm new to iOS and Swift and I'm trying to learn a little by creating a simple Todo app. The problem I came across is that no matter how I implement the code (followed multiple tutorials) and storyboards, the data doesn't show and the custom cells is not customized (it looks exactly how the default cells look even though I've customized it). I already connected my delegate and dataSource
Edit: I already assigned the reuse identifier
TodosView.swift
import UIKit
class TodosView: UIViewController {
#IBOutlet weak var todosTable: UITableView!
var todos: [Todo] = []
override func viewDidLoad() {
super.viewDidLoad()
todosTable.delegate = self
todosTable.dataSource = self
self.addTodo()
}
func addTodo() {
let todo1 = Todo(text: "My first todo")
let todo2 = Todo(text: "My second todo")
todos = [todo1, todo2]
}
}
extension TodosView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let todo = todos[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell") as! TodoCell
cell.setTodo(todo: todo)
return cell
}
}
TodoCell.swift
import UIKit
class TodoCell: UITableViewCell {
#IBOutlet weak var todoText: UILabel!
func setTodo(todo: Todo) {
todoText.text = todo.text
}
}
Todo.swift
import Foundation
struct Todo {
var text: String
var done: Bool
init(text: String, done: Bool = false) {
self.text = text
self.done = done
}
}
I succeeded in using your code to successfully generate your Todo rows (I did not reloadData() after calling addTodo());
Having proven that your code does work, it leads me to believe that you have an issue somewhere in your Storyboard setup, more-so than you do in your code itself. A few suggestions:
Verify your custom cell is subclassed as a TodoCell. You can do this by clicking on your TodoCell in Interface Builder, and in the Identity Inspector tab, verify you have this set to TodoCell:
This is likely not the issue as your app would more than likely crash if your cells were not subclassed properly.
Verify you have set the cell identifier in Interface Builder. Again, click on the TodoCell in Interface Builder, go to the Attributes Inspector tab, and verify identifier is set to TodoCell:
Also, do make sure that you've actually connected your tableView and todoText UILabel to your code. I see you have #IBOutlets to these items, but if you were copying and pasting from a tutorial, it's possible you typed in the items and never actually connected them. The gray circle next to your IBOutlet for both the tableView and UILabel should be filled in, like so:
If it's empty, you may not have a connection, which could explain the issue. Again, I copied and pasted your code verbatim and set things per the above suggestions; I do not believe that reloadData() or setting the number of sections will help the issue (as your code did not have them and it's working on my end).
You need to reload your tableview after updating the datasource :-
func addTodo() {
let todo1 = Todo(text: "My first todo")
let todo2 = Todo(text: "My second todo")
todos = [todo1, todo2]
todosTable.reloadData()
}
Edit
I also noticed by looking at the JWC comment you didn't have the numberOfSection method implemented so you must also add the number of section delegate method.
You are adding the data to array after creating the tableView.
Change this line :
var todos: [Todo] = []
to
var todos: [Todo]! {
didSet {
self.todosTable.reloadData()
}
}
and in numberOfRowsInSection :
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard (todos != nil) else {
return 0
}
return todos.count
}
you can use this
func addTodo() {
let todo1 = Todo(text: "My first todo")
let todo2 = Todo(text: "My second todo")
todos = [todo1, todo2]
DispatchQueue.main.async {
self.todosTable.reloadData()
}
}
This might help you
I've already looked at the post UITableView.reloadData() is not working. I'm not sure that it applies to my situation, but let me know if I'm wrong.
My app has a tableView. From the main viewController I am opening another viewController, creating a new object, and then passing that object back to the original viewController, where it is added to an array called timers. All of that is working fine. However, when I call tableView.reloadData() in didUnwindFromNewTimerVC() to display the updated contents of the timers array, nothing happens.
NOTE: I have verified that the timers array is updated with the new object. Its count increments, and I can access its members. Everything else in didUnwindFromNewTimerVC() executes normally. The tableView just isn't updating to reflect it.
Here is my code:
import UIKit
class TimerListScreen: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tabelView: UITableView!
var timers = [Timer]()
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tabelView.delegate = self
tabelView.dataSource = self
let tempTimer = Timer(timerLabel: "temp timer")
timers.append(tempTimer)
}
#IBAction func didUnwindFromNewTimerVC(_sender:UIStoryboardSegue){
guard let newTimerVC = _sender.source as? newTimerVC else{return}
newTimerVC.timer.setTimerLabel(timerLabel: newTimerVC.timerLabel.text!)
timers.append(newTimerVC.timer)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as? TimerCell{
let timer = timers[indexPath.row]
cell.updateUI(Timer: timer)
return cell
}else{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 78
}
}
Thank you
Please note the spelling. There are two table view instances: the outlet tabelView and a (pointless) instance tableView.
Reload the data of the outlet
tabelView.reloadData()
and delete the declaration line of the second instance let tableView ....
However I'd recommend to rename the outlet to correctly spelled tableView (you might need to reconnect the outlet in Interface Builder).
And force unwrap the cell
let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as! TimerCell
and remove the if - else part. The code must not crash if everything is hooked up correctly in IB.
I'm trying to combine a CollectionViewwith a TableView, so fare everything works except one problem, which I cant fix myself.
I have to load some data in the CollectionViews which are sorted with the header of the TableViewCell where the CollectionView is inside. For some reason, every time I start the app, the first three TableViewCells are identical. If I scroll a little bit vertically, they change to the right Data.
But it can also happen that while using it sometimes displays the same Data as in on TableViewCell another TableViewCell, here again the problem is solved if I scroll a little.
I think the problem are the reusableCells but I cant find the mistake myself. I tried to insert a colletionView.reloadData() and to set the cells to nil before reusing, sadly this didn`t work.
My TableViewController
import UIKit
import RealmSwift
import Alamofire
import SwiftyJSON
let myGroupLive = DispatchGroup()
let myGroupCommunity = DispatchGroup()
var channelTitle=""
class HomeVTwoTableViewController: UITableViewController {
var headers = ["LIVE","Channel1", "Channel2", "Channel3", "Channel4", "Channel5", "Channel6"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isTranslucent = false
DataController().fetchDataLive(mode: "get")
DataController().fetchDataCommunity(mode: "get")
}
//MARK: Custom Tableview Headers
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return headers[section]
}
//MARK: DataSource Methods
override func numberOfSections(in tableView: UITableView) -> Int {
return headers.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//Choosing the responsible PrototypCell for the Sections
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellBig", for: indexPath) as! HomeVTwoTableViewCell
print("TableViewreloadMain")
cell.collectionView.reloadData()
return cell
}
else if indexPath.section >= 1 {
// getting header Titel for reuse in cell
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
else {
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
}
}
}
My TableViewCell with `CollectionView
import UIKit
import RealmSwift
var communities: Results<Community>?
class HomeVTwoTableViewCellSmall: UITableViewCell{
//serves as a translator from ChannelName to the ChannelId
var channelOverview: [String:String] = ["Channel1": "399", "Channel2": "401", "Channel3": "360", "Channel4": "322", "Channel5": "385", "Channel6": "4"]
//Initiaize the CellChannel Container
var cellChannel: Results<Community>!
//Initialize the translated ChannelId
var channelId: String = ""
#IBOutlet weak var collectionView: UICollectionView!
}
extension HomeVTwoTableViewCellSmall: UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Datasource Methods
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (cellChannel.count)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellSmall", for: indexPath) as? HomeVTwoCollectionViewCellSmall else
{
fatalError("Cell has wrong type")
}
//removes the old image and Titel
cell.imageView.image = nil
cell.titleLbl.text = nil
//inserting the channel specific data
let url : String = (cellChannel[indexPath.row].pictureId)
let name :String = (cellChannel[indexPath.row].communityName)
cell.titleLbl.text = name
cell.imageView.downloadedFrom(link :"link")
return cell
}
//MARK: Delegate Methods
override func layoutSubviews() {
myGroupCommunity.notify(queue: DispatchQueue.main, execute: {
let realm = try! Realm()
//Getting the ChannelId from Dictionary
self.channelId = self.channelOverview[channelTitle]!
//load data from Realm into variables
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.dataSource = self
self.collectionView.delegate = self
print("collectionView layout Subviews")
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCommunity = (cellChannel[indexPath.row].communityId)
let home = HomeViewController()
home.showCommunityDetail()
}
}
Thanks in advance.
tl;dr make channelTitle a variable on your cell and not a global variable. Also, clear it, and your other cell variables, on prepareForReuse
I may be mistaken here, but are you setting the channelTitle on the cells once you create them? As I see it, in your viewController you create cells based on your headers, and for each cell you set TableViewController's channelTitle to be the title at the given section.
If this is the case, then the TableViewCell actually isn't receiving any information about what it should be loading before you call reloadData().
In general, I would also recommend implementing prepareForReuse in your HomeVTwoTableViewCellSmall, since it will give you a chance to clean up any stale data. Likely you would want to do something like set cellChannel and channelId to empty strings or nil in that method, so when the cell is reused that old data is sticking around.
ALSO, I just reread the cell code you have, and it looks like you're doing some critical initial cell setup in layoutSubviews. That method is going to be potentially called a lot, but you really only need it to be called once (for the majority of what it does). Try this out:
override the init with reuse identifier on the cell
in that init, add self.collectionView.dataSource = self and self.collectionView.delegate = self
add a didSet on channelTitle
set channelTitle in the viewController
So the code would look like:
var channelTitle: String = "" {
didSet {
self.channelId = self.channelOverview[channelTitle]!
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.reloadData()
}
}
This way you're only reloading your data when the cell is updated with a new channel, rather than every layout of the cell's views.
Sorry... one more addition. I wasn't aware of how your channelTitle was actually being passed. As I see it, you're using channelTitle as a global variable rather than a local one. Don't do that! remove channelTitle from where it is currently before implementing the code above. You'll see some errors, because you're setting it in the ViewController and accessing it in the cell. What you want is to set the channelTitle on the cell from the ViewController (as I outlined above). That also explains why you were seeing the same data across all three cells. Basically you had set only ONE channelTitle and all three cells were looking to that global value to fetch their data.
Hope that helps a little!
(also, you should be able to remove your else if block in the cellForRowAtIndexPath method, since the else block that follows it covers the same code. You can also delete your viewDidLoad, since it isn't doing anything, and you should, as a rule, see if you can get rid of any !'s because they're unsafe. Use ? or guard or if let instead)