I have an iOS project I'm working on in Xcode 7 using Swift 2. I have an array called details with a dictionary which includes a String and an Int value. The Int is called cellOrder in the Class and the idea is to sort the details array in a TableView with a sort based on the cellOrder Int value.
The array shows the String values which are names. I looked here to try and implement this into my project with no success.
Here is my array:
// Array of data for the TableView
var details = [ProjectDetails]()
Here is my TableView Code:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return details.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = details[indexPath.row]
return cell
}
How do I do the sort and where would I put the code, ViewDidLoad() or maybe cellForRowAtIndexPath?
UPDATE:
My ViewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
// TableView Sorting
details.sortInPlace({$0.cellOrder < $1.cellOrder})
tableView.delegate = self
...
}
Update 2:
My ProjectDetails class for the array data is:
import UIKit
class ProjectDetails: NSObject, NSCoding {
// MARK: Properties
var fileName: String
var cellOrder: Int
...
}
To sort the array you would just do
details.sortInPlace({$0.cellOrder < $1.cellOrder})
in viewDidLoad, or you could use
details.sort({$0.cellOrder < $1.cellOrder})[indexPath.row]
in cellForRowAtIndexPath, but that could prove dangerous if you do not keep track of your array.
One will sort the array in place, one will return a sorted immutable array.
If you insist on sorting immutably (has its pros and cons beyond this scope) assign it to another array and use that in your logic.
You must sort the itens in this array details[], after that, you can call tableView.reloadData() to reload the info.
In the cellForRowAtIndexPath you must just take the data to fill the cell, so NO SORT THERE!
Sort anywhere else, before call the function to reload.
Related
I'm building an app, where I got several sections in an UITableView. My current solution is collecting my data in a dictionary, and then pick one key for every section. Is there a better solution?
One of the good ways to it - direct model mapping, especially good with swift enums.
For example you have 2 different sections with 3 different type of rows. Your enum and ViewController code will look like:
enum TableViewSectionTypes {
case SectionOne
case SectionTwo
}
enum TableViewRowTypes {
case RawTypeOne
case RawTypeTwo
case RawTypeThreeWithAssociatedModel(ModelForRowTypeNumberThree)
}
struct ModelForRowTypeNumberThree {
let paramOne: String
let paramTwo: UIImage
let paramThree: String
let paramFour: NSData
}
struct TableViewSection {
let type: TableViewSectionTypes
let raws: [TableViewRowTypes]
}
class ViewController: UIViewController, UITableViewDataSource {
var sections = [TableViewSection]()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].raws.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let raw = sections[indexPath.section].raws[indexPath.row]
switch raw {
case .RawTypeOne:
// Here return cell of first type
case .RawTypeTwo:
// There return cell of second type
case .RawTypeThreeWithAssociatedModel(let modelForRawTypeThree):
// And finally here you can use your model and bind it to your cell and return it
}
}
}
What benefits? Strong typization, explicit modelling, and explicit handling of your various cell types. The only simple thing that you have to do in that scenario it is parse your data into this enums and structs, as well as you do it for your dictionaries.
Here is a quick example that I wrote. Please note, it error-prone since it is not checking wether the keys exists not does it create a proper cell.
You could do this with a dictionary as well, since you can iterate over its content.
Hope it helps:
class AwesomeTable: UITableViewController {
private var tableContent: [[String]] = [["Section 1, row 1", "Section 1, row 2"], ["Section 2, row 1", "Section 2, row 2"]]
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return tableContent.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableContent[section].count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
let item = tableContent[indexPath.section][indexPath.row]
cell.textLabel?.text = item
return cell
}
}
Implement the table view datasource as follows:-
1) Set number of sections = no of keys in dictionary
2) No of rows in section = no of values in dictionary at index(section)
I am completely new to swift and I am pretty find it difficult to find that its quite different than obj.C
I have a difficulty while populating the table view.
my coding to populate goes as follows -
class DetailTableViewController: UITableViewController
{
var items = []
override func viewDidLoad() {
super.viewDidLoad()
items=["dodnf","dgfd"]
Item()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("cellreuse", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.item]
return cell
}
When I am writing the following code cell.textLabel?.text = items[indexPath.item] I am getting an error message as- AnyObject is not convertible to String
So what is my error and why is it so?
Just add the type when declaring the data source array:
var items : [String] = []
That's all. In cellForRowAtIndexPath the compiler can infer the type.
However the proper syntax is supposed to be
cell.textLabel?.text = items[indexPath.row]
The problem is that you have declared items as an array without specifying the type of the elements (var items = []). Therefore, when you try to get an element from the array, the compiler errors because it cannot guarantee the type of the element is what you are expecting.
You need to specify the type of the items in the array. You can do this in either one of the two possible stages:
when you declare the array (preferred to leverage Swift's type safety):
var items = [String]() // or its equivalent: var items : [String] = []
// Alternatively, if you know it at the time of declaration you can just do the following and let
// Swift's type inference do its work
var items = ["dodnf","dgfd"]
OR when you get an element from the array:
cell.textLabel?.text = items[indexPath.item] as? String
You can learn more about Collection Types in Swift in The Swift Programming Language.
I have written this code:
//Basic methods
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.DevicesTableView.registerClass(UITableViewCell.self,forCellReuseIdentifier: "cell")
self.DevicesTableView.dataSource = self
}
var array = ["one","two"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return array.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: UITableViewCell! = self.DevicesTableView
.dequeueReusableCellWithIdentifier("cell") as UITableViewCell!
cell.textLabel!.text = self.array[indexPath.row]
return cell
}
So the code works pretty well and shows me the tableview filled with:
Row1 "One"
Row2 "Two"
But how can I fill the TableView not at the beginning of my program but anytime else? I tried to call the methods below in an other function that viewDidLoad but it doest't work... why?
self.DevicesTableView.registerClass(UITableViewCell.self,forCellReuseIdentifier: "cell")
self.DevicesTableView.dataSource = self
EDIT:
I would like to display an array which isn't initialized at the beginning of my program. It contains some BLE devices after the user searched for them.
Its very simple
Update your data in your array
Then reload your tableview by using
your_tableview.reloadData()
Ex:
var array = ["one","two","Three","Four"] // your array will take no.of.row in section
DevicesTableView.reloadData()
Table views have a reloadData method which does what you want.
I'd like to get started using swift to make a small list based application. I was planning on using two table view controllers to display the two lists, and was wondering if it were possible to have them share a common data source.
Essentially the data would just be an item name, and two integers representing the amount of the item owned vs needed. When one number increases, the other decreases, and vice versa.
I figured this might be easiest to do using a single data source utilized by both table view controllers.
I did some googling on shared data sources and didn't find anything too useful to help me implement this. If there are any good references for me to look at please point me in their direction!
You can create one data source class and use it in both view controllers:
class Item {
}
class ItemsDataSource: NSObject, UITableViewDataSource {
var items: [Item] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
//setup cell
// ...
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
}
class FirstViewController : UITableViewController {
var dataSource = ItemsDataSource()
override func viewDidLoad() {
self.tableView.dataSource = dataSource
self.tableView.reloadData()
}
}
class SecondViewController : UITableViewController {
var dataSource = ItemsDataSource()
override func viewDidLoad() {
self.tableView.dataSource = dataSource
self.tableView.reloadData()
}
}
use singleton design pattern, it means both tables will get data source from the same instance
class sharedDataSource : NSObject,UITableViewDataSource{
static var sharedInstance = sharedDataSource();
override init(){
super.init()
}
//handle here data source
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
}
}
var tableOne = UITableView();
var tableTwo = UITableView();
tableOne.dataSource = sharedDataSource.sharedInstance;
tableTwo.dataSource = sharedDataSource.sharedInstance;
The first argument to the delegate method is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
At that point, your one Datasource delegate can decide which table view is wanting a cell, for example, and return results accordingly.
At first the indexpath are called in sequence of 0,1,2... but after clicking on the table view , the func tablview is called with indexpath that seems completely random , and hence i am unable to reproduce the same data . I am using array to correlate the rows of the table .
The code is here:
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return myList.count
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
// Configure the cell...
println("table view --> ")
let CellID : NSString = "Cell"
var cell : UITableViewCell = tableView?.dequeueReusableCellWithIdentifier(CellID)as UITableViewCell
var VehicleName = cell.viewWithTag(1) as UILabel
var VehicleNumber = cell.viewWithTag(2) as UILabel
if let ip = indexPath
{
VehicleName.text = arrayData[ip.row*2];
VehicleNumber.text = arrayData[ip.row*2+1];
}
}
return cell
}
You have a design problem. You need to be able to supply the data as requested in a random access fashion.
Study the documentation on UITableView and `UITableViewDataSource'.
The API calls cellForRowAtIndexPath as it needs the data, it just requests the data that it needs to display, not all the data each time. It only creates the cells that are being displayed. If you had 1000 rows of data and were displaying rows 900 through say 903 it only needs that data. If the view scrolls to display one more row it only needs more data from row 904.
There is absolutely no guarantee in what order of indexPaths cellForRowAtIndexPath gets called. It is upon the UIKit framework.
You need to, and can, fully control how your data source array holds data. In this case, it is arrayData - I also have a feeling that this is in some way related to myList, but it is not visible from your code snippet. Work on how elements are arranged within arrayData and then you will have expected elements in all cells.