I'm having a problem finding out which NSTextfield is focused.
I am building a multi-language form and have several NSTextfields for data entry. I have to change the text input source for some of the NSTextfields during data entry, and I need it to happen automatically.
For now, I can change the text input source as I mentioned here without problem.
The problem that I have is to change the input source right when the NSTextfield becomes focused. If I use the controlTextDidBeginEditing: delegate method it changes the source input after typing the first letter.
This means that I lose the first word I typed in proper language.
Is there any delegate to find it ?
You can subclass your NSTextField and override - (BOOL)becomeFirstResponder (NSResponder) to respond to this kind of event.
You can also try control:textShouldBeginEditing: instead.
You will need to subclass NSTextField
Swift 3+
class FocusingTextField : NSTextField {
var isFocused : Bool = false
override func becomeFirstResponder() -> Bool {
let orig = super.becomeFirstResponder()
if(orig) { self.isFocused = true }
return orig
}
override func textDidEndEditing(_ notification: Notification) {
super.textDidEndEditing(notification)
self.isFocused = false
}
override func selectText(_ sender: Any?) {
super.selectText(sender)
self.isFocused = true
}
}
self.view.window?.firstResponder inside your view controller would give a NSTextView
Related
I want to use .submitLabel to change the "return" key to something else (namely, .done) but it does not appear to work with the TextEditor input form. Is it supposed to?
Sample code:
struct ContentView: View {
#State var text: String = ""
var body: some View {
Form {
TextEditor(text: $text)
.submitLabel(.search)
}
}
}
Upcoming Support
Support was added in Xcode 13.2 beta 3, which is not yet available.
Temporary Fix
Ok, so based on my attempts, .submitLabel(...) doesn't function with TextEditor. There is a possible solution where you add a Text(...) as a subview to the TextEditor(...) but that is extremely jenk and I wouldn't recommend that. There is however a solution I found for iOS 13 that seems to function the exact same way that the TextEditor appears to work and that's to use UIViewRepresentable. Thankfully it's very easy to implement. Create this struct.
struct TextView: UIViewRepresentable {
typealias UIViewType = UITextView
var configuration = { (view: UIViewType) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType {
UIViewType()
}
func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<Self>) {
configuration(uiView)
}
}
Then use that view as you would use a TextView() where you assign its properties. This is effectively pulling over the UIKit version of a textView, so its properties will be the same. You can get fancy with it at this point and mix SwiftUI and UIKit properties and modifiers.
TextView { view in
view.text = sampleText
view.returnKeyType = .done
}
Although I have no evidence, I would think that since Apple defines a TextEditor as:
"A text editor view allows you to display and edit multiline, scrollable text in your app’s user interface",
The "Return" key is needed for line feeds, and may therefore not be able to be affected by .submitLabel
https://developer.apple.com/documentation/swiftui/texteditor
I've a ViewController with a NSButton linked to a IBAction let's say perform(). The button is key equivalent to the return key.
However the return key is also used in for a other temporary action. In an other file no related to previous ViewController, I have a NSTextfield which is editable, so the return key would validate the text changes.
I would like to validate my text without firing perform() function called from the key equivalent button.
For the moment the only solution I found is to send a notification textIsBeingEditing when my text field becomeFirstResponder and an other when the delegate function controlTextDidEndEditing.
Here is the selector of my notification:
#objc func trackNameIsBeingEditedNotification(_ notification: Notification) {
guard let value = notification.userInfo?["trackNameIsBeingEdited"]
as? Bool else {
return
}
if value {
backButton.keyEquivalent = ""
backButton.action = nil
} else {
myButton.keyEquivalent = "\r"
myButton.action = #selector(self.perform(_:))
// Here the `perform()` function is fired but I would avoid this behaviour…
}
}
Isn't any way to cancel the key event in order to prevent perform() to be fired just after I set myButton.action = #selector(self.perform(_:)) ?
I see a function called flushBufferedKeyEvents() but I totally doesn't know how to use it
Don't use notifications, use the delegate method optional func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool to validate the value of the text field.
After experimenting with a few little Swift programs, I decided my next step was to port a single module in an Objective-C program into Swift to see what steps were required. I had a number of issues, so I thought I'd post my process and results here in case others might find it useful.
I also created a table to help me remember the different conversions. Unfortunately, StackOverflow doesn't support tables, so I posted these conversions as a Github gist here.
Although Apple will undoubtedly provide an Xcode Refactor to convert from Objective-C to Swift, converting one manually is a great way to get familiar with the differences between the two languages. There is so much 'muscle memory' involved in a language you know well, and this is a great way to get familiar with the new syntax. As promised by Apple, it turns out the languages share so many common ideas, that it's mostly a mechanical process (as opposed to porting from, say C++ or even traditional C).
Note that this process uses none of the exciting new features of Swift, it only gets the code straight across. I should mention that moving to Swift will restrict any backwards compatability to iOS 7 or OS X 10.9. I also ran into a couple of issues (with workarounds below) that I'm sure are just due to the first beta release status of the project, so may not be required in future versions.
I chose iPhoneCoreDataRecipes and picked a module that didn’t rely on a lot of others: IngredientDetailViewController. If you'd like to follow along, check out my "answer" below.
Hope this is of use.
0) Download a copy of the project here and open Recipes.xcodeproj in Xcode version 6.
1) Choose File>New File…>iOS Source>Swift File> IngredientDetailViewController (Folder: Classes, Group: Recipe View Controllers)
2) Reply Yes to “Would you like to configure an Objective-C bridging header?”
3) Copy the first three lines below from Recipes_Prefix.pch and the next three from IngredientDetailViewController.m into Recipes-Bridging-Header.h. If you do further files, obviously don't duplicate lines, and remove any files that you've converted to Swift. I haven't found any where that documents the need for the Cocoa lines, given that they're imported in the swift file, but ...
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "Recipe.h"
#import "Ingredient.h"
#import "EditingTableViewCell.h"
4) Copy/paste the text from both the IngredientDetailViewController.h file and the IngredientDetailViewController.m files into IngredientDetailViewController.swift.
5) Delete both IngredientDetailViewController.h and .m files from project.
6) Do a global Find-and-Replace from #import "IngredientDetailViewController.h" to #import "Recipes-Swift.h" (Only one conversion in this case, and again for further files, don't duplicate this line in your Objective-C modules.)
7) Check the Project>Targets>Recipes>Build Settings Runpath Search Paths. If it shows $(inherited), remove this line or you'll get an error on launch about "no image found"
8) Convert Objective-C syntax in IngredientDetailViewController.swift to Swift. See the GitHub Gist mentioned above the substitutions required, or below for my converted version.
9) You may need to update the IB links. Do a Find>Find in Files on IngredientDetailViewController and select the one in Interface Builder. Open the Identity Inspector in the right-hand column. Select IngredientDetailViewController in the Class field, type xxx or something and tab.
10) Build and Run. Note that after going into a recipe, you must tap Edit and then the info button of an ingredient to activate IngredientDetailViewController
12) Congrats on building your first mixed Swift/Objective-C program!
Here's my cut at this particular module:
``
class IngredientDetailViewController: UITableViewController {
var recipe: Recipe!
var ingredient: Ingredient! {
willSet {
if let newIngredient = newValue {
self.ingredientStr = newIngredient.name
self.amountStr = newIngredient.amount
} else {
self.ingredientStr = ""
self.amountStr = ""
}
}
}
init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName:nibNameOrNil, bundle: nibBundleOrNil?)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
init(style: UITableViewStyle) {
super.init(style: style)
}
// MARK: table's data source
var ingredientStr: String?
var amountStr: String?
// view tags for each UITextField
let kIngredientFieldTag = 1
let kAmountFieldTag = 2
override func viewDidLoad () {
super.viewDidLoad()
self.title = "Ingredient"
self.tableView.allowsSelection = false
self.tableView.allowsSelectionDuringEditing = false
}
override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let IngredientsCellIdentifier = "IngredientsCell"
let cell = tableView.dequeueReusableCellWithIdentifier(IngredientsCellIdentifier, forIndexPath: indexPath ) as EditingTableViewCell
if (indexPath.row == 0) {
// cell ingredient name
cell.label.text = "Ingredient"
cell.textField.text = self.ingredientStr
cell.textField.placeholder = "Name"
cell.textField.tag = kIngredientFieldTag
}
else if (indexPath.row == 1) {
// cell ingredient amount
cell.label.text = "Amount"
cell.textField.text = self.amountStr
cell.textField.placeholder = "Amount"
cell.textField.tag = kAmountFieldTag
}
return cell
}
#IBAction func save (sender: AnyObject!) {
if let context = self.recipe.managedObjectContext {
if (!self.ingredient) {
self.ingredient = NSEntityDescription.insertNewObjectForEntityForName("Ingredient",
inManagedObjectContext:context) as Ingredient
self.recipe.addIngredientsObject(self.ingredient)
self.ingredient.displayOrder = self.recipe.ingredients.count
}
// update the ingredient from the values in the text fields
let cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow:0, inSection:0)) as EditingTableViewCell
self.ingredient.name = cell.textField.text
// save the managed object context
var error: NSError? = nil
if !context.save( &error) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate.
You should not use this function in a shipping application, although it may be
useful during development. If it is not possible to recover from the error, display
an alert panel that instructs the user to quit the application by pressing the Home button.
*/
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
}
// if there isn't an ingredient object, create and configure one
self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
}
#IBAction func cancel(sender: AnyObject!) {
self.parentViewController.dismissViewControllerAnimated(true, completion:nil)
}
func textFieldDidEndEditing(textField:UITextField) {
// editing has ended in one of our text fields, assign it's text to the right
// ivar based on the view tag
//
switch (textField.tag)
{
case kIngredientFieldTag:
self.ingredientStr = textField.text
case kAmountFieldTag:
self.amountStr = textField.text
default:
break
}
}
}
I'm subclassing NSButtonCell to customize the drawing (customizable theme). I'd like to customize the way checkboxes and radio buttons are drawn.
Does anyone know how to detect whether a button is a checkbox or radio button?
There is only -setButtonType:, no getter, and neither -showsStateBy nor -highlightsBy seem to give any unique return values for checkboxes that don't also apply to regular push buttons with images and alternate images.
So far I've found two (not very pretty) workarounds, but they're the kind of thing that'd probably get the app rejected from MAS:
Use [self valueForKey: #"buttonType"]. This works, but since the method is not in the headers, I presume this is something Apple wouldn't want me to do.
Override -setButtonType: and -initWithCoder: to keep track of the button type when it is set manually or from the XIB. Trouble here is the XIB case, because the keys used to save the button type to disk are undocumented. So again, I'd be using private API.
I'd really like this to be a straight drop-in replacement for NSButtonCell instead of forcing client code to use a separate ULIThemeSwitchButtonCell class for checkboxes and a third one for radio buttons.
A button does not know anything about its style.
From the documentation on NSButton
Note that there is no -buttonType method. The set method sets various button properties that together establish the behavior of the type. -
You could use tag: and setTag: (inherited by NSButton from NSControl) in order to mark the button either as a checkbox or a radio button. If you do that programatically then you should define the constant you use. You can also set the tag in Interface Builder, but only as an integer value (magic number).
In initWithCoder, here is my adaptation of the BGHUDButtonCell.m solution, updated for Mac OS Sierra:
-(id)initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder: aDecoder]) ) return nil;
NSImage *normalImage = [aDecoder decodeObjectForKey:#"NSNormalImage"];
if ( [normalImage isKindOfClass:[NSImage class]] )
{
DLog( #"buttonname %#", [normalImage name] );
if ( [[normalImage name] isEqualToString:#"NSSwitch"] )
bgButtonType = kBGButtonTypeSwitch;
else if ( [[normalImage name] isEqualToString:#"NSRadioButton"] )
bgButtonType = kBGButtonTypeRadio;
}
else
{
// Mac OS Sierra update (description has word "checkbox")
NSImage *img = [self image];
if ( img && [[img description] rangeOfString:#"checkbox"].length )
{
bgButtonType = kBGButtonTypeSwitch;
}
}
}
This is strange to me that it's missing from NSButton. I don't get it. That said, it's easy enough to extend NSButton to store the last set value:
import Cocoa
public class TypedButton: NSButton {
private var _buttonType: NSButton.ButtonType = .momentaryLight
public var buttonType: NSButton.ButtonType {
return _buttonType
}
override public func setButtonType(_ type: NSButton.ButtonType) {
super.setButtonType(type)
_buttonType = type
}
}
Swift 5.5
This is my approach. I use a standard naming convention in my app that relies on plain language identifiers. All my UI elements incorporate their respective property names and what type of UI element is associated with the property. It can make for some pretty long IBOutlet and IBAction names, but remembering tag numbers is way too complicated for me.
For example:
#IBOutlet weak var serveBeerCheckbox: NSButton!
#IBOutlet weak var headSize0RadioButton: NSButton!
#IBOutlet weak var headSize1RadioButton: NSButton!
#IBOutlet weak var headSize2RadioButton: NSButton!
\\ etc.
If there are UI properties that need to be stored, I name those without the type of UI element:
var serveBeer: Bool = true
var headSize: Int = 1
Bare bones example:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var serveBeerCheckbox: NSButton!
#IBOutlet weak var headSize0RadioButton: NSButton!
#IBOutlet weak var headSize1RadioButton: NSButton!
#IBOutlet weak var headSize2RadioButton: NSButton!
var serveBeer: Bool = true
var headSize: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonClicked(button: NSButton) {
guard let identifier = button.identifier else { return }
if identifier.rawValue.contains("Checkbox") {
switch button.identifier {
case serveBeerCheckbox.identifier:
// Do something with the Checkbox
serveBeer = (serveBeerCheckbox?.state == .on)
default:
// Another checkbox button
}
} else if identifier.rawValue.contains("RadioButton") {
switch button.identifier {
case headSize0RadioButton.identifier:
headSize = 0
case headSize1RadioButton.identifier:
headSize = 1
case headSize2RadioButton.identifier:
headSize = 2
default:
}
} // You could continue checking for different types of buttons
print("Serve beer? \(serveBeer ? "Sure!" : "Sorry, no.")")
if serveBeer {
switch headSize {
case 1:
print("With one inch of head.")
case 2:
print("With two inches of head!")
default:
print("Sorry, no head with your beer.")
}
}
}
}
As you can see, one could write a very generic method that can work on any type of UI element and use the rawValue of the identifier string with .contains() to isolate the type of element being worked with.
I have found using this approach allows me to initialize a UI with a lot of different elements pretty quickly and efficiently without having to recall tag numbers.
I have the code all done for my keydown actions, but i dont know what to do with the first responder that every site i go to seems to skim over. Can anyone tell me how to set it up to recognise keydown actions in cocoa objectivec?
Thanks
First, keyDown: is an event message, not an action message. Note that its argument is an NSEvent, not a UI object of some sort (such as an NSControl or NSMenuItem).
Action messages go down the responder chain, in which case the “first responder” is not special. Each responder will hand any action message it doesn't know how to handle off to its next responder. This is the “responder chain”. The first responder is simply whatever responder is at the head of the responder chain—i.e., is first. You would simply need to be in that chain, behind anything that doesn't know how to respond to the action being passed down it.
But since this is an event message, things are different. You need to be the key view, which is the first responder.
And that's all there is to it. You need to respond to the keyDown: message (and possibly related ones) in a view, and that view needs to be the first responder to receive the message.
The NSResponder class reference and Cocoa Event-Handling Guide will tell you more.
Here is what I've done and it works well. (Swift 3 for macOS Sierra)
override func viewDidLoad() {
keyIsDown = false // variable defined in the NSViewController
NSEvent.addLocalMonitorForEvents(matching: .keyUp) { (aEvent) -> NSEvent? in
self.keyUp(with: aEvent)
return aEvent
}
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { (aEvent) -> NSEvent? in
self.keyDown(with: aEvent)
return aEvent
}
}
Now I also override these:
override var acceptsFirstResponder: Bool { return true }
override func becomeFirstResponder() -> Bool { return true }
override func resignFirstResponder() -> Bool { return true }
And these:
override func keyUp(with event: NSEvent) {
keyIsDown = false
if event.keyCode == 1 {
print("s key released")
}
}
override func keyDown(with event: NSEvent) {
if keyIsDown == true {
return
}
keyIsDown = true
// Whatever you'd like to do (check to see which key released, etc.)
}
That should get you started.