Can't setup bindings in Swift Storyboard? - macos

I've got some code that runs a fairly complex algorithm. I want to put together a fairly simple UI that will allow me to monitor the values of the various variables in the algorithm in some graphical ways -- think of it like a dashboard of sorts.
So, for simplicity's sake, let's say I have an algorithm like what follows. It searches a vector of values for the two values that most closely sum to a target value:
import Foundation
class algorithm {
var numbers = [Double]()
let numberOfRandoms = 1000
dynamic var a: String
dynamic var b: String
init () {
// Load initial vector with some random numbers between 0 and 1
for _ in 1...numberOfRandoms {
numbers.append(Double(arc4random()) / Double(UINT32_MAX))
}
a = " "
b = " "
}
func findTheTwoNumbersThatAddUpTheClosestToTarget(target: Double) {
//Initializing this to a very large value
var currentBestSum = 1000.0
//Begin brute force search for the optimal solution
for i in 0...numbers.count-2 {
for j in i+1...numbers.count-1 {
//Check to see if the current candidate exceeds the best solution
if abs(numbers[i] + numbers[j] - target) < currentBestSum {
//If it does, store the new champion
a = String(i)
b = String(j)
//And reset the current top score to match
currentBestSum = abs(numbers[i] + numbers[j]-target)
}
}
}
}
}
Now, this is just a simple (and silly) example, but it suits these purposes. I basically want to create a simple UI that displays the important values in the process as it runs (dynamically).
In this example, let's say that I just want to display two labels that contain the index values of the two leaders as the algorithm executes.
I created the labels in the storyboard.
Then, I created IBOutlets in the ViewController (Actually, storyboards did it for me when I Ctrl-dragged):
class ViewController: NSViewController {
#IBOutlet weak var a: NSTextField!
#IBOutlet weak var b: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
Then, I ctrl-dragged the labels to the a and b in the algorithm class to create the bindings.
Finally, I create an class variable in the view controller and instantiate it in the viewDidLoad method. This doesn't seem like the right thing to do -- maybe it is. Seems like you would want to keep separate your interface and data...
The labels do, in fact, show up -- but they never show any values of a and b. They just show the default text.
Not sure what I'm doing wrong.
Help!?
P.S., in response to Anthony Kong, I do recognize that I could manually synchronize all the view elements in the code, but I thought the whole point of using bindings was to avoid having to do this manual synchronization. I just can't figure out how to set it up.

Without commenting on your specific code I think I have experienced (and solved) the problem you describe. I was able to write an app that had two targets, one NIB-based and one Storyboard-based. As much as I was able I duplicated the code in each and shared the common data instance that I was trying to display in a TableView. The NIB-based app worked using the stock Cocoa Bindings that I set in IB. But the Storyboard-based app did not, the array controller did not see the data.
My solution was simply to add the binding for contentArray programmatically in viewDidLoad. The one line that fixed it for me is:
ac.bind("contentArray", toObject: cd, withKeyPath: "people", options: nil)
ac is the IBOutlet for the ArrayController in the Storyboard. cd is the class instance that contains the people array.
This is using XCode 6.2 (6C107a) which is Beta 3 I think.
This was the only binding that I had to set myself, the TableView to ArrayController (arrangedObjects) and TableViewCell to TableView (objectValue) didn't need any tweaking.

There are several problems with your code
1) In your code,
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
var test = algorithm()
test.findTheTwoNumbersThatAddUpTheClosestToTarget(0.5)
}
The variable test goes out of scope when the function exits. Based on the wording of your question, you are expecting it to be a long running process. So this will not do what you want.
You should
a) Add test as class variable to the ViewController class
b) instantiate the variable in viewDidLoad method.
2) In your algorithm it does not actually provide any feedback to the labels. Maybe you think because the class has the ivar a and b so they are hooked to the IBOutlet by the same names. But of course it is not the case. And you do not need the keyword dynamic too.
What you should do is:
a) provide a method in the View Controller class to update the labels. It will serve as a callback function to be used by algorithm class to feedback the calculation result.
It may look like this:
func update_value_callback(vala: String, valb: String) {
a.text = vala; // updating the label here
b.text = valb;
}
b) make the algorithm class calls the callback function e.g.
func findTheTwoNumbersThatAddUpTheClosestToTarget(target: Double, viewController: ViewController) {
// do your stuff
...
// execute the callback
viewController.update_value_callback(a, b)
}

