I want to make a tableView method static - xcode

I am currently writing a small application that involves a tableView and an array of ManagedObjects for persistent storage.
What I want to do delete all the ManagedObjects in the array by clicking a button in another view controller.
To do this, I tried to make the array a static variable, unfortunately this conflicts with the methods that I use to populate the table with data from this array. Frustrating stuff.
Here is the code for the class:
class ClassOverviewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
static var subjects = [NSManagedObject]()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return subjects.count
}
static func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")
let subject = subjects[indexPath.row]
cell!.textLabel!.text = subject.valueForKey("subjectName") as? String
return cell!
}
static func clearSubjects() {
for item in (self.subjects)
{
CalculateClass.managedContext.deleteObject(item)
}
do {
try CalculateClass.managedContext.save()
}
catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
}
I have removed functions from the class that I did not think were necessary to show you.
It does not like me making the second tableView method static because I have taken that method from UITableViewDataSource.
I am unsure how I am supposed to proceed. Please help!

Go back to the non-static implementation so your table works.
When you want to remove the objects either:
get a reference to your ClassOverviewController object and call its method
or, if there's no connection between controllers, use a notification that tells the ClassOverviewController object that it should reset its array.

Related

How to replace a simple delegate protocol with RxSwift?

I'm looking for the optimal way to avoid defining
protocol SomeTableviewCellDelegate: class {
func didSelectTopic(topic: RTopic)
}
and use RxSwift instead.
I already have defined
var didSelectTopic: Observable<RTopic> {
return _didSelectTopic.asObservable()
}
private let _didSelectTopic = PublishSubject<RTopic>()
var didDeselectTopic: Observable<RTopic> {
return _didDeselectTopic.asObservable()
}
private let _didDeselectTopic = PublishSubject<RTopic>()
I can't find the proper way to emit a topic (I am new to RxSwift).
You trying to pass information about selected items (Correct me if I wrong).
Currently selected items
We don't care about selection / unselection, we care only about items, that selected in each moment of time.
This is probably most common use case, since your user (user of component) doesn't need to have local state.
protocol TopicSelectionProvider {
var selectedTopicsStream: Observable<Set<RTopic>> { get }
}
class MyTableDelegate: NSObject, UITableViewDelegate {
private var selectedTopics = BehaviourRelay<Set<RTopic>>([])
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var selected = selectedTopics.value
selected.insert(topics[indexPath.row])
selectedTopics.accept(selected)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
var selected = selectedTopics.value
selected.remove(topics[indexPath.row])
selectedTopics.accept(selected)
}
}
extension MyTableDelegate: TopicSelectionProvider {
var selectedTopicsStream: Observable<Set<RTopic>> {
return selectedTopics.asObservable()
}
}
Selection / Unselection events
We don't care about selected items, we only interested in events them self.
This use case can be used, when you don't need items, but events (for api calls for example).
enum TopicSelectionEvent {
case select(RTopic)
case deselect(RTopic)
}
protocol TopicSelectionProvider {
var topicSelectionStream: Observable<TopicSelectionEvent> { get }
}
class MyTableDelegate: NSObject, UITableViewDelegate {
private var topicSelection = PublishSubject<TopicSelectionEvent>()
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
topicSelection.onNext(.select(topics[indexPath.row]))
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
topicSelection.onNext(.deselect(topics[indexPath.row]))
}
}
extension MyTableDelegate: TopicSelectionProvider {
var topicSelectionStream: Observable<TopicSelectionEvent> {
return topicSelection.asObservable()
}
}
Both approach valid and have proc and cons. Choose one that suits you best.

NSTableView - Better solution for sorting collection with NSSortDescriptor

