Anyone know how to detect the "delete" key using UIKeyCommand on iOS 7?
As people were having problems with Swift, I figured a small, complete example in both Objective C and Swift might be a good answer.
Note that Swift doesn't have a \b escape character for backspace, so you need to use a simple Unicode scalar value escape sequence of \u{8}. This maps to the same old-school ASCII control character number 8 ("control-H", or ^H in caret notation) for backspace as \b does in Objective C.
Here's an Objective C view controller implementation that catches backspaces:
#import "ViewController.h"
#implementation ViewController
// The View Controller must be able to become a first responder to register
// key presses.
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (NSArray *)keyCommands {
return #[
[UIKeyCommand keyCommandWithInput:#"\b" modifierFlags:0 action:#selector(backspacePressed)]
];
}
- (void)backspacePressed {
NSLog(#"Backspace key was pressed");
}
#end
And here's the equivalent view controller in Swift:
import UIKit
class ViewController: UIViewController {
override var canBecomeFirstResponder: Bool {
return true;
}
override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(input: "\u{8}", modifierFlags: [], action: #selector(backspacePressed))
]
}
#objc func backspacePressed() {
NSLog("Backspace key was pressed")
}
}
Simple really - need to look for the backspace character "\b"
You can always try UIKeyInput. https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIKeyInput_Protocol/index.html#//apple_ref/occ/intfm/UIKeyInput/deleteBackward
The function should be
- (void)deleteBackward
Related
If a key command is registered, it's action might be called many times if the user holds down the key too long. This can create very weird effects, like ⌘N could repeatedly open a new view many times. Is there any easy way to stop this behavior without resorting to something like a boolean "already triggered" flag?
Here's how I register two different key commands:
#pragma mark - KeyCommands
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (NSArray<UIKeyCommand *>*)keyCommands {
return #[
[UIKeyCommand keyCommandWithInput:#"O" modifierFlags:UIKeyModifierCommand action:#selector(keyboardShowOtherView:) discoverabilityTitle:#"Show Other View"],
[UIKeyCommand keyCommandWithInput:#"S" modifierFlags:UIKeyModifierCommand action:#selector(keyboardPlaySound:) discoverabilityTitle:#"Play Sound"],
];
}
- (void)keyboardShowOtherView:(UIKeyCommand *)sender {
NSLog(#"keyboardShowOtherView");
[self performSegueWithIdentifier:#"showOtherView" sender:nil];
}
- (void)keyboardPlaySound:(UIKeyCommand *)sender {
NSLog(#"keyboardPlaySound");
[self playSound:sender];
}
#pragma mark - Actions
- (IBAction)playSound:(id)sender {
AudioServicesPlaySystemSound(1006); // Not allowed in the AppStore
}
A sample project can be downloaded here: TestKeyCommands.zip
In general, you don't need to deal with this, since the new view would usually become the firstReponder and that would stop the repeating. For the playSound case, the user would realize what is happening and take her finger off of the key.
That said, there are real cases where specific keys should never repeat. It would be nice if Apple provided a public API for that. As far as I can tell, they do not.
Given the '//Not allowed in the AppStore' comment in your code, it seems like you're OK using a private API. In that case, you could disable repeating for a keyCommand with:
UIKeyCommand *keyCommand = [UIKeyCommand ...];
[keyCommand setValue:#(NO) forKey:#"_repeatable"];
I reworked #Ely's answer a bit:
extension UIKeyCommand {
var nonRepeating: UIKeyCommand {
let repeatableConstant = "repeatable"
if self.responds(to: Selector(repeatableConstant)) {
self.setValue(false, forKey: repeatableConstant)
}
return self
}
}
Now you can have to write less code. If for example just override var keyCommands: [UIKeyCommand]? by returning a static list it can be used like this:
override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(...),
UIKeyCommand(...),
UIKeyCommand(...),
UIKeyCommand(...).nonRepeating,
UIKeyCommand(...).nonRepeating,
UIKeyCommand(...).nonRepeating,
]
}
This makes the first three command repeating (like increasing font size) and the last three ones non repeating (like sending an email).
Works with Swift 4, iOS 11.
This works in iOS 12, a little bit less 'private' compared to the accepted answer:
let command = UIKeyCommand(...)
let repeatableConstant = "repeatable"
if command.responds(to: Selector(repeatableConstant)) {
command.setValue(false, forKey: repeatableConstant)
}
I am new to Swift and maybe it's a stupid question, but I can't find an answer to it.
I have created an extension:
extension UITextField {
var placeholderLabel: UILabel {
get {
return self.placeholderLabel
}
set {
self.placeholderLabel = newValue
}
}
}
When the property is set, the application crashes.
You can't have a stored property in extension.
Extensions are not allowed to add a property to existing class because adding a property structure of the class will change. And because Objective C, Swift or any other programming language that am aware of could not afford it, it won't allow you to add the stored property to extension.
Isn't there any work around then ??
This is what you can do to save the label as stored property in your extension :)
import Foundation
import UIKit
fileprivate var ascociatedObjectPointer : UInt8 = 99
extension UITextField {
var myLabel : UILabel {
get {
return objc_getAssociatedObject(self, &ascociatedObjectPointer) as! UILabel
}
set {
objc_setAssociatedObject(self, &ascociatedObjectPointer, myLabel, .OBJC_ASSOCIATION_RETAIN)
}
}
}
How it works ??
Simple by writing setter and getter for the variable which you are posing or pretending to be stored property and by internally holding a pointer which has nothing to do with the existing class, hence it won't affect the structure of existing class.
Hope it helps.
You can use NSMapTable like this:
extension UITextField {
private static var placeholderLabelMap: NSMapTable<UITextField, UILabel> = .weakToStrongObjects()
var placeholderLabel: UILabel? {
get {
return UITextField.placeholderLabelMap.object(forKey: self)
}
set {
UITextField.placeholderLabelMap.setObject(newValue, forKey: self)
}
}
}
The advantage of Sandeep's answer might be thread safety. You can see this Stack Overflow topic for comparison between the approaches.
I have the following objective-c category:
#implementation UINavigationBar (Awesome)
static char overlayKey;
static char emptyImageKey;
- (UIView *)overlay
{
return objc_getAssociatedObject(self, &overlayKey);
}
- (void)setOverlay:(UIView *)overlay
{
objc_setAssociatedObject(self, &overlayKey, overlay, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
I tried to translate it into a Swift extension like this:
var AssociatedObjectHandle: UInt8 = 0
extension UINavigationBar {
var overlay:UIView! {
get {
return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! UIView
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
}
It looks strange because I created a global variable AssociatedObjectHandle and I am not sure what the Character actually does?
Is this the correct way to translate the computed property?
You asked: "Is this the correct way to translate the computed property?"
Ermm, no.
What you are doing is not translating a computed property. It looks like you're using associated objects to simulate a stored property in an extension.
If that is your intention (adding a stored property to your UINavigationBar extension) then the code you posted should work.
The AssociatedObjectHandle variable is some "magic" that makes associated value storage work. You have to pass in the address of some unique variable that tells the Objective-C runtime what key to use when getting/setting associated values for your class. You simply create a static variable and use it's address in the get/set associated value calls.
Right now I have created a basic prototype custom keyboard, however, I do not know how to make a shift key that would capitalize the letters. Right now the letters are all lowercase. Also I wrote this in Objective-C not Swift, but a swift based solution is welcome as well! :) Thank you
Make a key that toggles between whatever images you are using for it. When the key is toggled to shifted, store that in a boolean property.
Then, use this code for each key press:
- (NSString *)stringForAKeyPress:(id)sender {
if (self.shifted) {
return #"A";
} else {
return #"a";
}
}
I have an issue with converting character type to String type. First of all, I have below extension of String for finding nth character within String.
extension String {
func characterAtIndex(index: Int) -> Character? {
var cur = 0
for char in self {
if cur == index {
return char
}
cur++
}
return nil
}
}
I get what I want with this class extension. However when I use that nth character for title of my custom UIButton, gives an error. My Uibutton Class is
class hareketliHarfler: UIButton {
init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
}
func getLetter(letter:String!){
self.titleLabel.text = letter
}
}
The error show when i try to access "getLetter(letter:String)" function. Here is example of main view Controller codes:
var harfim = hareketliHarfler(frame: CGRectMake(100,100,100,100))
var str="This is my String"
var bufi=str.characterAtIndex(3)
harfim.getLetter(bufi as AnyObject) ****
In * section I try .getLetter(bufi), .getLetter(bufi as String) also I try to change parameter type of function. Look like: func getLetter(letter:Character!) or func getLetter(letter:AnyObject!)...etc
Didn't find a way. Need a help on that. Thank you
How about the simple
String(theCharacter)
Works in Swift 4 and Swift 5
Your problem is quite simple: your characterAtIndex function returns a Character, and self.titleLabel.text is a String. You can't convert between the two implicitly. The easiest way would be to turn the Character into a String using the String initialiser:
// ch will be Character? type.
if let ch = str.characterAtIndex(3) {
// Initialise a new String containing the single character 'ch'
harfim.getLetter(String(ch))
} else {
// str didn't have a third character.
}
Unlike other solutions, this is safe for unusual Unicode characters, and won't initialise a potentially large array or iterate the whole String just to get the third character.
Change this:
var bufi=str.characterAtIndex(3)
harfim.getLetter(bufi as AnyObject)
to this:
harfim.getLetter(String(Array(str)[3]))
So what happening here:
we create an array from our string. Array elements are symbols from original string. Such break down correctly tracks symbols that are presented with a sequences of two or more code points. E.g. emoji or flag as noted by #MartinR.
We access element at 4-th position.
Note that as we crate an array from initial string then performance wise is better to use this method only with short strings and avoid it in oft-repeated routines. But in your case it seems to be OK.
Can also use Character(text).isNumber if you want to get localised numbers.
Reference:
https://developer.apple.com/documentation/swift/character/3127015-isnumber