How to connect swift codes and storyboard? - ios

I am trying to figure out a way to use the Pod ColorMatchTabs, however its example is doing it programmatically and I can't find a way to use them in my project. I will summarize this:
To use the pod, I have to follow this protocols to install the tabs in my viewController:
extension ExampleViewController: ColorMatchTabsViewControllerDataSource {
func tabsViewController(_ controller: ColorMatchTabsViewController, iconAt index: Int) -> UIImage {
return TabItemsProvider.items[index].normalImage
}
func tabsViewController(_ controller: ColorMatchTabsViewController, hightlightedIconAt index: Int) -> UIImage {
return TabItemsProvider.items[index].highlightedImage
}
func numberOfItems(inController controller: ColorMatchTabsViewController) -> Int {
return TabItemsProvider.items.count
}
func tabsViewController(_ controller: ColorMatchTabsViewController, viewControllerAt index: Int) -> UIViewController {
return StubContentViewControllersProvider.viewControllers[index]
}
func tabsViewController(_ controller: ColorMatchTabsViewController, titleAt index: Int) -> String {
return TabItemsProvider.items[index].title
}
func tabsViewController(_ controller: ColorMatchTabsViewController, tintColorAt index: Int) -> UIColor {
return TabItemsProvider.items[index].tintColor
}
These are the protocols to handle how the tabs look like, how many items and my difficulties are in the part calling UIViewController. I assume it return which controllers are used based on which tab, and here goes the code in StubContentViewControllersProvider:
import UIKit
import ColorMatchTabs
class StubContentViewControllersProvider {
static let viewControllers: [UIViewController] = {
let productsViewController = StubContentViewController()
productsViewController.type = .products
let venuesViewController = StubContentViewController()
venuesViewController.type = .venues
let reviewsViewController = StubContentViewController()
reviewsViewController.type = .reviews
let usersViewController = StubContentViewController()
usersViewController.type = .users
return [productsViewController, venuesViewController, reviewsViewController, usersViewController]
}()
}
I assume we are calling all the controllers from StubContentViewController and here is how it looks like:
import UIKit
class StubContentViewController: UITableViewController {
enum `Type` {
case products, venues, reviews, users
}
var type: Type!
fileprivate var objects: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
setupDataSource()
}
fileprivate func setupTableView() {
tableView.backgroundColor = UIColor.clear
tableView.allowsSelection = true
tableView.separatorColor = UIColor.clear
tableView.register(UINib(nibName: "ExampleTableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
}
fileprivate func setupDataSource() {
if type == .products || type == .reviews {
self.objects = [UIImage(named: "product_card1")!, UIImage(named: "product_card2")!]
} else if type == .venues || type == .users {
self.objects = [UIImage(named: "venue_card1")!, UIImage(named: "venue_card2")!]
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ExampleTableViewCell
let image = objects[(indexPath as NSIndexPath).row]
cell.apply(image)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.bounds.width / 1.4
}
}
So my question here is, I am setting up a TableViewController and reference it to StubContentViewController in storyboard, but why when I make changes in the storyboard, it doesn't make any effect? And for example I want to perform a segue for the table cell, I couldn't find any way to make it via the storyboard?

Related

Filtering query for Realm

I have a function which prints all the objects in my realm table to a table view. I would like to be able to filter these objects by their "muscle" property.
Here's my DB helper functions:
func getMusclesCount()-> Int {
let storedExercise = realm.objects(StoredExercise.self)
return storedExercise.count
}
//MARK:- getAllMuscelsNames
func getAllMusclesNames()-> [String] {
var musclesName = [String]()
let storedExercise = realm.objects(StoredExercise.self)
for exercise in storedExercise {
print("Muscle = \(exercise.muscle)")
musclesName.append(exercise.name)
}
return musclesName
}
Here's my Table View Controller class :
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DBHelper.shared.getAllMusclesNames().count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let muscle = DBHelper.shared.getAllMusclesNames()[indexPath.row]
cell.textLabel?.text = muscle
return cell
}
I've tried adding .Filter to 'let storedExercise' but I'm not sure how to set it up correctly. Any assitance would be greatly appreciated, thanks.
If your StoredExercise model looks like this
class StoredExercise: Object {
#objc dynamic var muscle = ""
}
then to get all of the exercises that are for the biceps, it's this
let bicepResults = realm.objects(StoredExercise.self).filter("muscle == 'biceps'")

What is a better way of handling this type of issue?

I'm sort of new to iOS development using Swift. So, I might be missing a simple solution in Swift that I'm not aware of.
I am working on a tvOS app where I display a list of video content that the user can select from. The app also contains a settings tab that allows the user to configure 5 different types of settings. Once they select a specific category, it displays a new table view with the corresponding options which are in the options array. This is where the "issue" is that I need help.
I have this struct which I'm using as a singleton:
struct BMUserSettings
{
internal static var shared = BMUserSettings()
var categories = [String]()
var options = [[String]]()
var currOptionsSelected: [Int] = [0,0,0,0,0] // This array corresponds to the categories array. It tells us what option within that group was selected.
init()
{
self.categories = ["Brand", "Environment","UI Language", "Playback Language", "Geo Location Permission"]
let brandOptionsGroup: [String] = ["CTV", "CTVHUB", "TSN", "Snackable", "RDS", "CP24", "BNN", "CTVNews", "Crave", "BRAVO", "E_BRAND", "SE", "VIDIQA"]
let environmentOptionsGroup: [String] = ["Staging", "Prod"]
let uiLanguageOptionsGroup: [String] = ["en", "fr"]
let playbackLanguageOptionsGroup: [String] = ["en", "fr"]
let geoLocationOptionsGroup: [String] = ["Allow", "Don't Allow"]
options.append(brandOptionsGroup)
options.append(environmentOptionsGroup)
options.append(uiLanguageOptionsGroup)
options.append(playbackLanguageOptionsGroup)
options.append(geoLocationOptionsGroup)
}
// MARK: - Custom Methods
func displayUserSettings() -> String
{
let displayText: String = "Brand=\(options[0][currOptionsSelected[0]]) Environment=\(options[1][currOptionsSelected[1]]) UI Language=\(options[2][currOptionsSelected[2]]) Playback Language=\(options[3][currOptionsSelected[3]]) Geo Location=\(options[4][currOptionsSelected[4]])"
return displayText
}
// MARK: - User Defaults
func saveToUserDefaults()
{
UserDefaults.standard.set(BMUserSettings.shared.currOptionsSelected, forKey: "currentoptions")
}
func loadFromUserDefaults(){
if let currentOptionsSelected = UserDefaults.standard.object(forKey: "currentoptions") as? [Int]{
BMUserSettings.shared.currOptionsSelected = currentOptionsSelected
}
else{
BMUserSettings.shared.currOptionsSelected = [0,0,0,0,0]
}
}
}
As you can see, the "currOptionsSelected" integer array is holding the option that the user selected for each of the categories. For example, if the user chooses the brand "Snackable", then the first element of the currOptionsSelected array will hold a 3 as a value.
I'm saving and loading the currOptionsSelected to/from UserDefaults so that I know what the user's current settings are.
The problem with this approach is:
1) Even if I know the index of the specific option that the user chose, I will still need a set if if-else or switch conditions to make sure I can actually get the correct string value from the corresponding "options" array
2) If any other developer needs to add categories and corresponding options, then they need to make sure they keep everything in order
3) I just don't know if this is the best way of handling this type of issue
What is a better way of doing this?
Here's how I'm trying to use it in a table view:
import UIKit
final class BMSettingsViewController: UIViewController
{
// MARK: - Instance Variables
private static let reuseIdentifier = String(describing: BMContentCell.self)
private let tableview = UITableView(backgroundColor: .white, autoResizingMask: false)
private let tabBarBannerHeight: CGFloat = 150
private var selectedCategoryIndex: Int = 0
private var settingsDetailVC: BMDetailSettingsViewController?
private var categoryNames: [String] = [String]()
private var categoryOptions: [String] = [String]()
// MARK: - View Lifecycle Methods
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
categoryNames = BMCategory.allValues
categoryOptions = BMUserSettings.shared.currOptionsSelected.map { $0.value }
self.tableview.reloadData()
}
override func viewWillDisappear(_ animated: Bool)
{
BMUserSettings.shared.saveToUserDefaults()
}
override func loadView()
{
super.loadView()
self.tableview.dataSource = self
self.tableview.delegate = self
self.tableview.register(BMUserSettingsCell.self, forCellReuseIdentifier: BMSettingsViewController.reuseIdentifier)
displayContent()
}
// MARK: - Custom Methods
private func displayContent()
{
view.addSubview(tableview)
tableview.anchor(
top: self.view.topAnchor,
leading: self.view.leadingAnchor,
bottom: self.view.bottomAnchor,
trailing: self.view.trailingAnchor,
padding: UIEdgeInsets(top: tabBarBannerHeight, left: 0, bottom: 0, right: 0)
)
}
}
// MARK: - UITableView Datasource & Delegate Extension
extension BMSettingsViewController: UITableViewDataSource, UITableViewDelegate
{
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
return "Select an option below to configure it's settings..."
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return categoryNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: BMSettingsViewController.reuseIdentifier, for: indexPath) as! BMUserSettingsCell
cell.configureCell(categoryName: categoryNames[indexPath.row], optionDetailDescription: categoryOptions[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let selectedCategory = categoryNames[indexPath.row]
settingsDetailVC = BMDetailSettingsViewController()
guard let settingsVC = settingsDetailVC else {return}
settingsVC.options = BMUserSettings.shared.options[BMCategory.init(rawValue: selectedCategory)!]!
settingsVC.delegate = self
settingsVC.selectedCategoryIndex = indexPath.row
BMViewControllerManager.shared.getTopViewController()?.present(settingsVC, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 120
}
}
// MARK: - Protocol Extension
extension BMSettingsViewController: OptionsSelector
{
func didFinishSelectingOption(selectedCategoryIndex: Int, selectedOptionIndex: Int)
{
self.selectedCategoryIndex = selectedCategoryIndex
// BMUserSettings.shared.currOptionsSelected[self.selectedCategoryIndex] = selectedOptionIndex
}
}
Here's the settings detail controller that lists just the options within that specific category:
import UIKit
// MARK: - Protocol (used to notify Settings view controller when an option was selected)
protocol OptionsSelector
{
func didFinishSelectingOption(selectedCategoryIndex: Int, selectedOptionIndex: Int)
}
final class BMDetailSettingsViewController: UIViewController
{
// MARK: - Instance Variables
private let cellId = "cellId"
private let tabBarBannerHeight: CGFloat = 150
private var selectedOptionIndex: Int = 0
private let tableview = UITableView(backgroundColor: .white, autoResizingMask: false)
var options: [String] = [String]()
var selectedCategoryIndex: Int = 0
var delegate: OptionsSelector?
// MARK: - View Life Cycle Methods
override func loadView()
{
super.loadView()
self.tableview.dataSource = self
self.tableview.delegate = self
self.tableview.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
displayContent()
}
// MARK: - Custom Methods
private func displayContent()
{
view.addSubview(tableview)
tableview.anchor(
top: self.view.topAnchor,
leading: self.view.leadingAnchor,
bottom: self.view.bottomAnchor,
trailing: self.view.trailingAnchor,
padding: UIEdgeInsets(top: tabBarBannerHeight, left: 0, bottom: 0, right: 0)
)
}
}
// MARK: - UITableView Datasource & Delegate Extension
extension BMDetailSettingsViewController: UITableViewDataSource, UITableViewDelegate
{
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return "Select an option below..." }
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return options.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = "\(options[indexPath.row])"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
BMViewControllerManager.shared.getTopViewController()?.dismiss(animated: true)
self.selectedOptionIndex = indexPath.row
self.delegate?.didFinishSelectingOption(selectedCategoryIndex: self.selectedCategoryIndex, selectedOptionIndex: self.selectedOptionIndex)
}
}
Thank you!
First of all create an enum Category,
enum Category: String {
case brand = "Brand"
case environment = "Environment"
case uiLanguage = "UI Language"
case playbackLanguage = "Playback Language"
case geoLocationPermission = "Geo Location"
}
Next,
Create options of type [Category:[String]], currOptionsSelected of type [Category:String] and defaultOptions of type [Category:String].
Also, instead of displayUserSettings, conform struct BMUserSettings to CustomStringConvertible and implement the description to return the relevant String value.
And, to create the Singleton, mark init() as private.
There is no need to create a separate array for categories.
So the whole struct BMUserSettings will be like,
struct BMUserSettings: CustomStringConvertible {
static var shared = BMUserSettings()
let options: [Category:[String]]
let defaultOptions: [Category:String]
var currOptionsSelected: [Category:String]
let categories: [Category]
private init() {
options = [
.brand : ["CTV", "CTVHUB", "TSN", "Snackable", "RDS", "CP24", "BNN", "CTVNews", "Crave", "BRAVO", "E_BRAND", "SE", "VIDIQA"],
.environment : ["Staging", "Prod"],
.uiLanguage : ["en", "fr"],
.playbackLanguage : ["en", "fr"],
.geoLocationPermission : ["Allow", "Don't Allow"]
]
defaultOptions = self.options.mapValues{ $0.first! }
currOptionsSelected = self.defaultOptions
categories = [.brand, .environment, .uiLanguage, .playbackLanguage, .geoLocationPermission]
}
var description: String {
return self.currOptionsSelected.reduce("") { (result, option) -> String in
return "\(result) \(option.key.rawValue) = \(option.value)\n"
}
}
// MARK: - User Defaults
func saveToUserDefaults() {
var dict = [String:String]()
currOptionsSelected.forEach { dict[$0.key.rawValue] = $0.value }
UserDefaults.standard.set(dict, forKey: "currentoptions")
}
mutating func loadFromUserDefaults() {
if let currentOptionsSelected = UserDefaults.standard.object(forKey: "currentoptions") as? [String:String] {
var dict = [Category:String]()
currentOptionsSelected.forEach {
if let category = Category(rawValue: $0.key) {
dict[category] = $0.value
}
}
self.currOptionsSelected = dict
}
else {
self.currOptionsSelected = self.defaultOptions
}
}
}
Use it in the following way,
BMUserSettings.shared.currOptionsSelected[.brand] = "Snackable"
BMUserSettings.shared.saveToUserDefaults()
BMUserSettings.shared.loadFromUserDefaults()
print(BMUserSettings.shared)
BMUserSettings.shared.categories.forEach {
print($0.rawValue, ":", BMUserSettings.shared.currOptionsSelected[$0]!)
}