I have a NSTableView with 2 columns bound with a custom type (SelectedFiles) array as File Name and File Path, after clicking the header, I want it to sort the data in ascending / descending order, I tried these codes with NSSortDescriptor:
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))
tableView.tableColumns[0].sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
let selectedFilesArray = NSMutableArray(array: selectedFiles)
selectedFilesArray.sort(using: tableView.sortDescriptors) // Signal SIGABRT
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
My custom collection for the data in table view:
struct SelectedFiles: CustomStringConvertible {
let fileName: String
let filePath: String
var description: String {
return "\(fileName) at path \(filePath)"
}
}
var selectedFiles: [SelectedFiles] = []
It turns out it doesn't work at all, IDK if its anything wrong with my code or I'm missing something.
So, I came up with this awkward solution:
var tableViewSortingOrder = ComparisonResult.orderedAscending
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
switch tableViewSortingOrder {
case .orderedAscending:
tableViewSortingOrder = .orderedDescending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
default:
tableViewSortingOrder = .orderedAscending
selectedFiles.sort { (previous, next) -> Bool in
return previous.fileName.compare(next.fileName) == tableViewSortingOrder
}
tableView.reloadData()
}
}
After I changed to this solution, it worked perfectly as it switches swiftly between ascending / descending order. But, when it comes to deleting objects in the collection, it throws Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value when I'm trying to delete multiple objects from both collection and table view with some specific files.
So, I'm thinking if I should change a way of achieving this header sorting thing by using NSSortDescriptor (use the old-fashioned way by correcting my first method) in order to get away from this issue, I have to admit that my second way is a bit of awkward (is more like a plan C).
I've red through multiple StackOverflow posts on this topic and I tried all of their ways, especially this one, I am not using CoreData which its solutions does not work for my situation.
Anyone can help point out the way please? 😊
I red the guide to NSTableView from Apple Developer Site and few other StackOverflow posts, I found myself a workable solution for Swift 4:
I set the sortDescriptorPrototype to fileNameSortDescriptor in viewDidLoad() under ViewController class.
class ViewController: NSViewController {
override func viewDidLoad()
super.viewDidLoad()
let fileNameSortDescriptor = NSSortDescriptor(key: "fileName", ascending: true, selector: #selector(NSString.localizedStandardCompare))
let tableColumn = tableView.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "fileNameColumn"))!
tableColumn.sortDescriptorPrototype = fileNameSortDescriptor
// other codes
}
}
And then I added an inheritance from NSObject and inserted #objcMembers to prevent warning: Object <#object#> of class '<#class#>' does not implement methodSignatureForSelector: -- trouble ahead from occurring and then cause Signal SIGABRT while calling selectedFiles.sort(using: tableView.sortDescriptors) (Reference: Object X of class Y does not implement methodSignatureForSelector in Swift).
#objcMembers class SelectedFiles: NSObject {
let fileName: String
let filePath: String
override var description: String {
return "\(fileName) at path \(filePath)"
init(fileName: String, filePath: String) {
self.fileName = fileName
self.filePath = filePath
}
}
Here's the code for tableView(_:sortDescriptorsDidChange:) in NSTableViewDataSource:
extension ViewController: NSTableViewDataSource {
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
var selectedFilesArray = NSArray(array: selectedFiles)
selectedFilesArray = selectedFilesArray.sortedArray(using: tableView.sortDescriptors) as NSArray
selectedFiles = selectedFilesArray as! [SelectedFiles]
tableView.reloadData()
}
}
Now, everything works perfectly fine.

How to download name and images on one row in ascending order

