Swift 2.0 and Xcode-beta 7.0 – Using UITextField for numeric input - xcode

Completely frustrated noob here. Surely this isn't as hard as it looks?
I want to use a number entered by the user, perform a calculation, and send the result back to the screen.
I have code working that can use a string forced in by code, convert it to double, do the math and send the result to the screen. For example:
#IBAction func buttonPressed() {
NSLog("Button Pressed")
let decimalAsString = "123.45"
let decimalAsDouble = NSNumberFormatter().numberFromString(decimalAsString as String)!.doubleValue
TempLabel.text = "\(decimalAsDouble+2.45)"
}
This simply adds 2.45 to the string "123.45" and sends the result 125.9 back as a string to my label for display, all when the button is pressed. Great. This simpler form also works:
let decimalAsDouble = Double(decimalAsString)
What I have been struggling with is using a number entered into the UITextField.
My UITextField uses a decimal pad for entry, and I've always had a number entered there when the errors were thrown. (Or did I? The numbers show on screen but are they really "entered"? Hmmm...)
No matter what I try, I cannot find code that will both compile and then not blow up at execution, when the button is pressed. The error I get generally complains about unwrapping an optional nil.
I can detail some of the things that DON'T work, if that helps.

OK, well I've finally solved my own problem. My problem was with closing the entry field and dismissing the numeric entry pad. Once I did that, the entered value became available.
The critical code, in the view controller, was:
func textFieldShouldReturn(textField: UITextField!) -> Bool { //delegate method
textField.resignFirstResponder()
return true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?){
view.endEditing(true)
super.touchesBegan(touches, withEvent: event)
}
This dismisses the entry pad when the user clicks anywhere in the view except for the field itself or a button. Programming that looks for the entered value doesn't blow up.

Related

How did I manage to panic when using the Select Widget

