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)
Related
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.
I have a strange problem: in my table view I want to update a single cell when its UISlider gets modified (value changed control event). At the moment the value of the slider changes, I want to animate a button into the cell (sliding in from the right). I set the constant of the button's constraint to -50 to make sure it's not visible by default, and when the slider's value gets changed, a method gets called which updates the table view, so cellForRowAtIndexPath gets called again for all cells in my table. Simplified it looks something like this:
func valueChanged(sender:UISlider) {
// Reload table view
self.myTableView.reloadData()
// Let table view know the value of the slider got modified
self.didChangeValueOfSlider = true
}
And in cellForRowAtIndexPath I'm keeping track of which cell's slider got changed by using a custom selectedCellIndexPath variable. When the table view gets to the cell that got modified: it runs the following code:
// Check if cell is selected
if indexPath == self.selectedCellIndexPath {
// Check if value of slider was changed
if self.didChangeValueOfSlider == true {
// Value was changed: set constraint back to default
saveButtonTrailingConstraint.constant = CGFloat(15)
self.view.setNeedsUpdateConstraints()
self.view.updateConstraintsIfNeeded()
// Reset slider update status
self.didChangeValueOfSlider = false
}
}
Calling those setNeedsUpdateConstraints() and updateConstraintsIfNeeded() might be overkill or unnecessary, but please note this is my 15th attempt or so to actually get the view to display the updated layout. I used breakpoints to confirm the constant actually changes, after the code above is finished running and everything works perfectly fine. The only thing I can't get working is the updating part. I've tried UIView.animateWithDuration to animate the change, and I've tried methods like layoutIfNeeded() and beginUpdates() and endUpdates of the table view: nothing works. What could be the reason the layout doesn't get updated? And what am I supposed to be calling the layout and update methods on? I've been calling them on self.view but I'm not even sure if you're supposed to be calling it on the view if you're trying to update the layout of a table view cell. Any help would be appreciated.
I figured it out. The problem turned out to be the constraint itself. as I was accessing the wrong one. I defined it as saveButtonTrailingConstraint but I was actually modifying was the saveButton's width constraint (which apparently can't be animated). Instead of accessing saveButton.constraints[indexOfTrailingConstraint] I should have defined saveButton.superview!.constraints[indexOfTrailingConstraint] as the constraint belongs to the superview and not to the button itself. I guess the best thing to take away from this clumsy mistake is to always double-check if you're modifying the correct constraint if you're doing it programmatically, because it doesn't always show on the first eye.
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.
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 have a DataGridCheckBoxColumn in my DataGrid which is to indicate the rows the user has selected. I want the checkboxes to be checked/unchecked with a single click. Making the column editable (i.e. IsReadOnly="False") means the user has to click twice (first click just selects the row, 2nd click changes the checkbox), so I decided to set/clear the property the column is bound to in the view model code in response to the SelectionChanged trigger firing.
Setting/clearing the property works fine, however as soon as I call NotifyPropertyChanged("name of collection the grid is bound to") to get the view to show the change, this causes the SelectionChanged trigger to fire again. This loops about 10 times until an exception is thrown.
If I remove the call to NotifyPropertyChanged, the SelectionChanged trigger fires once, but of course I don't see any change in the UI. The collection is a PagedCollectionView if this makes any difference.
How can I get this to work? Note - I am using MVVM pattern, so everything is done with bindings to View Model (no code behind).
Thanks
Sounds like you have a infinite loop by design.
but try using the selectionchanging instead of selectionchanged,
or put a isloading flag in your viewmodel and dont call the inotify if the isloading is true
I found a very simple solution that doesn't involve triggers or code behind. See: Silverlight single-click checkbox DataGrid columns
It seems to work by using a column template, but only providing the CellEditingTemplate and no CellTemplate.