How to set a cell delegate when configuring a cell with RxTableViewSectionedReloadDataSource - rx-swift

I have a delegate method that I want to execute when button in a cell is tapped. I configured my cell using RxTableViewSectionedReloadDataSource and when I set the delegate here it it says they are unrelated types which I don't understand because my viewController inherits the delegate and implements its method.
let notificationsDataSource = RxTableViewSectionedReloadDataSource<SectionOfCustomData>(
configureCell: {dataSource, tableView, indexPath, item in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "NewNotificationTableViewCell", for: indexPath) as? NewNotificationTableViewCell else { return UITableViewCell() }
cell.configureCell(item: item, indePath: indexPath)
cell.notificationDeleteDelegate = self
return cell
})
I get this error "Cannot assign value of type
'(NotificationViewController) -> () -> NotificationViewController' to
type 'TableViewCellDeleteDelegate?'"

Seems that NotificationViewController is not conform to TableViewCellDeleteDelegate...
extension NotificationViewController: TableViewCellDeleteDelegate {
// TableViewCellDeleteDelegate Methods here
}

extension NotificationViewController {
func setupTableView() {
let dataSource = RxTableViewSectionedReloadDataSource<NotificationSectionModel>(
configureCell: { dataSource, tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: Cellidentifier, for: indexPath) as! NotificationCellCell
cell.configure(title: item, indexPath: indexPath, viewModel: self.viewModel)
return cell
})
viewModel.sections
.asDriver()
.drive(tableView.rx.items(dataSource: dataSource))
>>> bag
tableView
.rx.setDelegate(self)
>>> bag
}
}
and then in your cell class use this for binding
func configure(title: String, indexPath: IndexPath, viewModel: YourViewModel) {
setupBinding(section: indexPath.section, viewModel: viewModel)
}
private func setupBinding(section: Int, viewModel: YourViewModel) {
}

Related

Insert information from REST API in a table view

I after asking another question today and fixed my Swift 4.2 code, I realized that I can't debug the app because of some profiles.
Errors shown when Testing application
The app also crashes on launch so there is not much I can do for now. I believe that the issue is how I am getting the information from the webserver (currently my computer). I am quite new to this, so i might have some mistakes in my code, so bear with me.
import UIKit
class InfoTableViewController: UITableViewController {
//MARK: Properties
class Produs {
var nume: String!
var codBare: String!
init(nume: String, codBare: String) {
self.nume = nume
self.codBare = codBare
}
}
var produse = [Produs]()
override func viewDidLoad() {
super.viewDidLoad()
//Load elements from server, theoretically
loadProducts()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return produse.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "InfoTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InfoTableViewCell else {
fatalError("The dequeued cell is not an instance of InfoTableViewCell.")
}
// Fetches the appropriate meal for the data source layout.
let produs = produse[indexPath.row]
cell.nameMain.text = produs.nume
cell.nameInfo.text = produs.codBare
return cell
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
//MARK: Private Methods
private func loadProducts() { //This function retrieves information in a JSON format from the server
var request = URLRequest(url: URL(string: "192.168.0.145")!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request, completionHandler: { data, response, error -> Void in
do {
let decoder = JSONDecoder()
let json = try decoder.decode([[String]].self, from: data!)
print(json)
for produs in json {
print(produs)
var nume_prod: String = produs[0] // Numele produsului
var cod_ext: String = produs[1] // Codul de bare al produsului
var prod_obj = Produs(nume: nume_prod, codBare: cod_ext)
self.produse.append(prod_obj)
}
} catch {
print("JSON Serialization error")
}
}).resume()
}
}
I am getting the information from the server in a JSON file that is an array of arrays and looks like this:
[
[
"product1",
"code1"
],
[
"product2",
"code2"
],
[
"product3",
"code3"
]
]
Thank you for your help!
Don't send the JSON as array of arrays, send it as array of dictionaries. It simplifies the decoding considerably.
[
{"name":"product1", "code":"code1"},
{"name":"product2", "code":"code2"},
{"name":"product3", "code":"code3"}
]
Then declare the model as struct and never declare properties as implicit unwrapped optional which are initialized with non-optional values. If you need optionals declare them as regular optional (?) otherwise non-optional
struct Produs {
let name: String
let code: String
}
Replace loadProducts with
private func loadProducts() { //This function retrieves information in a JSON format from the server
let url = URL(string: "192.168.0.145")!
URLSession.shared.dataTask(with: url, completionHandler: {[unowned self] data, response, error in
if let error = error { print(error); return }
do {
self.produse = try JSONDecoder().decode([Produs].self, from: data!)
print(self.produse)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}).resume()
}
And replace cellForRowAt with
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InfoTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InfoTableViewCell
// Fetches the appropriate meal for the data source layout.
let produs = produse[indexPath.row]
cell.nameMain.text = produs.name
cell.nameInfo.text = produs.code
return cell
}