New to Go and Fyne, and stumbling trying to get what I need out of Fyne. Sorry, this will be long.
My problem is this. I’m writing a application that gets a list of commands from a server, telling it to create a series of widgets and display them. They are of various types – Label, Button, Entry, Select, etc.
But these aren’t standard widgets; I need to extend their behavior a bit. For one thing, when operated by the user, they each need access to some per-widget information. Button click, for example, has to reference some data specific to that button so it knows what to do. We’ll call this additional information the About struct.
Secondly, every widget needs to be able to take a right click and drop down a widget specific menu. Yes, even buttons and labels need to be able to provide drop down menus. In the case of a widget like Entry, I know this design is going to condemn me to having to write my own menu choices for Paste, Copy and the other operations Entry normally offers on a right click, but I’m ok with that.
I have all this working, but in the process I broke Select (and probably there will be breakage for other widgets.) and I can’t see how to fix it.
Problem: trying to send the Select widget causes a panic: interface conversion: fyne.Canvas is nil, not *glfw.glCanvas
Approach:
type GenericWidget struct {
fyne.Widget //I’m some kind of widget
about *About //here’s my personal “About” data
//function pointers
OnRightClickp func(gw *GenericWidget, pe *fyne.PointEvent)
OnLeftClickp func(gw *GenericWidget, pe *fyne.PointEvent)
…other “function pointers” for OnRunep and so on…
}
And now I have to catch all “events” so GenericWidget will see them:
func (gw *GenericWidget) TappedSecondary(pe *fyne.PointEvent) {
if (gw.OnRightClickp != nil) {gw.OnRightClickp(gw, pe)}
}
func (gw *GenericWidget) Tapped(pe *fyne.PointEvent) {
if (gw.OnLeftClickp != nil){gw.OnLeftClickp(gw, pe)}
}
//type Focusable interface
//etc….
This should represent any single widget, regardless of type. It’s not complicated: When Tapped is invoked by the driver, GenericWidget.Tapped gets called. If this widget has a function pointer for “OnLeftClickp” set, call it. Crucially, we pass a pointer to the widget itself when that happens, because all the event handlers I write will need access to the *About and maybe anything else I add to GenericWidget.
Creation is simple enough:
func NewGenericWidget(w fyne.Widget, about *About) *GenericWidget {
gw := GenericWidget{w, about, nil, nil, nil, nil, nil, nil, nil, nil}
return &gw
}
And when it’s time to create a Label, I do that and fold it into a GenericWidget
func NewExtendedLabelWithStyle( //Label with catachable left and right click
text string,
about *About,
alignment fyne.TextAlign,
style fyne.TextStyle,
tappedLeft func(*GenericWidget, *fyne.PointEvent),
tappedRight func(*GenericWidget, *fyne.PointEvent)) *GenericWidget {
e := NewGenericWidget(widget.NewLabelWithStyle(text, alignment, style), about)
e.OnLeftClickp = tappedLeft
e.OnRightClickp = tappedRight
return e
}
All these GenericWidgets work fine – I add them to the appropriate Box and the window paints as I’d expect. I can right click on a label and if OnRightClickp is set, which is generally is, code gets called and is given access to *GenericWidget, which leads to *About, which means the menu that gets put up can offer all the right stuff for what’s in this label.
Of course, Labels don’t normally care about clicking, so the fact that I’ve stolen all the calls in Tappable doesn’t matter.
But a Select widget does care about left clicks, so the fact that GenericWidget is intercepting the call to Tapped() means I’d never see the dropdown appear.
No problem, I thought. When I create the Select widget and the surrounding GenericWidget, I’ll just specify I want to call Select’s Tapped myself:
func NewExtendedSelect(about *About, sel func(*GenericWidget, string)) *GenericWidget {
//build the options. NewSelect demands a slice of strings, so...
st := make([]string, 64)
for e := about.theList.Front(); e != nil; e = e.Next() {
st = append(st, e.Value.(string))
}
s := widget.NewSelect(st, func(c string){})
//make sure it selects the text it should, initially
s.SetSelected(about.value)
//wrap it
e := NewGenericWidget(s, about)
//e.OnChangedp = sel //not set up yet (and don’t know how)
//HERE BE DRAGONS --------------
//But we don't want to break left click. So when we intercept it,
// pass it down to the actual Selection widget:
e.OnLeftClickp = func(me* GenericWidget, pe *fyne.PointEvent) {
fmt.Println("select tapped")
//call the Tapped that Select knows about:
s.Tapped(pe) //PANIC!
}
// -----------------------------
//Handle right click with our usual menu magic
e.OnRightClickp =func(me* GenericWidget, pe *fyne.PointEvent) {
//defer used because not sure what to do with the return value, so make it Go's problem
defer widget.NewPopUpMenuAtPosition(me.GetRightClickMenu(),
me.about.sheet.window.Canvas(), pe.AbsolutePosition)
}
return e //here's your GenericWidget, ready to drop into a Fyne container.
}
Compiles fine, Selects get displayed, but when I left click it prints the expected “select tapped” and then immediately panics:
select tapped
panic: interface conversion: fyne.Canvas is nil, not *glfw.glCanvas
I’m lost. My GenericWidget is just a widget; I thought that’s what composition did, and all the GenericWidgets I create are put in a Box which is in a Box which is SetContent into the window. But the error is suggesting to me that somehow this Select object wasn’t set up right and when it goes to draw the options, something is missing. What did I do wrong?
It’s possible my whole approach is wrong (I do a lot of C++ and Python and I take an OO view of things). In that case, how do I do all this?
This turns out to have been a Fyne bug, which has since been fixed.
It does not look like the select is being composed - your extended select is just creating a new one, tapping it later.
The crash is coming from it trying to display a pop up next to a select that has not been found on the canvas.
If you are extending builtin widgets you also need to call ExtendBaseWidget so that the driver can lookup your widget instead of the default one.
That said a single widget that can extend any other type of widget kind of goes against the strongly types design of Fyne APIs and you may run in to trouble.

Replacing the ESC button on a NSTouchBar

