Swift generic enums for as TableView data source - ios

I want to create a UITableViewController that accepts an enum as it's data source.
The thing is, I have quite a few enums that I want it to be able to handle.
I created a protocol called TableViewSelectable and created some enums that conform to it like so:
protocol TableViewSelectable {
}
enum Genders: Int, TableViewSelectable {
case male = 0, female
static let allKeys = [male, female]
static let allNames = [male.getName, female.getName]
var getName: String {
switch self {
case .male:
return "Male"
case .female:
return "Female"
}
}
}
enum Goals: Int, TableViewSelectable {
case gainMuscleMass, getTrimFit, loseWeight
static let allKeys = [gainMuscleMass, getTrimFit, loseWeight]
static let allNames = [gainMuscleMass.getName, getTrimFit.getName, loseWeight.getName]
var getName: String {
switch self {
case .gainMuscleMass:
return "Gain muscle mass"
case .getTrimFit:
return "Get trim & fit"
case .loseWeight:
return "Lose weight"
}
}
}
And I have created an instance variable on my UITableViewController like so:
class ChoiceListTableViewController: UITableViewController {
var data: TableViewSelectable?
}
The problem is I have no idea how to go from here.
What I want to have is the option to give that UITableViewController any enum that conforms to TableViewSelectable in order to use as its data source.
I want to access it on the UITableViewController like this:
final class ChoiceListTableViewController: UITableViewController {
var data: TableViewSelectable?
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.clearsSelectionOnViewWillAppear = false
}
}
// MARK: Table View Data Source & Delegate
extension ChoiceListTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.allKeys.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChoiceTableViewCell", for: indexPath)
cell.textLabel?.text = data.allNames[indexPath.row]
return cell
}
}
Help please? :)

The issue with your current implementation is that the allKeys and allNames static properties that you should be using in your UITableViewDataSource method are not part of the TableViewSelectable protocol, so you cannot access them using the data variable.
You should modify your protocol to include these.
protocol TableViewSelectable {
static var allKeys: [TableViewSelectable] {get}
static var allNames: [String] {get}
}
You also need to modify your enums conforming to it accordingly.
enum Genders: Int, TableViewSelectable {
case male = 0, female
static let allKeys:[TableViewSelectable] = [male, female]
static let allNames = [male.getName, female.getName]
var getName: String {
switch self {
case .male:
return "Male"
case .female:
return "Female"
}
}
}
enum Goals: Int, TableViewSelectable {
case gainMuscleMass, getTrimFit, loseWeight
static let allKeys:[TableViewSelectable] = [gainMuscleMass, getTrimFit, loseWeight]
static let allNames = [gainMuscleMass.getName, getTrimFit.getName, loseWeight.getName]
var getName: String {
switch self {
case .gainMuscleMass:
return "Gain muscle mass"
case .getTrimFit:
return "Get trim & fit"
case .loseWeight:
return "Lose weight"
}
}
}
Then you can simply use data.allKeys and data.allNames to access the corresponding element of these arrays in your data source methods.
class ChoiceListTableViewController: UITableViewController {
var data:TableViewSelectable.Type! // make sure that this is actually initialized before accessing it
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.allKeys.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath)
cell.textLabel?.text = data.allNames[indexPath.row]
return cell
}
}

Related

ObjectMapper displayed model in TableView Swift

I am new to Swift , i parsing my JSON by using ObjectMapper but I want data displayed in TableView
I do not know how to do that
My Model:
import Foundation
import ObjectMapper
import SwiftyJSON
class AllCnannelModel : Mappable {
var id : Int?
var name: String?
var url : URL?
var picture : URL?
var category_id: Int?
required init?(map: Map) {}
func mapping(map: Map) {
id<-map["id"]
name<-map["name"]
url<-map["url"]
picture<-map["picture"]
category_id<-map["category_id"]
}
}
My TableView :
var name = [String]()
var urlChannel = [URL]()
override func viewDidLoad() {
super.viewDidLoad()
let URL = "http://52.50.138.211:8080/ChanelAPI/chanels"
Alamofire.request(URL).responseArray { (response: DataResponse<[AllCnannelModel]>) in
let channnellArray = response.result.value
if let channnellArray = channnellArray {
for channel in channnellArray {
self.name.append(channel.name!)
}
}
self.tableView.reloadData()
}
}
I can describe one type of data in the array and display:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.name.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
cell.textLabel?.text = self.name[indexPath.row]
return cell
}
but i want all type of data in one array and display TableViewController
How can you implement?
Did you implemented numberOfSections(in tableView: UITableView) -> Int ?

Use of undeclared type 'Player'