Realm note based App not properly deleting the Correct Object

When a cell is deleted, the item at the end of the list takes the place of the item that just got deleted. This only happens when there are more than 3 items in the list.
In the gif below I delete numbers 3 and 4 which leaves me with numbers 1,2,5 in the simulator. HOWEVER in the Realm file I have numbers 1,2,4. I have no clue why it does this?
Data Model
import Foundation
import RealmSwift
class Item: Object {
#objc dynamic var name = ""
}
View Controller
import UIKit
import RealmSwift
class ListViewController: UITableViewController {
let realm = try! Realm()
var itemArray : Results<Item>?
var item:Item?
override func viewDidLoad() {
super.viewDidLoad()
self.itemArray = realm.objects(Item.self)
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Item", message: "", preferredStyle: .alert)
alert.view.tintColor = UIColor.red
let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
let newItem = Item()
newItem.name = textField.text!
try! self.realm.write {
self.realm.add(newItem)
}
self.tableView.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.itemArray!.count//Size of the Array
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)//Asigns the Protocol Cell
let data = self.itemArray![indexPath.row]
cell.textLabel?.text = data.name
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let item = itemArray?[indexPath.row] {
try! self.realm.write {
self.realm.delete(item)
}
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
You are retrieving an unsorted result set from Realm, which as per documentation does not necessarily retain insertion order after deletions (basically when you remove 3, then 5 is shifted in its place):
Note that the order of Results is only guaranteed to stay consistent when the query is sorted. For performance reasons, insertion order is not guaranteed to be preserved.
So there are two things you can do:
1.) sort the result set
2.) instead of assuming you're only deleting a single object and otherwise have no movements of any sort, you can rely on Realm's own diffing + change set evaluation with a notification token so that you receive a change set for any possible change that happens to the result set.
// see https://realm.io/docs/swift/latest/#collection-notifications
class ViewController: UITableViewController {
var notificationToken: NotificationToken? = nil
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
let results = realm.objects(Person.self).filter("age > 5")
// Observe Results Notifications
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
}
deinit {
notificationToken?.invalidate()
}
}

core data nstableview searchbar