showing an error as has need to conform the protocol

This is my code:-
Model:-
class QuestionListModel: NSObject {
var optionsModelArray:[OptionsModel] = []
var question:String!
init(dictionary :JSONDictionary) {
guard let question = dictionary["question"] as? String
else {
return
}
if let options = dictionary["options"] as? [String]{
print(options)
print(options)
for values in options{
print(values)
let optionmodel = NH_OptionsModel(values: values)
self.optionsModelArray.append(optionmodel)
}
}
self.question = question
// print(self.dataListArray33)
}
}
optionModel:-
class OptionsModel: NSObject {
var values:String?
init(values:String) {
self.values = values
print( self.values)
}
}
in viewmodel:-
var questionsModelArray:Array<NH_QuestionListModel>? = []
init(withdatasource newDatasourceModel:NH_QuestionDataSourceModel) {
datasourceModel = newDatasourceModel
print(datasourceModel.dataListArray?.count)
self.questionsModelArray = datasourceModel.dataListArray
print(self.questionsModelArray)
print(datasourceModel.dataListArray)
}
func numberOfSections() -> Int{
return (self.questionsModelArray?.count)!
}
func titleForHeaderInSection(atindexPath indexPath: IndexPath) -> QuestionListModel {
return self.questionsModelArray![indexPath.row]
}
func numberOfRowsInSection(indexPath:IndexPath) -> Int {
if let questionModel = self.questionsModelArray?[indexPath.section]{
return questionModel.optionsModelArray.count
}
else{
return 0
}
}
func datafordisplay(atindex indexPath: IndexPath) -> OptionsModel{
let questionModel = self.questionsModelArray?[indexPath.section]
return questionModel!.optionsModelArray[indexPath.row]
}
And in ViewController:-
func numberOfSections(in tableView: UITableView) -> Int {
return questionViewModel.numberOfSections()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: IndexPath) -> UIView? {
// let headercell = Bundle.main.loadNibNamed("HeaderCell", owner: self, options: nil)?.first as! NH_questionheader
let identifier = "HeaderCell"
var headercell: NH_questionheader! = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader
if headercell == nil {
tableView.register(UINib(nibName: "NH_questionheader", bundle: nil), forCellReuseIdentifier: identifier)
headercell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader
}
headercell.setReviewData(reviews:questionViewModel.titleForHeaderInSection(atindexPath:section))
return headercell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: IndexPath) -> Int {
return questionViewModel.numberOfRowsInSection(indexPath: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
var cell: CellTableViewCell! = tableView.dequeueReusableCell(withIdentifier: identifier) as? CellTableViewCell
if cell == nil {
tableView.register(UINib(nibName: "CellTableViewCell", bundle: nil), forCellReuseIdentifier: identifier)
cell = tableView.dequeueReusableCell(withIdentifier: identifier) as? CellTableViewCell
}
cell.contentView.backgroundColor = UIColor.clear
cell.setOptions(Options1: questionViewModel.datafordisplay(atindex: indexPath))
print("Section \(indexPath.section), Row : \(indexPath.row)")
return cell
}
my json file:-
{
"data":[
{
"question": "Gender",
"options": ["Male","Female"]
},
{
"question": "How old are you",
"options": ["Under 18","Age 18 to 24","Age 25 to 40","Age 41 to 60","Above 60"]
}, {
"question": "I am filling the Questionnaire for?",
"options": ["Myself","Mychild","Partner","Others"]
}
]
}
This is my data .So i need to display the questions in header and options in the cell for index .But showing as error as UITableview has need to conform the protocol UITableviewDataSource.
Also showing error as Index out of range.
How to do.....
I think you are not assign a datasource to your view controller. So please assign it in your ViewDidLoad of your view controller
override func viewDidLoad() {
super.viewDidLoad()
self.yourtableview.delegate = self
self.yourtableview.dataSource = self
// Do any additional setup after loading the view.
}
This error usually occurs when you fail to implement the required methods of a protocol. In this case the methods would be :
cellForRowAt
numberOfRowsInSection
Since you already have them implemented in your view controller chances are that you might have failed to set the datasource for the table view.
Refer to this
https://developer.apple.com/documentation/uikit/uitableviewdatasource
your view controller cannot find the data source and delegate for the table view. make sure you have assigned the data source and delegate
self.yourtableview.delegate = self
self.yourtableview.dataSource = self
and also make sure that your controller also inherit the UITableViewDelegate and UITableViewDataSource like this
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource
To achieve what you want, you should set your VC as the delegate and datasource of your table.
Option 1, do it dynamically:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
}
Option 2, from your storyboard (example below):
After this, you should use the following datasource functions of UITableView:
// return number of questions
func numberOfSections(in tableView: UITableView) -> Int
// return number of options per question (indicated by section)
func tableView(UITableView, numberOfRowsInSection: Int) -> Int
You haven't correctly declared the numberOfRowsInSection function; section is an Int, not an IndexPath. As a result you have not implemented the mandatory functions of UITableViewDataSource.
You want:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionViewModel.numberOfRowsIn(section: section)
}
With an appropriate change in your view model:
func numberOfRowsIn(section:Int) -> Int {
return self.questionsModelArray?[section].optionsModelArray.count ?? 0
}
I would also suggest that you review your use of implicitly unwrapped optionals and force unwrapping; this is just asking for crashes.
For example, there is no reason for the question property of QuestionListModel to be String!; just declare it as String and make your initialiser failable. Better yet, use Codable to create your model from JSON and get rid of all of that code.
You can eliminate the force unwrapping in numberOfSections too:
func numberOfSections() -> Int {
return self.questionsModelArray?.count ?? 0
}
I would also suggest you make QuestionListModel a struct rather than an NSObject subclass.
If I were you I would re-factor to remove the view model, it is adding unnecessary complexity in this case, and use Codable for your JSON deserialisation:
struct Questions: Codable {
enum CodingKeys: String, CodingKey {
case questions = "data"
}
var questions: [Question]
}
struct Question: Codable {
var question: String
var options: [String]
}
Your view controller then becomes much simpler:
class ViewController: UIViewController, UITableViewDatasource {
var questionData: Questions?
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "NH_questionheader", bundle: nil), forCellReuseIdentifier: "HeaderCell")
tableView.register(UINib(nibName: "CellTableViewCell", bundle: nil), forCellReuseIdentifier: "Cell")
// You don't show how you load your JSON, but assuming you have it in an instance of `Data` called `jsonData`:
do {
self.questionData = try JSONDecoder().decode(Questions.self, from: jsonData)
} catch {
print("Error decoding JSON: \(error.localizedDescription)")
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: IndexPath) -> UIView? {
let identifier = "HeaderCell"
guard let questionData = self.questionData,
let headercell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader else {
return nil
}
headercell.label.text = questionData.questions[section].question
return headercell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.questionData?.questions[section].options.count ?? 0
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.questionData?.questions.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
// Note, I have used force unwrapping and a forced downcast here as if either of these lines fail you have a serious problem and crashing is the simplest way of finding it during development
let option = self.questionData!.questions[indexPath.section].options[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath ) as! CellTableViewCell
cell.contentView.backgroundColor = .clear
cell.label.text = option
return cell
}
}
Once you have this basic approach working you can try and add a view model if you like.