I am building a NSTouchBar for my app using storyboard.
I want to replace the ESC button with something else.
As usual, there is no doc telling you how to do that.
I have searched the web and I have found vague informations like
You can change the content of "esc" to something else, though, like
"done" or anything, even an icon, by using
escapeKeyReplacementItemIdentifier with the NSTouchBarItem.
But this is too vague to understand.
Any ideas?
This is what I did so far.
I have added a button to NSTouchBar on storyboard and changed its identifier to newESC. I added this line programmatically:
self.touchBar.escapeKeyReplacementItemIdentifier = #"newESC";
When I run the App the ESC key is now invisible but still occupies its space on the bar. The button that was supposed to replace it, appears next to it. So that bar that was
`ESC`, `NEW_ESC`, `BUTTON1`, `BUTTON2`, ...
is now
`ESC` (invisible), `NEW_ESC`, `BUTTON1`, `BUTTON2`, ...
The old ESC is still occupying its space on the bar.
This is done by creating a touch bar item, let's say a NSCustomTouchBarItem containing a NSButton, and associating this item with its own identifier.
Then with another identifier you do your usual logic but you add the previously created identifier as the ESC replacement.
Quick example in Swift:
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
switch identifier {
case NSTouchBarItemIdentifier.identifierForESCItem:
let item = NSCustomTouchBarItem(identifier: identifier)
let button = NSButton(title: "Button!", target: self, action: #selector(escTapped))
item.view = button
return item
case NSTouchBarItemIdentifier.yourUsualIdentifier:
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = NSTextField(labelWithString: "Example")
touchBar.escapeKeyReplacementItemIdentifier = .identifierForESCItem
return item
default:
return nil
}
}
func escTapped() {
// do additional logic when user taps ESC (optional)
}
I also suggest making an extension (category) for the identifiers, it avoids making typos with string literals:
#available(OSX 10.12.2, *)
extension NSTouchBarItemIdentifier {
static let identifierForESCItem = NSTouchBarItemIdentifier("com.yourdomain.yourapp.touchBar.identifierForESCItem")
static let yourUsualIdentifier = NSTouchBarItemIdentifier("com.yourdomain.yourapp.touchBar.yourUsualIdentifier")
}

How to Get Notified When the Insertion Point Leaves a NSComboBox

I want to gain control when the insertion point leaves a combobox so that I can test if the text matches an entry in the combox's list. I've already added a delegate to my view controller for other combobox functions, for example:
func comboBox(_ aComboBox: NSComboBox, completedString string: String) -> String?
The following function looks like what I want:
func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool
My App Code IDE identifies it as being in the API. When I run my code, it doesn't get invoked (I have a print statement in it). I tried changing "control" to "comboBox" without success. Do I need to do something additional to get this function to be called?
Update
As was pointed out by #Mangerlahn, the function needs to be in the datasource. I moved it there with no effect. It was in the delegate.
extension TrackDialogViewController: NSComboBoxDataSource {
...
func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {...}
...
}
Stupid User Error. I lost a RAID drive and had to restart. XCode got messed up and was displaying two window for my project. The one I was looking at didn't have the console.

Access data from table view cell in Swift