i have a nstableview which i fill with data of core data.
NSManagedObject
import Foundation
import CoreData
#objc(Person)
public class Person: NSManagedObject {
#NSManaged public var firstName: String
#NSManaged public var secondName: String
}
RequestData
func requestData() {
let appdelegate = NSApplication.shared().delegate as! AppDelegate
let context = appdelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
do {
person = try context.fetch(request) as! [Person]
tableViewn.reloadData()
} catch { }
}
i also have a custom cell view for my tableView.
I fill the data like this:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let view = tableView.make(withIdentifier: "Cell", owner: self) as? CustomCell
view?.txtfirstName.stringValue = person.firstName
view?.txtsecondName.stringValue = person.secondName
return view
}
Now i would like to realize a searchbar (which i have already in my view controller) for searching with first or second name.
but i have no idea how i can realize this.
Set the delegate of the NSSearchField to the target class
implement controlTextDidChange
override func controlTextDidChange(_ notification: Notification) {
if let field = notification.object as? NSSearchField {
let query = field.stringValue
let predicate : NSPredicate?
if query.isEmpty {
predicate = nil
} else {
predicate = NSPredicate(format: "firstName contains[cd] %# OR lastName contains[cd] %#", query, query)
}
requestData(with: predicate)
} else {
super.controlTextDidChange(notification)
}
}
Change requestData to
func requestData(with predicate : NSPredicate? = nil) {
let appdelegate = NSApplication.shared().delegate as! AppDelegate
let context = appdelegate.persistentContainer.viewContext
let request = NSFetchRequest<Person>(entityName: "Person")
request.predicate = predicate
do {
person = try context.fetch(request)
tableViewn.reloadData()
} catch { }
}
Side note:
If you are using NSManagedObject subclasses create the fetch request more specific NSFetchRequest<Person>(entityName... rather than NSFetchRequest<NSFetchRequestResult>(entityName..., that avoids the type cast in the fetch line.

Error :"Cannot subscript a value of type'[String: Array<String>]' with an index of type 'Int'"

Assignment: "Create an application that displays a list of gas stations and their gas prices and distance. Store the information into a Dictionary. Display the results in a table view. Let the user select an entry and then display a UIAlert dialog showing the entry."
I am working on the dictionary part. The code for the dictionary is
"var gasStation = ["76": ["$2.76", "1.2 miles"],
"Arco":["$2.56", "2.4 miles"],
"Shell":["$3.54", "3.5 miles"],
"Tower mart": ["$2.36", "5.7 miles"]]"
The error pops up on this line of code
cell!.textLabel!.text = gasStation[indexPath.row]
Here is the New Updated code
import UIKit
class ViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
let dwarves = [ "Sleepy", "Sneezy", "bashful", "Happy"]
let gasStation = ["76": ["$2.76", "1.2 miles"],
"Arco":["$2.56", "2.4 miles"],
"Shell":["$3.54", "3.5 miles"],
"Tower-Mart": ["$2.36", "5.7 miles"]]
var gasStationNames = Array(gasStation) // error: "ViewController.Type' does not have a member named 'gasStation'
let simpleTableIdentifier = "SimpleTableIdentifier"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return dwarves.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier(
simpleTableIdentifier) as? UITableViewCell
if (cell == nil) {
cell = UITableViewCell(
style: UITableViewCellStyle.Default, reuseIdentifier: simpleTableIdentifier)
}
cell!.textLabel?.text = dwarves[indexPath.row]
return cell!
}
}
This is how you get an array of all the gas Station names.
let gasStationNames = Array(gasStation.keys)
But it will not be in the same order.
And for assigning it to the tableView:
cell!.textLabel!.text = gasStationNames[indexPath.row]
Here, you can index gasStationNames with indexPath.row of type Int as it is of type Array. And to get a value out of Dictionary, you should pass a key of proper type.
You should have a look at this document page
EDIT
Assign gasStationNames in your cellForRow
let gasStationNames = Array(gasStation.keys)
and then assign to cell's textLabel
cell!.textLabel!.text = gasStationNames[indexPath.row]

Reordering Realm.io data in tableView with Swift

