I have an array of dictionaries class instance, outlined below:
class SomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var array = [[String: AnyObject]]()
override func viewDidLoad() {
super.viewDidLoad()
}
// tableview delegates
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
print(“array address: \(unsafeAddressOf(array))”) // 0x000000015cf0ebd0
let option = UITableViewRowAction(style: .Default, title: “Option”, handler: { [weak self] (_, _) in
guard let strongSelf = self else { return }
print(“array address1: \(unsafeAddressOf(strongSelf.array))” // 0x000000015cd10c50
})
return [option]
}
}
why is the address of array is changed (0x000000015cf0ebd0 vs 0x000000015cd10c50) as I just capture it in UITableViewRowAction initialization?
Thanks,
It's a nature of unsafeAddressOf and Swift Arrays.
A simplified example you can test in the Playground.
(No closure, no strongSelf...)
import Foundation
var str = "Hello, playground"
class MyClass {
var array = [[String: AnyObject]]()
}
let obj1 = MyClass()
let ptr1 = unsafeAddressOf(obj1.array)
let ptr2 = unsafeAddressOf(obj1.array)
print(ptr1 == ptr2)
Tested in Xcode 7.3.1 (Swift 2.2.1) a few times and all printed "false".
The signature of unsafeAddressOf is:
func unsafeAddressOf(object: AnyObject) -> UnsafePointer<Void>
As you know Swift Arrays are value types and you cannot pass them to AnyObject. So, when "bridging to Objective-C" feature is available, your array is converted to NSArray. This conversion is done in a "hard-to-predict" manner, which means, at any time this conversion is made, Swift may allocate a new NSArray instance.
In general, you should not expect something "firm" from unsafeAddressOf when applied to Swift Arrays or other value types.
Because you are assigning self to variable strongSelf, causing the value of self to be copied to the new variable.
Remember that when you assign a variable to another, it's the value that's copied from one to another, not the reference to the memory allocation.
You can try this by printing exactly the same way you are doing the first time:
print("array address: \(unsafeAddressOf(self.array))")
You need to add self in order to access the array class member when inside a closure block.
Related
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.
I am new to Swift and trying to learn how to implement NSTreeController with NSOutlineView. I've been following several guides which shows such examples, but I keep getting an error. I followed step by step and/or try to run their source codes if available, but I was getting same error. I come to think there is some change in Swift 4 which makes these Swift 3 examples to produce error. As there are not many examples done in Swift 4, I decided I'd give a try by asking the question here.
The error I'm getting is:
this class is not key value coding-compliant for the key isLeaf.
I believe that error is coming from the key path set up for NSTreeController:
However I am not sure what needs to be done to fix the error.
I have simple model class called Year.
class Year: NSObject {
var name: String
init(name: String) {
self.name = name
}
func isLeaf() -> Bool {
return true
}
}
My view controller looks like this.
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
#IBOutlet weak var outlineView: NSOutlineView!
#IBOutlet var treeController: NSTreeController!
override func viewDidLoad() {
super.viewDidLoad()
addData()
outlineView.delegate = self
outlineView.dataSource = self
}
func addData() {
let root = ["name": "Year", "isLeaf": false] as [String : Any]
let dict: NSMutableDictionary = NSMutableDictionary(dictionary: root)
dict.setObject([Year(name: "1999"), Year(name: "2000")], forKey: "children" as NSCopying)
treeController.addObject(dict)
}
func isHeader(item: Any) -> Bool {
if let item = item as? NSTreeNode {
return !(item.representedObject is Year)
} else {
return !(item is Year)
}
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if isHeader(item: item) {
return outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "HeaderCell"), owner: self)!
} else {
return outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self)!
}
}
}
When I run the program, it causes no issue, but when I expand the node to show the two children of the root, it is giving the error I mentioned above.
Because is isLeaf is used in KVO by NSOutlineView, you have to add #objc in front of isLeaf function:
#objc func isLeaf() -> Bool {
return true
}
The class to which you are binding needs to be KVO compliant.
So, it needs to be a subclass of NSObject.
And the objc runtime needs access.
One way to do this:
#objcMembers
class FileSystemItem: NSObject {
Or, you can annotate each field/function with #objc
Full Example
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.
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.
This question already has answers here:
Initialize class-instance and access variables in Swift
(2 answers)
Closed 8 years ago.
Below is code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
#IBAction func sliderMoved(sender: UISlider) {
var sliderValue = Int(sender.value)
println(sliderValue)
}
#IBOutlet var sliderValue: UISlider!
var arr:[Int] = []
for index in 0...21 {
arr.append(index)
}
println(arr)
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
/*
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
cell.textLabel?.text = sliderValue.value * arr[indexPath.row]
}
*/
}
It gives me the error on line 3.
**I had to delete some code, stack overflow didn't like that much code. I only deleted the viewDidLoad and didReceiveMemoryWarning code.
It's a very simple fix: you have to add a blank between the ++ operator and the opening bracket:
for var i = 0; i < 21; i++ {
^
without that, I think the compiler tries to consider ++{ as an operator.
An alternate way of initializing an array with increasing numbers is:
var arr: [Int] = (0..<21).map {$0}
Update
Code must go inside a method - you cannot execute code at the same level of properties. I suggest initializing that property inline using the alternate method described in my answer, that should work.
If you still want to use inline initialization, but you have more complex code to execute, you can also initialize the property by defining a closure and immediately execute it:
var arr:[Int] = {
var array = [Int]()
for index in 0...21 {
array.append(index)
}
return array
}()
I suggest using the swift syntax for the for loop:
for i in 1..<21 {
arr.append(i)
}