Why won't my tableview cell load when I run this app? - swift

In this simple project! I am using a tableview and trying to display one cell but when I run the app it doesn't show the one cell I want it to. I don't know what I did wrong. I think it has something to do with the constraints. I'm lost!

You need to set the delegate / data source and then reload the tableview within view did load
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
tableview.reloadData()
}

Related

How to automatically refresh table view in view controller upon entering screen?

I'm able to make use of refreshControl and pull down to manually update the screen, but how can i update it automatically every time i enter the screen without having to pull down.
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
configureRefreshControl()
tableView.refreshControl = refreshControl
}
func configureRefreshControl() {
self.refreshControl.addTarget(self, action: #selector(handleRefreshControl), for: .valueChanged)
self.refreshControl.tintColor = UIColor.white
}
#objc func handleRefreshControl() {
updateCommentsOverview()
}
You can call tableView.reloadData() or updateCommentsOverview() either on viewDidLoad. Which will update once Per screen launch. Or call it on viewWillAppear. Which will cause an update every-time you “see” the screen, meaning even when you come back from the background.
I can only guess you mean to use viewWillAppear.
See - https://developer.apple.com/documentation/uikit/uiviewcontroller/1621510-viewwillappear for more info

UiView with TableView and CollectionView not refreshing randomly

I have a UIView that contains a CollectionView and a TableView, both shows data from a service, but sometimes randomly when I enter on this UIView the data is not showing, I am using self.collectionView.reloadData() and self.servicesTableView.reloadData() but it don't shows the information all the times.
A sample of your code would help a lot to figure it out what is happening. But if you do something like this it should work:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
*Do your request*
After that:
self.yourtableview.reloadData()
self.yourcollectionView.reloadData()
}

self.tabBarController.selectedIndex not calling viewDidAppear

I've been looking at and trying all the solutions others have posted to this problem, but I'm still not getting it.
My use case is very simple. I have a viewController with a button, when that button is pressed I want to navigate to another tab and reload the data including an api call.
When using the button, I navigate to the tab fine, but viewDidAppear is not being called.
If on another tab, and navigate using the tab bar, viewDidAppear works fine. Also viewWillAppear is working, but I have to add a manual delay to the functions I want to call so it's not ideal.
So what do I need to do to navigate using self.tabBarController.selectedIndex = 0 and get the functionality of viewDidAppear?
Update: The viewWillAppear method I added gets called but I have to add a delay to my functions in order for them to work, and it's a bit clunky, not ideal. Not sure why viewDidAppear will not work :(
Here is a screenshot of the structure:
I appreciate any help on this one!
The "current" ViewController is my tab index 2:
import UIKit
class PostPreviewVC: UIViewController {
//Here I create a post object and post it to the timeline with the below button
#IBAction func postButtonPressed(_ sender: Any) {
//create the post via Firebase api
self.tabBarController?.selectedIndex = 0
}
}
In my destination viewController:
import UIKit
import Firebase
import SDWebImage
import AVFoundation
class HomeVC: UIViewController {
// MARK: - PROPERTIES
var posts = [Post]()
let refreshControl = UIRefreshControl()
//more properties...
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
configureTableView()
reloadTimeline()
UserFirebase.timeline { (posts) in
self.posts = posts
self.tableView.reloadData()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
_ = self.view
setupUI()
configureTableView()
reloadTimeline()
UserFirebase.timeline { (posts) in
self.posts = posts
self.tableView.reloadData()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewWillAppear")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.reloadTimeline()
self.configureTableView()
}
}
//All the tableview code below here...
}
Added a custom class for my tab bar controller:
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear in tabBar custom Class called")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear in tabBar custom Class called")
}
}
When you are using UITabBarController, the method viewDidLoad will called only once when any UIViewController is loaded in memory. After that, when you are navigating the same UIViewController, then it will load from memory.
In order to overcome this problem, you must divide your code in viewDidLoad & viewDidAppear. So, in viewDidLoad, you only put that code which you want to intialize once throughout the app such as adding UIView's or other things, while in viewDidAppear / viewWillAppear, you can make API calls or some functions which fetches dynamic data.
Finally, when you are calling self.tabBarController.selectedIndex = 0, it will call viewDidLoad only once and viewDidAppear / viewWillAppear every time when you are navigating that UIViewController.
Hope this helps to understand like how UITabBarController works.
For UITabBarController viewDidLoad only gets called once. and your viewWillAppear and viewDidAppear get called multiple times. you can either check if your viewWillAppear gets called or not. because your view will appear gets called before your viewDidAppear it's just like going through the reverse engineering process.
You can also add viewDidAppear method into your UITabBarController custom class. and call its superclass method into it in that way I think it will solve your problem.
Note: In the case of UITabbarController, Always do your UI update task and API calling a task in either
viewWillAppear or viewDidAppear

Swift TableViewDataSource Separate Other Class