Add an Index to a searchBar

Need help to correct this code. I have a coredata app with restaurant names and address, have a search Bar and it is working. What I want to add is a Index and a IndexTitle as showed in the picture below (arrows). Any help is more than welcome. Thanks in advance.
import UIKit
import CoreData
class DictionaryTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchResultsUpdating
{
var searchController:UISearchController!
var searchResults:[Dictionary] = []
private var dictionaryItems:[Dictionary] = []
var fetchResultController:NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
let fetchRequest = NSFetchRequest(entityName: "DictionaryEntity")
do {
dictionaryItems = try managedObjectContext.executeFetchRequest(fetchRequest) as! [Dictionary]
} catch {
print("Failed to retrieve record")
print(error)
}
}
searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
tableView.estimatedRowHeight = 30.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 26
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active {
return searchResults.count
} else {
return dictionaryItems.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! DictionaryTableViewCell
// Configure the cell...
cell.wordLabel.text = dictionaryItems[indexPath.row].word
cell.definitionSmallLabel.text = dictionaryItems[indexPath.row].definition
let dictionary = (searchController.active) ? searchResults[indexPath.row]: dictionaryItems[indexPath.row]
// Configure the cell...
cell.wordLabel.text = dictionary.word
cell.definitionSmallLabel.text = dictionary.definition
return cell
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
return "dictionaryItems\(section)"
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if searchController.active{
return false
}else{
return true
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDictionaryDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let destinationController = segue.destinationViewController as! DictionaryDetailViewController
destinationController.dictionary = (searchController.active) ? searchResults[indexPath.row] : dictionaryItems[indexPath.row]
searchController.active = false
}
}
}
func updateSearchResultsForSearchController(searchController:
UISearchController) {
if let searchText = searchController.searchBar.text {
filterContentForSearchText(searchText)
tableView.reloadData()
}
}
func filterContentForSearchText(searchText: String) {
searchResults = dictionaryItems.filter({ (dictionary:Dictionary) -> Bool in
let wordMatch = dictionary.word!.rangeOfString(searchText, options:
NSStringCompareOptions.CaseInsensitiveSearch)
return wordMatch != nil
})
}
}
What I am trying to have in my table View is the Index (Arrows in the left) and the Index Title (Arrows in the right side).
You have typos in your override function for titleForHeaderInSection
Here is the corrected one, note that v in tableView is capital: (copy paste my code)
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
}
I'd recommend using the auto-complete feature of Xcode. that way, you don't get stuck in insidious errors like this.
UPDATE:
You also need to provide both of these methods to see section header and section index titles.
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return ["A", "B", "C"]
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "A title for section: \(section)"
}
Try the following code:
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 9
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 9
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel?.text = "\(indexPath.section+1)\(indexPath.row+1)"
return cell
}
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return ["A", "C", "B"]
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "SECTION \(section+1)"
}
}
One way to do this is using built-in properties on the NSFetchedResultsController. The most important part of this is setting up the sectionNameKeyPath (which should be the managed object attribute holding the title).
private var frc: NSFetchedResultsController? = nil
private lazy var fetchedResultsController:NSFetchedResultsController = {
if self.frc != nil {
return self.frc!
}
//grab your managed object context
let moc = dbStore.sharedInstance.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Transaction")
fetchRequest.fetchBatchSize = 30
fetchRequest.resultType = NSFetchRequestResultType.ManagedObjectResultType
let sortDescriptorByTimeStamp = NSSortDescriptor(key: "timeStamp", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptorByTimeStamp]
//in-memory cache when cachename is nil, delegate is non-nil
//note the sectionNameKeyPath requires that I have a related category object with the categoryName attribute on my Transaction managed object
let nfrc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "category.categoryName", cacheName: nil)
self.frc = nfrc
do
{
try self.frc!.performFetch()
} catch let e as NSError {
fatalError(e.localizedDescription)
}
return self.frc!
}()
Section Titles
To get the section titles you implement this method.
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionTitle = self.fetchedResultsController.sections?[section].name ?? "No Category Titles"
return sectionTitle
}
Table Index
To setup the index you implement the following method.
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return self.fetchedResultsController.sectionIndexTitles ?? [""]
}
Note the Quick Help Documentation when you click on
sectionIndexTitles. The array of section index titles. The default
implementation returns the array created by calling
sectionIndexTitleForSectionName: on all the known sections. You should
override this method if you want to return a different array for the
section index. You only need this method if you use a section index.
Source
If you want to see this in context, to get the gist of how to use the NSFetchedResultsController in this way you can checkout my sample at this location.
Alternates (Arrays/Dictionaries)
There are a few other tutorials about how to build an index from arrays and dictionaries (but I recommend the method above as simplest way to proceed).
Alternate Tutorial #1
Alternate Tutorial #2
Alternate Implementation
Note: Code in this section is from Alternate Tutorial #1
// `UIKit` convenience class for sectioning a table
let collation = UILocalizedIndexedCollation.currentCollation()
as UILocalizedIndexedCollation
....
/* section headers
appear above each `UITableView` section */
override func tableView(tableView: UITableView,
titleForHeaderInSection section: Int)
-> String {
// do not display empty `Section`s
if !self.sections[section].users.isEmpty {
return self.collation.sectionTitles[section] as String
}
return ""
}
/* section index titles
displayed to the right of the `UITableView` */
override func sectionIndexTitlesForTableView(tableView: UITableView)
-> [AnyObject] {
return self.collation.sectionIndexTitles
}
override func tableView(tableView: UITableView,
sectionForSectionIndexTitle title: String,
atIndex index: Int)
-> Int {
return self.collation.sectionForSectionIndexTitleAtIndex(index)
}