I have implemented a basic example of an ios app using Realm.io
I'd like to be able to reorder table rows in my iOS app and save the order back to Realm.
Realm model contains a property called position for this purpose.
P.S: Sorry for so much code.
import UIKit
import Realm
class Cell: UITableViewCell {
var position: Int!
init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier)
}
}
class Language: RLMObject {
var title = ""
var position = Int()
}
class ManagerLanguagesController: UITableViewController, UITableViewDelegate, UITableViewDataSource {
var array = RLMArray()
var notificationToken: RLMNotificationToken?
var editButton = UIBarButtonItem()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
notificationToken = RLMRealm.defaultRealm().addNotificationBlock { note, realm in
self.reloadData()
}
reloadData()
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return Int(array.count)
}
func setupUI() {
tableView.registerClass(Cell.self, forCellReuseIdentifier: "cell")
self.title = "Languages"
var addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "add")
editButton = UIBarButtonItem(title: "Edit", style: .Plain, target: self, action: "edit")
var buttons = [addButton, editButton]
self.navigationItem.rightBarButtonItems = buttons
}
func add() {
var addLanguageView:UIViewController = self.storyboard.instantiateViewControllerWithIdentifier("newLanguage") as UIViewController
self.navigationController.presentViewController(addLanguageView, animated: true, completion: nil)
}
func edit () {
if tableView.editing {
/* FROM THIS POINT I'M PROBABLY DOING SOMETHING WRONG.. IT IS NOT WORKING */
var positionArray = NSMutableArray()
let realm = RLMRealm.defaultRealm()
var i = 0
for var row = 0; row < tableView.numberOfRowsInSection(0); row++ {
var cellPath = NSIndexPath(forRow: row, inSection: 0)
var cell:Cell = tableView.cellForRowAtIndexPath(cellPath) as Cell
positionArray.addObject(cell.position)
}
realm.beginWriteTransaction()
for row: RLMObject in array {
row["position"] = positionArray[i]
i++
}
realm.commitWriteTransaction()
/* -- NOT WORKING END -- */
tableView.setEditing(false, animated: true)
editButton.style = UIBarButtonItemStyle.Plain
editButton.title = "Edit"
} else{
tableView.setEditing(true, animated: true)
editButton.title = "Done"
editButton.style = UIBarButtonItemStyle.Done
}
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as Cell
let object = array[UInt(indexPath!.row)] as Language
cell.textLabel.text = object.title
cell.position = object.position // I have implemented this to be able to retain initial positions for each row and maybe use this when reordering..
return cell
}
override func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
return true
}
override func tableView(tableView: UITableView!, moveRowAtIndexPath sourceIndexPath: NSIndexPath!, toIndexPath destinationIndexPath: NSIndexPath!) {
// println("Old index: \(sourceIndexPath.indexAtPosition(sourceIndexPath.length - 1)+1)")
// println("New index: \(destinationIndexPath.indexAtPosition(sourceIndexPath.length - 1)+1)")
// Maybe something needs to be implemented here instead...
}
func reloadData() {
array = Language.allObjects().arraySortedByProperty("position", ascending: true)
tableView.reloadData()
}
}
Thanks in advance
Instead of using a position property, you could instead keep an ordered array as a property on another object. This way you don't have to keep the position up to date and instead arrange your objects as needed:
class Language: RLMObject {
dynamic var title = ""
}
class LanguageList: RLMObject {
dynamic var languages = RLMArray(objectClassName: "Language")
}
class ManagerLanguagesController: UITableViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// create our list
var realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.addObject(LanguageList())
realm.commitWriteTransaction()
...
}
// helper to get the RLMArray of languages in our list
func array() -> RLMArray {
return (LanguageList.allObjects().firstObject() as LanguageList).languages
}
override func tableView(tableView: UITableView!, moveRowAtIndexPath sourceIndexPath: NSIndexPath!, toIndexPath destinationIndexPath: NSIndexPath!) {
var languages = array()
var object = languages.objectAtIndex(UInt(sourceIndexPath.row)) as Language
var realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
languages.removeObjectAtIndex(UInt(sourceIndexPath.row))
languages.insertObject(object, atIndex: UInt(destinationIndexPath.row))
realm.commitWriteTransaction()
}
...
}
this work for me to move rows in tableview using realm with swift 2.2:
func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
let aux = TimesHome.mutableCopy() as! NSMutableArray
let itemToMove = aux[fromIndexPath.row]
let realm = try! Realm()
realm.beginWrite()
aux.removeObjectAtIndex(fromIndexPath.row)
aux.insertObject(itemToMove, atIndex: toIndexPath.row)
try! realm.commitWrite()
TimesHome = aux
let times = realm.objects(ParciaisTimes)
if times.count > 0 {
for tm in times {
for i in 1...aux.count {
if aux[i-1].valueForKey("time_id") as! Int == tm.time_id {
realm.beginWrite()
tm.ordem = i
try! realm.commitWrite()
}
}
}
}
}

Resources