I have researched this and nothing seems to be working. I am trying to build a recipe app and the the image of the dish & names of the dish (appetizer) are not downloading in order. How can I do this?
Code:
class Appetizers: UITableViewController {
var valueToPass: String!
var valuePassed: String!
var appetizer = [String]()
var images = [UIImage]()
func refresh() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
override func viewDidLoad() {
super.viewDidLoad()
// Parse - class - column
let query = PFQuery(className: "Appetizers")
query.orderByAscending("appetizer")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
if let objects = objects {
for object in objects {
let load = object.objectForKey("appetizer") as! String
self.appetizer.append(load)
let imageFile = object["imageFiles"] as! PFFile
imageFile.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error != nil {
print(error)
} else {
if let data = imageData {
self.images.append(UIImage(data: data)!)
self.tableView.reloadData()
}
}
})
self.tableView.reloadData()
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
sleep(1)
refresh()
}
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return appetizer.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = appetizer[indexPath.row]
// add image to table
if images.count > indexPath.row {
cell.imageView?.image = images[indexPath.row]
}
return cell
}
// when user taps on cell ...
func getCellLabel () {
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
valueToPass = currentCell.textLabel!.text
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
getCellLabel()
self.performSegueWithIdentifier("0", sender: self)
}
}
When performing asynchronous queries, you have no assurances regarding the order they complete. So, the concept of two separate arrays, one an array of strings and another an array of images will always be problematic.
You could, for example, replace images with a dictionary indexed by the appetizer name, and thus it wouldn't matter what order they complete.
var appetizer = [String]()
var images = [String: UIImage]()
Thus, it might look like:
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Appetizers")
query.orderByAscending("appetizer")
query.findObjectsInBackgroundWithBlock { objects, error in
guard error == nil, let objects = objects else {
print(error)
return
}
for (index, object) in objects.enumerate() {
let appetizerName = object.objectForKey("appetizer") as! String
self.appetizer.append(appetizerName)
let imageFile = object["imageFiles"] as! PFFile
imageFile.getDataInBackgroundWithBlock { imageData, error in
guard error == nil, let data = imageData else {
print(error)
return
}
// when the image comes in, asynchronously update only that one row
self.images[appetizerName] = UIImage(data: data)
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Fade)
}
}
// reload the table only once, after all of the `appetizer` entries are created (but likely before the images come in)
self.tableView.reloadData()
}
}
And
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let name = appetizer[indexPath.row]
cell.textLabel?.text = name
cell.imageView?.image = images[name]
return cell
}
Or you could just replace these two separate properties with one that is an array of custom objects (e.g. an Appetizer object that has both a name property and an image property).
But any way you do that, you want to name sure you're not dealing with two separate array.
By the way, but this process of loading all of the images can be problematic if you have a lot of rows. This code is employing "eager" loading of images (loading them whether they're currently required or not). The problem is that images are relatively large assets (in comparison to the string values) and you can run into memory issues, network bandwidth issues, etc.
One generally likes to employ "lazy" loading (e.g. let cellForRowAtIndexPath request the image only when it's needed. For example, let's say you have 200 rows, of which only 12 are visible at one point. You shouldn't be requesting 200 images, but rather only that for the 12 visible ones. If you take the image retrieval out of viewDidLoad and, instead, have cellForRowAtIndexPath request them one at a time, you'll have much better network performance and less demanding memory characteristics.
If you're going to save the images in some structure like the code currently does, at the very least make sure you purge those images upon receiving notification of a memory warning (and, obviously, gracefully handle the re-requesting them in a JIT manner as needed).
I figured out the problem with my table not loading without sleep() ...
I had 'self.tableView.reloadData()' outside of the block.
Rob was very helpful :)

Two functions with one variable in Swift

I have two functions in a view controller. The first function parses JSON and makes an array; another generates a table with the array data. The problem is that it seems that the first function cannot send its array data to the second function.
Here is the code:-
class secondViewController: UIViewController, UITableViewDataSource {
let chartTitle:[String] = ["Name",......]
func parseJSON(){
let url = NSURL(string: "http://00000.us-west-2.elasticbeanstalk.com/index.php?000000")
let request = NSURLRequest(URL: url!)
do {
let data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: nil)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
var name = json["Name"]
var chartContent:[String] = ["\(name)",.....] //Contents of current chart contents
} catch{
//Handle Exception
}
} catch{
//Handle Exception
}
}
override func viewDidLoad() {
parseJSON()
...
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { //currnet table information.
let cell = UITableViewCell()
cell.textLabel?.text = chartTitle[indexPath.row] + "\t\t\t\t\t here comes info" + chartContent[indexPath.row]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return chartTitle.count
}
}
This code has an error at the tableView function:
Use of unresolved identifier 'chartContent'
I tried to declare the variables outside the first function which is right under the class secondViewController but there was another error on UITableViewDataSource.
Any solution for these?
Charttitle is defined outside any procedure, so it's available everywhere. Chartcontent is defined in a block, so it's usable just in it's block
Its because chartContent is a local variable just available to parseJson func only and its scope is till that func block. You have to create this variable the same way you dis chartTitle to be available throughout the class.

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]

Resources