I have been trying to add a tableview in my ViewController unfortunately no data is shown in my table view. numberOfSectionsInTableView , numberOfRowsInSection , cellForRowAtIndexPath are not even getting called.I tried tableview inside ViewController as there is another controls like label , text field's and TableView underneath .Because of these label's and text fields I am unable to use a TableViewController. what I have done ...
Created a UIViewController and added controls like label , text fields etc.
From Object Library I dragged a TableView into my UIViewController.
To manage my data from server I created a Model
class Model {
var name : String
var email : String
init?(name : String , email : String)
{
self.name = name
self.email = email
} }
as I have dynamic data, for the cell I created a swift file under the Subclass UITableViewCell named userinfoCell and connected my labels(two label's inside TableViewCell one for username and another for email) to this class(userinfoCell).
Finally in my UIViewController I have added the following code to populate my Table View.
Inside my class definition I have initialised a global variable like this
var model = [Model]()
I have created a function that adds data to my model
func loadMembers()
{
let member1 = Model(name: "Caprese Salad", email: "caprreswe#gmail.com")!
let member2 = Model(name: "Capresed", email: "pepperoni#gmail.com")!
model += [member1, member2]
}
on my ViewDidLoad I calls this function
override func viewDidLoad() {
super.viewDidLoad()
loadMembers()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Members", forIndexPath: indexPath) as! userinfoCell
let member = model[indexPath.row]
cell.MemberName.text = member.name
cell.MemberEmail.text = member.email
return cell
}
But I am getting an Empty table view. What went wrong in my approach
how can I implement a dynamic UITableView inside UIViewController ???
Modify your loadMembers method like this,
func loadMembers()
{
let member1 = Model(name: "Caprese Salad", email: "caprreswe#gmail.com")!
let member2 = Model(name: "Capresed", email: "pepperoni#gmail.com")!
model += [member1, member2]
// reload the table view after modifying the data source.
self.tableView.reloadData()
}
Ctrl drag from table view to View controller and set datasource and delegate
then call self.tableView.reloadData() in loadMember()
Hold control and drag from tableview to view controller and set data source and delegate like this...
Related
If i click on a particular cell of collection view then the data should be shown related to that cell of collection view in the table view in the same view controller (not in the other view controller)
Regardless of having one or multiple view controllers. A good practice is to have a data structure that fits your visual state. For your case I would expect to have something like
var collectionViewItems: [Any]
var tableViewItems: [Any]
But to be more concrete let's assume that we have a collection view of users where after pressing a certain user a table view should update a list of friends for that user.
A data source like the following could show that:
struct User {
let name: String
let friends: [User]
}
Now to create a structure that is more fit for your display you could have something like this:
class UserFriendsDataModel {
let allUsers: [User]
var selectedUser: User?
init(allUsers: [User]) { self.allUsers = allUsers }
}
In your view controller you would create a new instance of your data model. Probably in viewDidLoad but this all depends on how you collect your data.
For instance
class ViewController: UIViewController {
private var dataModel: UserFriendsDataModel?
override func viewDidLoad() {
super.viewDidLoad()
dataModel = .init(allUsers: [
.init(name: "Me", friends: [.init(name: "You", friends: [])])
])
}
}
Now your data source implementations can use dataModel?.allUsers for collection view and dataModel?.selectedUser?.friends for your table view.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataModel?.selectedUser?.friends.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = dataModel?.selectedUser?.friends[indexPath.row].name
return cell
}
}
Now all that is left is interaction. When a collection view cell is pressed you would do
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let user = dataModel?.allUsers[indexPath.row]
dataModel?.selectedUser = user
tableView?.reloadData()
}
}
note here that a new user is being selected using selectedUser = user and then a reload is triggered for table view calling tableView?.reloadData() which will force the table view to call data source methods and get the new information.
An even better approach may be to listen for changes in selected user by creating your own delegates on your data model and respond to that. But that is already out of scope for this question.
I hope this puts you on the right path.
On didSelectItemAt method of collectionView you reset the data source of tableView and reload tableView that should do the job.
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 have a UICollectionView containing a matrix of text fields. Since the number of text fields per row can be high, I implemented my custom UICollectionViewLayout to allow scrolling in this screen. When the user submits the form, I want to validate the value entered in every text field, thus I need to loop all the cells.
The problem that I'm facing is that I was using collectionView.cellForItemAtIndexPath for this but then found out that it fails with invisible cells, as I saw on this this question.
I understand the approach in the answer to store the values of the data source (in arrays) and then to loop the data source instead, however I don't know how to do this. I tried using function editingDidEnd as an #IBAction associated to the text field but I don't know how to get the "coordinates" of that text field. My idea behind this is to store the value just entered by the user in a two-dimensions array that I'll use later on to loop and validate.
Many thanks for your help in advance!
You don't have to loop invisible cells. Keep using datasource approach. What you are looking for is the way to map textFields to the datasource.
There are many solutions, but the easy one is using Dictionary.
Here's the code for UITableViewDataSource but you can apply it to UICollectionViewDataSource
class MyCustomCell: UITableViewCell{
#IBOutlet weak var textField: UITextField!
}
class ViewController: UIViewController{
// datasource
var textSections = [ [ "one" , "two" , "three"] , [ "1" , "2" , "3"] ]
// textField to datasource mapping
var textFieldMap: [UITextField:NSIndexPath] = [:]
// MARK: - Action
func textChanged(sender: UITextField){
guard let indexPath = textFieldMap[sender] else { return }
guard let textFieldText = sender.text else { return }
textSections[indexPath.section][indexPath.row] = textFieldText
}
#IBAction func submitButtonTapped(){
// validate texts here
for textRow in textSections{
for text in textRow{
if text.characters.count <= 0{
return print("length must be > 0.")
}
}
}
performSegueWithIdentifier("goToNextPage", sender: self)
}
}
extension ViewController: UITableViewDataSource{
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("identifer") as! MyCustomCell
// set value corresponds to your datasource
cell.textField.text = textSections[indexPath.section][indexPath.row]
// set mapping
textFieldMap[cell.textField] = indexPath
// add action-target to textfield
cell.textField.addTarget(self, action: "textChanged:", forControlEvents: .EditingChanged)
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textSections[section].count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return textSections.count
}
}
I am trying to create a subclass of UITableView in Swift.
var tableScroll = HorizontalScrollTableView(frame: CGRectMake(15, 15, cell.contentView.frame.width - 30, cell.contentView.frame.height - 30))
cell.contentView.addSubview(tableScroll)
I add the table in the following way and then I have a HorizontalScrollTableView swift file with
import UIKit
class HorizontalScrollTableView : UITableView {
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
...
However the Table presents like a normal default table and the functions in HorizontalScrollTableView do not override the default ones.
You're probably looking to override numberOfSections instead of numberOfSectionsInTableView:, which is a method on the UITableViewDataSource protocol. However, I believe it's more wise to create your own subclass of UITableViewController or your class conforming to UITableViewDataSource and set it as your table view's delegate rather than of the table view itself. Subclassing UIView and its descendants is usually reserved for customizing view-specific functionality and attributes, such as adding custom drawing or custom touch handling.
I went ahead and made a simple example using a Playground. You can see table view isn't subclassed, but instead there is a view controller which serves as the table view's delegate and a stand-in data source for the table view.
The data source provides the row data for the table view and the delegate (which is also the view controller) provides the cells.
This is a skeleton for how I normally set up my table views, although I would use an XIB generally.
import UIKit
// data for table view is array of String
let rowData = [ "Chicago", "Milwaukee", "Detroit", "Lansing" ]
//
// simple subclass of UITableViewCell that has an associated ReuseIdentifier
// and a value property. Setting the value property changes what the cell displays
//
public class TableViewCell : UITableViewCell
{
public static let ReuseIdentifier = "TableViewCell"
var value:AnyObject? {
didSet {
self.textLabel!.text = value as? String
}
}
}
//
// Simple implementation of a table view data source--just contains one String per row
// You could change your rowData to be an array of Dictionary for richer content possibilities.
// You could also load your data from a JSON file... for example
//
class TableViewDataSource : NSObject, UITableViewDataSource
{
var rowData:[String]?
init( rowData:[String] )
{
self.rowData = rowData
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return rowData?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
assert( indexPath.section == 0 ) // we only have 1 section, so if someone section ≠ 0, it's a bug
var cell:TableViewCell! = tableView.dequeueReusableCellWithIdentifier( "Cell" ) as? TableViewCell
if cell == nil
{
cell = TableViewCell( style: .Default, reuseIdentifier: "Cell" )
}
cell.value = self.rowData![ indexPath.row ]
return cell
}
}
// This is the view controller for our table view.
// The view controller's view happens to be a table view, but your table view
// could actually be a subview of your view controller's view
class TableViewController : UIViewController, UITableViewDelegate
{
// data source for our table view, lazily created
lazy var tableViewDataSource:TableViewDataSource = TableViewDataSource( rowData: rowData )
override func loadView()
{
// our view is a UITableView
let tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self.tableViewDataSource // using a self-contained data source object for this example
self.view = tableView
}
}
let window:UIWindow! = UIWindow()
window.rootViewController = TableViewController()
window.makeKeyAndVisible() // click the "preview eyeball" to see the window
I need to create a drill down effect with my table views that will expand four table views deep for each of my original cells in my master table view. So far i was successful in populating the master view, and second table view accordingly with this Object Oriented Method, here is the code in my master table view:
class FirstTableViewController: UITableViewController {
let aSport:[Sport] = {
var basketball = Sport()
basketball.name = "Basketball"
basketball.sportCategories = {
var temp = ["International Basketball","Wheelchair Basketball","Beach Basketball","Deaf Basketball","Dwarf Basketball"]
temp.sort(<)
return temp
}()
var golf = Sport()
golf.name = "Golf"
golf.sportCategories = {
var temp = ["Miniature Golf","Dart Golf","Sholf","Disc Golf","Footgolf"]
temp.sort(<)
return temp
}()
var football = Sport()
football.name = "Football"
football.sportCategories = {
var temp = ["Flag Football","Indoor Football","Arena Football","Non-Tackle Football","Paper Football"]
temp.sort(<)
return temp
}()
var swimming = Sport()
swimming.name = "Swimming"
swimming.sportCategories = {
var temp = ["Competitive Swimming","Synchronized Swimming","Duo Swimming","Relay Swimming"]
temp.sort(<)
return temp
}()
return [basketball,golf,football,swimming]
}()
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return aSport.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = aSport[indexPath.row].name
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let cell = sender as! UITableViewCell
let row = tableView.indexPathForCell(cell)?.row
let detail = segue.destinationViewController as! SecondTableViewController
detail.selectedSport = aSport[row!]
}
}
class Sport {
var name: String = "sport name"
var sportCategories: NSArray = ["variations of selected sport"]
var detailText: NSArray = ["little description of sport"]
}
here is the code in my second table view controller:
class SecondTableViewController: UITableViewController {
var selectedSport = Sport();
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedSport.sportCategories.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Custom", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = selectedSport.sportCategories[indexPath.row] as? String
cell.detailTextLabel!.text = selectedSport.detailText[indexPath.row] as? String
return cell
}
}
here are screenshots from my simulator so you get a visual:
https://40.media.tumblr.com/6ee47f49c2b223b514f8067c68ac6af1/tumblr_nqbe74HYGo1tupbydo1_500.png
when basketball is selected:
https://41.media.tumblr.com/ced0ee2ff111a557ec3c21f1fb765adf/tumblr_nqbe74HYGo1tupbydo2_500.png
Now as you can see, i was able to populate the first two views by creating a custom class, creating custom objects of that class and using the properties within the class. Now my dilemma is, each of the "sportCategories" properties have their OWN table views to expand to which will consist of a whole list of names of players in that respective sport. Now what method should i go about doing this? should i create a whole new class in my second table view controller to give the sportsCategories their own properties? or is there a way i can already expand off the work I've already done? a more efficient way?
If you only have one detail controller, you don't need the switch statement at all. The best (most object oriented) way to do this would be to use custom objects to populate your cells. You could create a Sport object that would have two properties, name (NSString), and categories (NSArray). In the master table view controller, you would create all the Sport objects, and add them to an array, sports. You would use sports to populate the array with,
let aSport = sports[indexPath.row] as! Sport
cell.textLabel.text = aSport.name
In didSelectRowAtIndexPath, you create an instance of DetailViewController, set the value of a property in that class (lets call it selectedSport), and push it,
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let detailVC = DetailViewController()
detailVC.selectedSport = sports[indexPath.row]
navigationController?.pushViewController(detailVC, animated: true)
}
In DetailViewController, you would use selectedSport.categories to populate the table view. Since you have access to the whole Sport object, you could use selectedSport.name to provide the title for the controller.
If the hierarchy is just from the main view controller to one of 21 specific view controllers and back (not 21 nested view controllers), and the behaviour of all these 21 tables is very similar, then there isn't really much reason to have 21 view controllers. Have one view controllers for all the 21 displays, then before you switch to it you tell it what data to display, and of course it has to be able to display the data of all 21 views. (If you had 15 views looking one way and 6 views looking a different way, you would use two view controllers).
(I don't like making things depending on a row index. That just makes it very difficult if you want to arrange the rows in a different way).