How to populate table with headers with associative array data source? Swift XCode 6.4

In my iOS app I have a table a table where I want to display a long list of tasks with 4 categories: open, closed, suspended, expired. So I have created a table with 4 headers and each header has a unique identifier for its cell:
Following a tutorial, after querying a database my tasks are organized in an object whose class is this:
import Foundation
class TaskMenuItems:NSObject {
var sections:[String] = []
var items:[String:[Task]] = [:]
func addSection(section:String, sectionItems:[Task]) {
self.sections = self.sections + [section]
self.items[section] = sectionItems
}
func organize(tasks:[Task]) {
var open:[Task] = []
var closed:[Task] = []
var expired:[Task] = []
var suspended:[Task] = []
for task in tasks {
switch task.stato {
case "Aperto":
open.append(task)
break
case "Chiuso":
closed.append(task)
break
case "Scaduto":
expired.append(task)
break
case "Sospeso":
suspended.append(task)
break
default:
break
}
}
self.addSection("Aperti", sectionItems: open)
self.addSection("Chiusi", sectionItems: closed)
self.addSection("Scaduti", sectionItems: expired)
self.addSection("Sospesi", sectionItems: suspended)
}
}
so it is an associative array where keys are the state of the task while the values are an array of tasks with that state. This is correctly done... the problm is that I'm not able to write the following method to populate the table:
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
So, here is my Swift class view controller associated to the table:
import UIKit
class AllTasksViewController: UITableViewController {
var allTasks = [Task]()
var taskService = TaskService()
var organizedTasks = TaskMenuItems()
override func viewWillAppear(animated: Bool) {
self.tabBarController?.navigationItem.setHidesBackButton(true, animated: true)
self.tabBarController?.navigationItem.rightBarButtonItem = self.editButtonItem()
if (LoggedUser.isLogged) {
self.navigationItem.setHidesBackButton(false, animated: true)
self.taskService.requestAllTasks {
(response) in
self.allTasks = self.taskService.loadTasks(response) as! [Task]
self.organizedTasks.organize(self.allTasks)
/*println(self.organizedTasks.items["Aperti"]?.count)
println(self.organizedTasks.items["Chiusi"]?.count)
println(self.organizedTasks.items["Scaduti"]?.count)
println(self.organizedTasks.items["Sospesi"]?.count)*/
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
/* Number of sections */
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.organizedTasks.sections.count
}
/* Number of rows for each section */
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return self.organizedTasks.items["Aperti"]!.count
case 1:
return self.organizedTasks.items["Chiusi"]!.count
case 2:
return self.organizedTasks.items["Scaduti"]!.count
case 3:
return self.organizedTasks.items["Sospesi"]!.count
default:
return -1
}
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
Actually I'm getting confused by something trivial I know because I think to indexPath as a String and not as an Int...In fact I am supposed to do:
let cell = tableView.dequeueReusableCellWithIdentifier("OpenTask",
forIndexPath: indexPath) as! UITableViewCell//2
cell.textLabel?.text = self.organizedTasks.items[indexPath.section][indexPath.row]
but in my case indexPath.row should be a String... :( Moreover, how can I access the n-th task object of that status in that associative array? something similar
self.organizedTasks.items["Aperti"][0]
won't work...
Would you please give any hint?
In swift you can use this method to display header
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int)
{
/* if let view = view as? UITableViewHeaderFooterView {
view.backgroundView?.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.8)
let subview = UIView(frame: CGRect(x: 0, y: view.frame.height - 2, width: view.frame.width, height: 2))
subview.backgroundColor = UIColor.whiteColor()
view.addSubview(subview) //according to need
}*/
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
if(section == 0)
{
return " Section 0"// according to need
}
else
{
return " Section 1"// according to need oR make it generic
}
}

Resources