I'm having trouble accessing an optional string in swift from a table view cell. Each cell has a title label as well as a detail text label and the amount of cells at any given time in the table view is dependent on the user (the table view is used to display saved values). Since the strings saved as the detail text label's text in each cell are way too long to read in the table view, I wish to load them in another view that is segued to when a cell is pressed. This is my prepare for segue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "cellSegue"
{
let cell = tableView.cellForRowAtIndexPath(tableView.indexPathForSelectedRow()!)
var transfer : ExplanationView = segue.destinationViewController as ExplanationView
if let unwrapped = cell?.textLabel?.text!
{
transfer.infoText.text = unwrapped
}
}
}
When a segue is performed, I get a fatal crash, the offending line is "transfer.infoText.text = unwrapped" and Xcode tells me it found nil when unwrapping an optional. During my debuggingg efforts, I've tried "println(cell?.textLabel?.text!)" and Xcode prints "Optional(String)" where "String" is the actual string I'm trying to access so it seems like I'm on the right track, but obviously there's something I'm missing. I've also tried "cell?.textLabel?.text" but I get the same error. Any help is appreciated
You shouldn't ever extract data from a view. You should extract data from a model. The fact that you have a tableView with cells implies you have a model for the data that goes in those cells. It might be an array of strings or whatever. Instead of trying to extract the data from the cells, just get the data from the data model you used to populate the cells. Something like:
let selectedIndexPath = tableView.indexPathForSelectedRow()
let selectedData = data[selectedIndexPath.row]
Your problem is not unwrapped but instead transfer.infoText.text. Your IBOutlets are not set up at the time of the prepareForSegue. You need add a property (var) to your destination view controller to hold the unwrapped value. Then in viewDidLoad when the IBOutlets are set up, copy the string to your text field.

NSTextField controlTextDidEndEditing: called while being edited (inside an NSOutlineView)

In my NSOutlineView, I have a NSTextField inside a NSTableCellView. I am listening for the controlTextDidEndEditing: notification to happen when the user finishes the editing. However, in my case, this notification is being fired even while the user is in the middle of typing, or takes even a second-long pause in typing. This seems bizarre. I tested a NSTextField in the same view, but outside of the NSOutlineView, and it doesn't behave this way; it only calls controlTextDidEndEditing: if the user pressed the Tab or Enter keys (as expected).
Is there something I can do to prevent the NSTextField from sending controlTextDidEndEditing: unless a Enter or Tab key is pressed?
Found a solution for this:
- (void)controlTextDidEndEditing:(NSNotification *) notification {
// to prevent NSOutlineView from calling controlTextDidEndEditing by itself
if ([notification.userInfo[#"NSTextMovement"] unsignedIntegerValue]) {
....
It's an old question, but for reference, I ran into a similar problem where controlTextDidEndEditing: was called at the beginning of the editing session.
My workaround is to check if the text field still has the focus (i.e. cursor):
func controlTextDidEndEditing(_ obj: Notification) {
guard
let textField = obj.object as? NSTextField,
!textField.isFocused
else {
return
}
...
}
public extension NSTextField
{
public var isFocused:Bool {
if
window?.firstResponder is NSTextView,
let fieldEditor = window?.fieldEditor(false, for: nil),
let delegate = fieldEditor.delegate as? NSTextField,
self == delegate
{
return true
}
return false
}
}
Note to self:
I ran into this problem when adding a new item to NSOutlineView and making it editable with NSOutlineView.editColumn(row:,with:,select).
controlTextDidEndEditing() would be called right away at the start of the editing session.
It turns out it was a first responder/animation race condition. I used a NSTableView.AnimationOptions.slideDown animation when inserting the row and made the row editable afterwards.
The problem here is that the row is made editable while it is still animating. When the animation finishes, the first responder changes to the window and back to the text field, which causes controlTextDidEndEditing() to be called.
outlineView.beginUpdates()
outlineView.insertItems(at: IndexSet(integer:atIndex),
inParent: intoParent == rootItem ? nil : intoParent,
withAnimation: .slideDown) // Animating!
outlineView.endUpdates()
// Problem: the animation above won't have finished leading to first responder issues.
self.outlineView.editColumn(0, row: insertedRowIndex, with: nil, select: true)
Solution 1:
Don't use an animation when inserting the row.
Solution 2:
Wrap beginUpdates/endUpdates into an NSAnimationContext group, add a completion handler to only start editing once the animation finished.
Debugging tips:
Observe changes to firstResponder in your window controller
Put a breakpoint in controlTextDidEndEditing() and take a very close look at the stack trace to see what is causing it to be called. What gave it away in my case were references to animation calls.
To reproduce, wrap beginUpdates/endUpdates in an NSAnimationContext and increase the animation duration to a few seconds.

Resources