I had an error in the variables player, I find fault has not been able to finish
import UIKit
class PlayersViewController: UITableViewController {
var players:[Player] = playersData
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return players.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PlayerCell", forIndexPath: indexPath)
let player = players[indexPath.row] as Player
cell.textLabel?.text = player.name
cell.detailTextLabel?.text = player.game
return cell
}
}
You need to define the Player type first. You may think of something like this:
class PlayersViewController: UITableViewController {
var players: [Player] = []
var xplayers: [XPlayer] = []
// struct type of Player
struct Player {
var name : String = ""
var height : Int = 0
}
// OR
// tuple type of XPlayer
typealias XPlayer = (String, Int)
func someMethod() {
self.players.append(Player(name: "John Herbert", height: 160))
// OR
self.xplayers.append(("John Herbert", 160))
}
}

Pass data from struct array to new tableview controller

I am trying to pass data from my Cheats struct into a new tableviewcontroller to populate the new table with the relevant info.
I have done research but am new to swift.
I do have experience in php but transferring my knowledge is proving quite difficult...
In my prepare for segue class i receive a few errors:
Definition conflicts with previous value:
var DataPass = CheatsArray[indexPath.row]
Cannot assign value type 'String' to '[String]':
DestViewController.CheatsArray = DataPass.name
Here is a copy of my current 3 files
Structs and arrays:
struct Game {
var name : String
var cheats : [Cheat]
}
struct Cheat {
var name : String
var code : String
var description : String
}
// Create Our Game Info And Cheats / Codes For Each Game!
//-------------------------------------------------------
let COD4 = Game(name: "Call Of Duty 4", cheats: [Cheat(name: "Cheat", code: "Code", description: "Description")])
let GTAV = Game(name: "Grand Theft Auto 5", cheats: [Cheat(name: "Cheat", code: "Code", description: "Description")])
// Place Our New Games Inside This Array!
//---------------------------------------
let ArrayOfGames = [COD4,GTAV]
GameListController:
import Foundation
import UIKit
class GamesListViewController: UITableViewController {
var CheatsArray = [Game]()
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 ArrayOfGames.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = ArrayOfGames[indexPath.row].name
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
let DestViewController = segue.destinationViewController as! CheatsListViewController
let DataPass : Game
var DataPass = CheatsArray[indexPath.row]
DestViewController.CheatsArray = DataPass.name
}
}
CheatListController:
class CheatsListViewController: UITableViewController {
var CheatsArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return CheatsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath)
cell.textLabel?.text = CheatsArray[indexPath.row]
return cell
}
}
The first error is because you defined DataPass twice, one row after another. You can't do that in the same scope. Change the name of one.
The second error is because you cannot assign value type 'String' to '[String]'. Just looking at CheatsArray and name variables, it's clear the first is an array and the second is most likely a string. You might want to append the name to the array.

Ios swift change type of results array

How can i change type of tableview results array.
var results: [Group] = []
now i use that viewcontroller for 2 purposes, same design and same table, but another results array.
Now i need to change it in this didSet:
var article:Article? {
didSet {
var results: [Article] = [] // something like this
getArticleOrderData()
}
}
when article is set, to change type of results array to [Article].
Is that possible?
Or should i just make another and another viewcontroller in storyboard for articles?
Declare a protocol where both types conform to and use the protocol as collection type for the table view.
Very simple example:
protocol Resultable {
var name : String {get set}
}
class Article : Resultable {
var name = "article"
}
class Group : Resultable {
var name = "group"
}
class MYTableViewController : UITableViewController {
var results = [Resultable]()
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let result = results[indexPath.row]
cell.textLabel!.text = result.name
return cell
}
}

Generic class when inherit from UICollectionViewDataSource in swift