Related

Xcode9 Swift4.2 NSOutlineView NSTreeController data

I am trying to create an outlineview in a MacOS app that has mutliple levels that are summaries for a set of data held in SQLite3. I have an outlineview working with a treecontroller with a very simple NSMutuableDictionary based on a model class.
import Cocoa
class Summary: NSObject {
#objc dynamic var name: String
#objc dynamic var trades: Int
#objc dynamic var avgPL: Double
#objc dynamic var pandl: Double
#objc dynamic var parent: String
#objc dynamic var isLeaf: Bool
#objc dynamic var childCount: Int
#objc dynamic var children: [Summary] = []
init(name: String, trades: Int, avgPL: Double, pandl: Double, parent: String, isLeaf: Bool,childCount: Int) {
self.name = name
self.trades = trades
self.avgPL = avgPL
self.pandl = pandl
self.parent = parent
self.isLeaf = isLeaf
self.childCount = childCount
}
#objc func add(child: Summary) {
children.append(child)
}
}
My simple example data is:
let root: [String : Any] = ["name": "Overall","trades":5,"avgPL":200,"pandl":500,"parent":"","isLeaf": false,"childCount": 2 ]
let dict: NSMutableDictionary = NSMutableDictionary(dictionary: root)
let l2a = Summary(name: "L2a", trades: 3, avgPL: 100, pandl: 300, parent: "L1",isLeaf: true,childCount: 0)
let l2b = Summary(name: "L2b", trades: 2, avgPL: 100, pandl: 200, parent: "L1",isLeaf: true,childCount: 0)
dict.setObject([l2a,l2b], forKey: "children" as NSCopying)
I pass the dictionary to the treeController:
treeController.addObject(dict)
And that works nicely giving me a collapsible outline:
But I have no idea how to add more levels or children to the children. I want to have up to four levels deep in the outline. I have all the SQL summaries working and I have tried so many variations of populating arrays and trying to create a dictionary with the data to no avail. I have children and childCount and isLeaf set on everything but treecontroller does not like the array complaining that isLeaf is not KVO compliant. My data in an array looks like this (not all of the data but enough to see what I'm doing) The main level and all of the subsequent children are all based on the Summary model class above. Can I simply convert this array to a dictionary? Or, can I make it KVO compliant by adding keys to the model class or something? I have all of the 4 levels in separate arrays I use to build the resultant array if that is useful :
I should add that I have an NSObject defined as an NSMutableArray and its content tied to the treeController. My treeController is bound to each variable in the model class and at the top level has:
If I pass the array I have built to the treeController I get the following error:
Failed to set (contentViewController) user defined inspected property on (NSWindow): [<_TtGCs23_ContiguousArrayStorageC11outlinetest7Summary_ 0x604000445160> addObserver:forKeyPath:options:context:] is not supported. Key path: isLeaf
After building out my NSOutlineView without an NSTreeController and getting everything working I still wanted to get back to this and implement the treeController in order to take advantage of the sorting mechanism it provides. And I did find as per my last comment that I did have something wrong in InterfaceBuilder that was causing it to complain about KVO compliance. I had everything wired correctly except for the Content Array binding on the treeController. Here I bound it to my ViewController and added my data array reportSummary to the Model Key Path.
I also no longer needed to manually add my data array to the treeController using treeController.addObject(reportSummary). Once this was working I was then able to implement sorting and everything is working well. I should point out two things.
Setup of sorting on the TreeController is slightly different than on an ArrayController tied to a TableView. With the tableview it was sufficient to specify which columns are sortable in the identity inspector in IB. But in the outlineView scenario I also needed to setup bindings in IB to the treeController and change the Controller Key from arrangedObjects to sortDescriptors.
While testing my tree controlled outlineview I ran into a problem when I double-clicked on a summary row. I had implemented Double Action on the outlineView in IB in order to control the expanding and collapsing of summary sections. Note that I read about doing this in a thread here and someone mentioned that you would need to maintain multiple arrays and track indexes because once a row is collapsed or expanded that changes the row number of all the subsequent rows. But I figured out that the solution is simply to iterate through rows in reverse order and expand or collapse them working back up the tree starting from outlineView.numberOfRows-1. This works well and along with Double Action (clicking) to expand and collapse I also added an NSSlider which tracks to the expansion level and lets me collapse all the lowest levels moving back up the tree instead of clicking all of the little arrows on each row. This broke when I implemented the treeController. I received an error
Could not cast value of type 'NSKVONotifying_NSTreeControllerTreeNode'
This line of code was the problem
let summary = reportOutline.item(atRow: x) as! Summary
I had to change this to
let node = reportOutline.item(atRow: x) as! NSTreeNode
let summary = node.representedObject as! Summary
And that is it. Working beautifully now.

Modify reference content of an object in swift

Given a stackView object, I got an array of stackView.arrangedSubviews
which is a get only property. Hence I can't REPLACE one item of it by merely doing
stackView.arrangedSubviews[i] = newSubView
what came to my mind is I want to replace the content of the reference at stackView.arrangedSubviews[i] with the content of newSubView. For instance if this was in C, I would have done something like
*arrangedSubViews[i] = *newSubView
I know there is a way to do what I need by removing arrangedSubViews and utilizing addArrangedSubView but it won't be very efficient.
You can't modify an immutable array, and this is a good thing in many ways.
You can create an extension method to replace the view like this:
#available(iOS 9.0, *)
extension UIStackView {
func replaceView(atIndex index: Int, withView view: UIView) {
let viewToRemove = arrangedSubviews[index]
removeArrangedSubview(viewToRemove)
insertArrangedSubview(view, atIndex: index)
}
}
Then, instead of using stackView.arrangedSubviews[i] = newSubView you can use this code:
stackView.replaceView(atIndex: i, withView: newSubView)
Performance-wise there's no negative impact on doing it this way.

How can I bind the text in a NSTextField to the value of a property on a controller using a storyboard?

I have a custom view with two text subviews, arranged, not that it matters, as per this amazing ASCII art:
/--------\
| lblOne |
| lblTwo |
\--------/
On my controller, I have a property of type Thingy:
class AwesomeController: NSViewController {
var thingy: Thingy! = nil
}
A Thingy has two properties of interest:
class Thingy: NSObject {
var one: String
var two: String
}
I would like to set up a binding between lblOne's string value and thingy.one, and lblTwo's string value and thingy.two, going through a custom view class if necessary.
When thingy is changed, obviously the two text fields should also change. (In other words, it should behave normally for a cocoa binding.)
I think it's probably a combination of learning Swift and my unfamiliarity with storyboards on OS X (last time I did cocoa development, it was still xibs), but I can't work out how to link the damn thing up.
Getting bindings to work in Swift requires two additional steps:
All the vars must be marked with dynamic, eg:
dynamic var one: String
You have to recompile the project (with cmd+B, not just in the background) in order for the var to appear as an option in IB

UITextInputDelegate methods not functioning correctly

I'm working on iOS 8 custom keyboard extension right now, and there are some issues in using UITextInputDelegate Methods.
Does this right: selectionWillChange: and selectionDidChange: methods should be called when user long-presses typing area? And textWillChange: and textDidChange: methods should be called whenever the text is literally changing?
Actually, what I observed is that, when I changed selection in text input area, textWillChange: and textDidChange: are called, and I cannot get a clue that the other two methods are called in what condition. If anyone knows about the usage of these delegate methods, please let me know.
It's not working for me either... what I am currently doing is just using textWillChange and textDidChange which does get called, as you mentioned, when you change your selection... (they get called BEFORE and AFTER) And then comparing the: self.textDocumentProxy.documentContextBeforeInputself.textDocumentProxy.documentContextAfterInput From BEFORE (textWillChange) to the AFTER (textDidChange) to see if selection range or length changed at all.
Something like this (set the 4 NSStrings below in your .h file of course... haven't tested this exact snippet because I wrote it from scratch just now on SO.com but I'm sure the principle works if I made any errors)
- (void)textWillChange:(id<UITextInput>)textInput {
beforeStringOfTextBehindCursor = self.textDocumentProxy.documentContextBeforeInput;
beforeStringOfTextAfterCursor = self.textDocumentProxy.documentContextAfterInput;
}
- (void)textDidChange:(id<UITextInput>)textInput {
afterStringOfTextBehindCursor = self.textDocumentProxy.documentContextBeforeInput;
afterStringOfTextAfterCursor = self.textDocumentProxy.documentContextAfterInput;
BOOL didSelectionChange = NO;
if (![beforeStringOfTextBehindCursor isEqualToString:afterStringOfTextBehindCursor]) {
didSelectionChange = YES;
}
if (![beforeStringOfTextAfterCursor isEqualToString:afterStringOfTextAfterCursor]) {
didSelectionChange = YES;
}
if (didSelectionChange) {
NSLog(#"Selection Changed!");
}
}
I had the same problem with the functions specified in the UITextInput protocol not being called. The reason as far as I can discern is due to the fact that the inputDelegate is set at runtime. According to the ios docs:
The UIKit provides a private text input delegate, which it assigns at runtime to the inputDelegate property of the object whose class adopts the UITextInput protocol. link
The fix which works in my case is to reset the inputDelegate in the function:
textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
by the following line:
[myUITextField setInputDelegate:self];
where self implements the UITextInputDelegate protocol.

Swift function swizzling / runtime

Before Swift, in Objective-C I would swizzle or hook methods in a class using <objc/runtime.h>.
If anyone has any info on the topic of modifying Swift's runtime and hooking functions like CydiaSubstrate and other libraries that helped in this area, please inform me.
I've succeed with method swizzling in Swift. This example shows how to hook description method on NSDictionary
My implementation:
extension NSDictionary {
func myDescription() -> String!{
println("Description hooked")
return "Hooooked " + myDescription();
}
}
Swizzling code:
func swizzleEmAll() {
var dict:NSDictionary = ["SuperSecret": kSecValueRef]
var method: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("description"))
println(dict.description) // Check original description
var swizzledMethod: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("myDescription"))
method_exchangeImplementations(method, swizzledMethod)
println(dict.description) //Check that swizzling works
}
Edited:
This code will work for any custom Swift class that inherits from NSObject (but will not work for classes that don't.) More examples - https://github.com/mbazaliy/MBSwizzler
You would likely be able to swizzle swift-generated classes that inherit from Objective-C classes with no problem, since they appear to use dynamic method dispatch all the time. You may be able to swizzle methods of swift-defined classes that exist in the Objective-C runtime by virtue of being passed across the bridge, but the Objective-C side methods are likely to just be proxies back across the bridge to the swift-side runtime, so it's not clear that it'd be particularly helpful to swizzle them.
"Pure" swift method calls do not appear to be dispatched dynamically via anything like objc_msgSend and it appears (from brief experimentation) that the type safety of swift is implemented at compile time, and that much of the actual type information is absent (i.e. gone) at runtime for non-class types (both of which likely contribute to the purported speed advantages of swift.)
For these reasons, I expect that meaningfully swizzling swift-only methods will be significantly harder than swizzling Objective-C methods, and will probably look a lot more like mach_override than Objective-C method swizzling.
I'm answering this question more than one year later because none of the other answers provide the definitive set of requirements for method swizzling for every kind of class.
What is described by other, while it will work flawlessly for extensions to foundation/uikit classes (like NSDictionary), will simply never work for your own Swift classes.
As described here, there is an additional requirement for method swizzling other than extending NSObject in your custom class.
The swift method you want to swizzle must be marked dynamic.
If you don't mark it, the runtime will simply continue to call the original method instead of the swizzled one, even if the method pointers appear to have been swapped correctly.
Update:
I've expanded this answer in a blog post.
I had a Xcode 7 iOS project written in Swift 2, using Cocoapods. In a specific Cocoapod, with Objective-C source, I wanted to override a short method, without forking the pod. Writing a Swift extension wouldn't work in my case.
For using method swizzling, I created a new Objective-C class in my main bundle with the method I wanted to replace/inject into the cocoapod. (Also added the bridging header)
Using mbazaliy 's solution on stackflow, I put my code similar to this into the didFinishLaunchingWithOptions in my Appdelegate:
let mySelector: Selector = "nameOfMethodToReplace"
let method: Method = class_getInstanceMethod(SomeClassInAPod.self, mySelector)
let swizzledMethod: Method = class_getInstanceMethod(SomeOtherClass.self, mySelector)
method_exchangeImplementations(method, swizzledMethod)
This worked perfectly. The difference between #mbazaliy 's code is that I didn't need to create an instance of the SomeClassInAPod class first, which in my case would have been impossible.
Note: I put the code in the Appdelegate because every other time the code runs, it exchanges the method for the original - it should only run one time.
I also needed to copy some assets that were referenced in the Pod's bundle to the main bundle.
I wouldn't do it that way, I think closures provide the answers (as they give you a chance to intercept, evaluate, and forward the invocation of the function, additionally it will be easy to extend when and if we have reflection.
http://www.swift-studies.com/blog/2014/7/13/method-swizzling-in-swift
I would like to extend the great answer provided by mbazaliy.
Another way of doing swizzling in Swift is by providing an implementation using an Objective-C block.
e.g. to replace descriptionmethod on class NSString we can write:
let originalMethod = class_getInstanceMethod(NSString.self, "description")
let impBlock : #objc_block () -> NSString =
{ () in return "Bit of a hack job!" }
let newMethodImp = imp_implementationWithBlock(unsafeBitCast(impBlock, AnyObject.self))
method_setImplementation(originalMethod, newMethodImp)
This works as of Swift 1.1.
A safe, easy, powerful and efficient hook framework for iOS (Support Swift and Objective-C). https://github.com/623637646/SwiftHook
For example, this is your class
class MyObject {
#objc dynamic func noArgsNoReturnFunc() {
}
#objc dynamic func sumFunc(a: Int, b: Int) -> Int {
return a + b
}
#objc dynamic class func classMethodNoArgsNoReturnFunc() {
}
}
The key words of methods #objc and dynamic are necessary
The class doesn't have to inherit from NSObject. If the class is written by Objective-C, Just hook it without any more effort
Perform the hook closure before executing specified instance's method.
let object = MyObject()
let token = try? hookBefore(object: object, selector: #selector(MyObject.noArgsNoReturnFunc)) {
// run your code
print("hooked!")
}
object.noArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
Perform the hook closure after executing specified instance's method. And get the parameters.
let object = MyObject()
let token = try? hookAfter(object: object, selector: #selector(MyObject.sumFunc(a:b:)), closure: { a, b in
// get the arguments of the function
print("arg1 is \(a)") // arg1 is 3
print("arg2 is \(b)") // arg2 is 4
} as #convention(block) (Int, Int) -> Void)
_ = object.sumFunc(a: 3, b: 4)
token?.cancelHook() // cancel the hook
The key word #convention(block) is necessary
For hook at before and after. The closure's args have to be empty or the same as method. The return type has to be void
Totally override the mehtod for specified instance. You can call original with the same parameters or different parameters. Don't even call the original method if you want.
let object = MyObject()
let token = try? hookInstead(object: object, selector: #selector(MyObject.sumFunc(a:b:)), closure: { original, a, b in
// get the arguments of the function
print("arg1 is \(a)") // arg1 is 3
print("arg2 is \(b)") // arg2 is 4
// run original function
let result = original(a, b) // Or change the parameters: let result = original(-1, -2)
print("original result is \(result)") // result = 7
return 9
} as #convention(block) ((Int, Int) -> Int, Int, Int) -> Int)
let result = object.sumFunc(a: 3, b: 4) // result
print("hooked result is \(result)") // result = 9
token?.cancelHook() // cancel the hook
For hook with instead. The closure's first argument has to be a closure which has the same types with the method. The rest args and return type have to be the same as the method.
Perform the hook closure before executing the method of all instances of the class.
let token = try? hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.noArgsNoReturnFunc)) {
// run your code
print("hooked!")
}
MyObject().noArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
Perform the hook closure before executing the class method.
let token = try? hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.classMethodNoArgsNoReturnFunc)) {
// run your code
print("hooked!")
}
MyObject.classMethodNoArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
After spending some time on it... Wake up this morning.... beta 6 is out and
Problem Fixed in beta6!
From release notes
"Dynamic dispatch can now call overrides of methods and properties introduced in class extensions, fixing a regression introduced in Xcode 6 beta 5. (17985819)!"

Resources