I have two examples
First
override func viewDidLoad() {
super.viewDidLoad();
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = TableViewDataSource();
}
Second
var dataSource:TableViewDataSource!;
override func viewDidLoad() {
super.viewDidLoad();
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
dataSource = TableViewDataSource();
tableView.dataSource = dataSource;
}
First example does not work but second works. Are these not the same? And What is the difference between two examples?
first one not works because you have to set a strong reference to it
tableView.dataSource = TableViewDataSource();
while the other have it here
var dataSource:TableViewDataSource!;
//
If you looked to dataSource delegate implementation file , it's declared like this
weak open var dataSource:UITableViewDataSource?
look carfeully to weak so it doesn't retain vars assigned to it that's why the other class instance must be strongly referenced
UITableView dataSource is a weak property. Your first example doesn't work because there is no strong reference to the TableViewDataSource instance that you create. By the time the end of viewDidLoad is reached, the instance is deallocated and dataSource is reset back to nil.
Your second example works because your dataSource property of your class maintains a strong reference to the TableViewDataSource instance. As long as your view controller exists, the TableViewDataSource will exist and the table view's data source will work.

Basic: Connecting multiple (View-)Controllers the right way

I'm trying to set up a login screen (ViewController) that leads - after a successful login - to a user list (UserTableViewController) that is itself part of a navigation controller. On the subsequent screens like the UserTableViewController it should be possible to logout. This would bring the user back to the initial login screen ViewController. I'm really struggling connecting those screens the right way. It must be said that I don't have a lot of experience with the different kinds of segues and/or delegates so, with some research done, I went for some trials:
A successful login on ViewController triggers a modal-segue to the navigation controller (that itself leads to the UserTableViewController)
The UserTableViewController has a logout button that triggers another modal-segue back to the ViewController. I took these modal segues because first, I didn't want to have a hierarchy that leads to an automatically created back-button or similar and second, I didn't want to have any "troubles" between these two screens, one having a navigation controller while the other one doesn't.
...it looks like that's not a way to go. Some things get mixed up after one loop and screens are changing the wrong way. I guess a modal-segue-circle is not possible as there has to be parent and a child at least. Next trial:
A successful login on ViewController triggers a modal-segue / push-segue to the navigation controller (that itself leads to the UserTableViewController)
To return to the login screen I implemented a delegate instead of another segue, triggered when tapping "logout" - here I'm facing the problem that I can't set up the UserTableViewController's delegate in preparingForSegue on ViewController properly. At this point segue.destinationViewController cannot be downcasted to UserTableViewController but only to NavigationController what doesn't allow me to set up the delegate at the destination (UserTableViewController).
The third trial was to do the same like in the second approach but implementing a segue from ViewController
to UserTableViewController directly. That may work but now I don't have any navigation bar anymore at my UserTableViewController...!
Of course I could go for a manual fix in the third solution like inserting a stand-alone navigation bar but neither way seems to be efficient. Therefore I'd very very thankful for some hints, highlighting what I misunderstood (completely) on one side and showing a good way of doing it on the other side. Thanks a lot for any help!
EDIT:
I could try to set the navigation controller as initial view controller and then just let the login screen ViewController being presented/dismissed by the UserTableViewController - is that a practical way or are there widely known best practices for those login view scenario?
Just a visual help:
Please consider using an "unwind" segue. In the ViewController create a function as follows:
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
yourLogoutCode()
}
In UserTableViewController control-drag from the Logout button to the exit symbol of UserTableViewController which is the third button at the top, and select unwindToThisViewController (or whatever name you selected in ViewController).
Now when you click on the Logout button you will return to ViewController and execute the unwindToThisViewController in which you can put your logout code.
Here is an example:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func yourLogoutCode() {
println("Logging Out ...")
}
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
self.yourLogoutCode()
}
}
And the UserTableViewController:
import UIKit
var selectedRow = -1
class UserTableViewController: UITableViewController {
var firstArray = ["Item1","Item2","Item3","Item4"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return firstArray.count
}
let nameOfCell = "Cell"
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(nameOfCell, forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = firstArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRow = indexPath.row
}
}
Notice that the whole connection from the Logout button to the unwind is buried inside the Storyboard. You can confirm that all this is happening by reviewing the Connections Inspector.
This post appears to address exactly the issue you are facing, if I understand correctly.
Also, not meaning to nitpick, but it's "modal", not "modular" :)
I think that could be an acceptable way of doing it:
Login view (ViewController) is initial View Controller
In case of successful login: Modal-segue from login view to the navigation controller that handles subsequent views
Enabling dismiss-segues in Swift by implementing a sub class of UIStoryboardSegue (needed to be written like this: #objc(DismissSegue) class DismissSegue: UIStoryboardSegue {
) that is overriding its perform() to (sourceViewController.presentingViewController!!).dismissViewControllerAnimated(true, completion: nil)
Dismiss-segue from subsequent views (in my case UserTableViewController) back to the login view
Implement the logout-action within prepareForSegue on each of the View Controllers which allows the user to logout
Still, if anyone knows a better practice, I'm happy to hear!