I have a view (created in a StoryBoard) that has a couple of NSTextFields and a slider control. I have one NSTextField that that takes the value of the another NSTextField and a sliderValue.
When I click the "Add" button (which is connected to the add: action of the array controller) all the values are added...except the value in the NSTextField with the "computed value". I can type in that field manually, and it works. Have looked on this site, apple documentation, and general google searches, but I'm not sure why I cannot add this data to the array controller. More details (code)...
NSTextField: Name **Gets added**
SliderValue: Grade **Gets added**
NSTextField: gradebookName **Only adds if manually typed in**
Code:
var txtGradeBookName: String
let sliderValueString = String(sender.integerValue)
txtGradeBookName = txtName.stringValue + "GradeBook" + sliderValueString
gradebookName.stringValue = txtGradeBookName
The ArrayController is bound to a Core Data model. Again, txtName and the Slider value gets added no problem...but gradebookName never gets added (unless I manually type it in)...and it's binding is set up identical to the the other two.
Related
I have a cocoa app written in Swift. The app has a number of NSControls for user entries. The user can use the tab key to move between the controls, most of them being textfields. I am doing all in Code, I do not use the Interface Builder.
Some of them are dynamic: I have a custom NSControl, let's call it myRow which itself is hosting two ´NSTextFields´. myRow itself is hosted in a superView, let's call it myRowManager. When the user makes an entry this myRow notifies the myRowManager, and another myRow is placed below the first one. When one field's content is deleted by the user the myRowManager then deletes that myRow and re-arranges the remaining myRows. The whole setup is mimicking a table view, where there is always one more row than there are entries so that the user can always add an entry at the end.
Using the tab key when modifying existing entries works just fine.
But the nature of my setup means, that when a row is added or deleted a method .layoutSubviews() is called, there all myRows are removed from the superView (the manager) and then new myRows are created, reflecting the new number and order, which is of course reflecting the underlying data.
But by removing the myRow which just had the focus from the view hierarchy sets the window's initial responder as the key-view, as the current and next element in the key-view loop are no longer present.
I therefore am trying to set the key-view manually to the newly created textfield. To do this, a reference to this textfield is kept by the layoutSubviews method.
Unfortunately I have not been successful so far.
What I am doing now (this is the method of the manager which is called by a myRow when an entry is added):
func rowAddedAtEnd(_ index: Int) {
layoutSubviews()
self.window?.recalculateKeyViewLoop()
self.window?.selectKeyView(following:targetView)
}
What strikes me as odd is that there is no method th set a specific view as the key view. I can either use .selectKeyView(following:) or (preceding:)
But anyway - it does not work. The focus ring just gets reset to the top left element in the window.
How can I solve this?
From the documentation of func selectKeyView(following view: NSView):
Sends the nextValidKeyView message to view and, if that message returns an NSView object, invokes makeFirstResponder(_:) with the returned object.
To select targetView, skip nextValidKeyView and call makeFirstResponder(_:).
self.window?.makeFirstResponder(targetView)
First — I even don't know how to describe this problem. I will start from the beginning.
I have a NSTableView with Managed Objects Invoice objects. Invoice has client, date... I can access to them by invoice.client in cells. OK. Works. When I double click on row, next widow with details of selected Invoice opens. It' done by binding Double Click Argument and Double Click Target. I'm passing selectedObjects to method, method loads nib, makes widndow and voila. In NSViewController subclass I have
#objc var invoice : Invoice? {
didSet {print ("Set Invoice \(invoice?.description ?? "no invoice")")}
}
and it prints Invoice. It is set.
And now is a puzzle. If I try to bind ANY UI object (label to invoice.date, invoice.client.name etc..)
window does not pop after double-click and I got an error:
2019-08-31 22:13:45.252956+0200 fakturaVatParser[11444:12617343] [General] [<__NSSingleObjectArrayI 0x60000004e180> addObserver:forKeyPath:options:context:] is not supported. Key path: client
Not only client... every relationship and attribute of Invoice. The same paths which works perfectly in NSTabView cells.
Any ideas?
Basically, what I'm trying to do is pass an integer from one interface controller. The slider in the controller "SecondPage" will get an integer value from the slider and should send it to the interface controller called "ThirdPage" and set the title of that label to the value. I've tried so many methods but all to no avail. I'm just trying to send the context from the 2nd interface controller, receive it in the awake(withContext context: Any?) method in the 3rd controller and save it as the title until the slider is changed again.
Here's what I tried first in the second page controller, but it didn't pass any data:
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
if segueIdentifier == "ThirdPage"{
return sliderval
}
return nil
}
here's what I tried after that, it passed the data but it doesn't "save" it. It pops up the interface controller with the updated label to the slider value, but I don't want it to pop up, I want that value to be saved to the label. When I close the pop up, and swipe right to the Third Page, the label is still called "Label"
self.presentController(withName: "ThirdPage", context: sliderval)
Here's some pictures of the output as well as some of my code. Any help is appreciated, thank you. PS: This is all in Swift 3
You are using a page-based navigation, and you need to pass data between pages.
What I would do is save the data from the slider somewhere that the third page can access, and populate the data in the label on willActivate. The willActivate method should be called by the system when the user swipes between pages.
At the global scope, create a variable to hold the slider value.
var sliderValue: Float = 0
In your second interface controller, set this value when the slider changes.
#IBAction func sliderChanged(_ value: Float) {
sliderValue = value
}
In your third interface controller, when the page is about to appear, set the label to the correct value.
override func willActivate() {
label.setText("\(sliderValue)")
}
If you want to avoid the use of global variables you can read and write from user defaults, or use other tactics, but this will depend on the exact needs of your application.
Your undesired popup problem is happening because you're calling presentController() in sliderChanged(). Every time the slider changes, you're telling it to pop up the "ThirdPage" controller. You can just delete that.
Edit: My answer incorrectly assumed the asker is working with hierarchical navigation. For page-based navigation, there is apparently no way to pass data from one page to the next, so you're stuck with using a global as the accepted answer suggests. The original answer follows:
As for the lack of passing context, I would guess you probably don't have the segue identifier set in Interface Builder, or it's set to something other than "ThirdPage". The segue is separate from the "ThirdPage" controller, and you have to set its identifier separately. Click on the circle in the middle of the segue arrow and take a look at it in the Attributes inspector.
Assuming I'm right about that, the problem is that contextForSegueWithIdentifier() is being called with an identifier that you don't expect. Since it's not "ThirdPage", the conditional fails and it returns nil. Setting the segue identifier should fix it.
It took me forever to figure out how to set up editing on a custom cell view for an NSTableView. Thanks to StackOverflow I figured that much out. P.S. I was doing all of this in Interface Builder.
I have a single column table in which the cell is a custom multi-control NSTableCellView, with:
name (in bold)
description
detail
It's all text. Set up editability on the name only. The table is sorted by the name.
When I edit the name, it has the effect on the bound model that I expect. The table even re-sorts correctly. However, it's displaying incorrectly. The description and detail (not editable) still show up correctly, but the name (which was edited) is a blank. When I inspect the model, it has the correct updated value. But the cell view itself is incorrect.
This doesn't happen all the time--it typically happens if the cell is re-sorted to the top or bottom of the table, but that may be a red herring and may instead have to do with NSTableView cell caching or something.
I hacked up a workaround in which I assign a delegate to the NSTextField (automatically generated for the NSTableCellView) and intercept the textShouldEndEditing event. For some reason this event is getting triggered twice for a given edit (after I press "enter" in the text field)--once for the actual edit where fieldEditor.string is different from the model name, followed by another event where fieldEditor.string is the same as the model name. If I return false for my textShouldEndEditing handler in the latter case, then the cell contents end up being drawn correctly. That's the hack.
I feel like I'm doing something wrong here though, and that shouldn't be necessary.
Is the textShouldEndEditing event supposed to be fired twice?
I am using storyboards for the 1st time, and I cant figure out what I am doing wrong here ... I have a button that transitions from one view controller to another using StoryBoards (the 2nd view is presented modally).
I am trying to use the "prepare for segue" in order to pass the value of a text field from view 1 to view 2, but it is not working. Can somebody tell me what I have wrong here ... ?
View 1:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"saveGame"]) {
statsViewController *svc = (statsViewController *)[segue destinationViewController];
[svc setStatsTextField:gameTextField];
}
}
If there is other code I can post to clarify please let me know.
(for the record there are no errors, the text field on view 2 just doesn't update.)
You cannot just assign a text field in one view controller to a property in another one. This achieves nothing for the text field that is actually in the second view controller's view. Instead, you have to assign a value to the text field's text property. (And ideally, this value should not come directly from another text field because you shouldn't use views to store your app's data. Whenever a text field updates, you should store the updated value in a variable in your view controller or model.)
Also, the statsTextField does not yet exist at the time this code is executed because the destination view controller's view is not yet loaded. You should declare a separate string property in statsViewController (class names should begin with a capital letter btw) and then assign the text field's value in viewDidLoad.
The text field is probably nil at that point since the view hasn't been loaded. You can force this (no problem doing so since its about to happen anyway!) by wrapping your code in an if statement:
if (svc.view)
svc.textfield.text = #"Hello";
Accessing the view property forces the view controller to load the view, if it is not already present.
I notice you seem to be passing a whole textfield object instead of a string to the text property - that doesn't seem like a good idea. It should be more like my example above.