when I try to create a generic class which implement UICollectionViewDataSource in swift it say that my class does not conform to protocol (and sometime Xcode crash).
Does it mean that we can't create generic data provider for UICollectionView and that we have to duplicate code ?
Here is the generic code :
// Enum protocol
protocol OptionsEnumProtocol
{
typealias T
static var allValues:[T] {get set}
var description: String {get}
func iconName() -> String
}
// enum : list of first available options
enum Options: String, OptionsEnumProtocol
{
typealias T = Options
case Color = "Color"
case Image = "Image"
case Shadow = "Shadow"
static var allValues:[Options] = [Color, Image, Shadow]
var description: String {
return self.rawValue
}
func iconName() -> String
{
var returnValue = ""
switch(self)
{
case .Color: returnValue = "color_icon"
case .Image: returnValue = "image_icon"
case .Shadow: returnValue = "shadow_icon"
}
return returnValue
}
}
// class to use as the uicollectionview datasource and delegate
class OptionsDataProvider<T>: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
private let items = T.allValues
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
// Configure the cell
cell.iconFileName = item.iconName()
cell.labelView.text = item.description
return cell
}
}
But because it failed I have to use this non generic form instead :
enum Options: String
{
case Color = "Color"
case Image = "Image"
case Shadow = "Shadow"
static var allValues:[Options] = [Color, Image, Shadow]
var description: String {
return self.rawValue
}
func iconName() -> String
{
var returnValue = ""
switch(self)
{
case .Color: returnValue = "color_icon"
case .Image: returnValue = "image_icon"
case .Shadow: returnValue = "shadow_icon"
}
return returnValue
}
}
class OptionsDataProvider: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
private let items = Options.allValues
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
// Configure the cell
cell.iconFileName = item.iconName()
cell.labelView.text = item.description
return cell
}
}
which obligate me to duplicate the class for each enum type I have.
Exact error :
You are right, it is not possible to write a generic class. However, I have found a workaround. It doesn't use enums and so maybe you don't find it very useful. However, it achieves what you want - you are getting a collection view data source which can be used with different classes providing necessary data. Here is the code:
protocol OptionsProviderProtocol
{
func allValues() -> [OptionsItem]
}
class OptionsItem:NSObject {
let itemDescription:String
let iconName:String
init(iconName:String,description:String) {
self.itemDescription = description
self.iconName = iconName
}
}
// class stores first available options
class Options: NSObject, OptionsProviderProtocol
{
let color = OptionsItem(iconName: "color_icon", description: "Color")
let image = OptionsItem(iconName: "image_icon", description: "Image")
let shadow = OptionsItem(iconName: "shadow_icon", description: "Shadow")
func allValues() -> [OptionsItem] {
return [color, image, shadow]
}
}
// class to use as the uicollectionview datasource and delegate
class OptionsDataProvider: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
private var items:[OptionsItem] = []
convenience init(optionsProvider:OptionsProviderProtocol) {
self.items = optionsProvider.allValues()
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
// Configure the cell
cell.iconFileName = item.iconName()
cell.labelView.text = item.description
return cell
}
}
If you have any questions please let me know.
When you inherit from a protocol you must implement all required methods. Swift 2 will change this somewhat. Perhaps you really want to inherit from a class.
I had the similar problem/question when I was trying to inherit Generic class from NSOperation class. xCode didn't give me a compile error because there were no protocols involved, instead my override func main() was simply never called :)
Anyway... If you follow workaround that mr. Topal Sergey advised, you can achieve exactly what you want relatively easily.
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView?
private var defaultDataProvider = OptionsDataProvider<Options>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
collectionView?.delegate = defaultDataProvider
collectionView?.dataSource = defaultDataProvider
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// Enum protocol
protocol OptionsEnumProtocol {
static var allValues: [OptionsEnumProtocol] {get set}
var description: String {get}
func iconName() -> String
}
// enum : list of first available options
enum Options: String, OptionsEnumProtocol {
case Color = "Color"
case Image = "Image"
case Shadow = "Shadow"
static var allValues: [OptionsEnumProtocol] = [Color, Image, Shadow]
var description: String {
return self.rawValue
}
func iconName() -> String
{
var returnValue = ""
switch(self)
{
case .Color: returnValue = "color_icon"
case .Image: returnValue = "image_icon"
case .Shadow: returnValue = "shadow_icon"
}
return returnValue
}
}
class OptionsDataProviderWrapper: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// MARK: protocols' funcs
final func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return wrapperCollectionView(collectionView, numberOfItemsInSection: section)
}
final func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return wrapperCollectionView(collectionView, cellForItemAtIndexPath: indexPath)
}
// MARK: for override
func wrapperCollectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 0
}
func wrapperCollectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return UICollectionViewCell()
}
}
class OptionsDataProvider<T: OptionsEnumProtocol>: OptionsDataProviderWrapper {
private let items = T.allValues
override func wrapperCollectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func wrapperCollectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("reuseId", forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
cell.labelView?.text = item.description
return cell
}
}
class GenericIconLabelCell: UICollectionViewCell {
#IBOutlet weak var labelView: UILabel?
}
The key here is to create OptionsDataProviderWrapper that is not a generic and implements all your protocols. The only thing that it does - it redirects calls to another functions like func wrapperCollectionView...
Now you can inherit your Generic class from this OptionsDataProviderWrapper and override that wrapper functions.
Note: you have to override exactly wrapper functions because native func collectionView... functions will not be called in your generic subclass similarly to my issue with NSOperation. That's why I marked native functions with final.

Resources