I have multiple keyboards in my Xcode iOS Simulator. I'm running Swift (I need the code to work in Swift), and when they keyboard input type is changed in my app (e.g. from English to Spanish), I want to know which language it has been changed to.
I have a function in my ViewDidLoad:
NotificationCenter.default.addObserver(self,
selector: #selector(changeInputMode(_:)),
name: UITextInputMode.currentInputModeDidChangeNotification, object: nil)
and this function that supports it.
#objc func changeInputMode(_ notification: Notification)
{
let inputMethod = UITextInputMode.activeInputModes.description
print("keyboard changed to \(inputMethod.description)")
}
The inputMethod description prints out this for me:
[<UIKeyboardInputMode: 0x6000027a28a0>, <UIKeyboardInputMode: 0x6000027ab2f0>, <UIKeyboardInputMode: 0x6000027bc9b0>, <UIKeyboardInputMode: 0x6000027bca50>, <UIKeyboardInputMode: 0x6000027b8ff0>, <UIKeyboardInputMode: 0x6000027b9090>]
I would like to get the language in simpler terms, such as "english" or "en" or something like that. How can this be achieved?
I also tried the following:
let language = UITextInputMode.currentInputMode()?.primaryLanguage
print(language)
but I got an error:
'currentInputMode()' is unavailable in iOS: APIs deprecated as of iOS 7 and earlier are unavailable in Swift
Thanks for your help in advance.
Related
I've used INIntent object since Siri shortcut is added. For that I made an intent definition and it generated a INIntent object automatically.
#available(iOS 12.0, watchOS 5.0, *)
#objc(SpotConditionIntent)
public class SpotConditionIntent: INIntent {
#NSManaged public var spotId: String?
#NSManaged public var spotName: String?
}
To customize the Siri shortcut voice record screen, I added a convenience initializer. It was basically to add suggestedInvocationPhrase and the top icon image.
#available(iOS 12, *)
extension SpotConditionIntent {
convenience init(spotId: String, spotName: String) {
self.init()
self.spotId = spotId
self.spotName = spotName
self.suggestedInvocationPhrase = "\(NSLocalizedString("how_are_waves_text", comment: "How are the waves at")) \(spotName)?"
if let uiimage = UIImage(named: "check-surf-icon"), let data = uiimage.pngData() {
let inImage = INImage(imageData: data)
setImage(inImage, forParameterNamed: \.spotName)
}
}
}
Today, I tried to convert the entire project to Swift 5 and there was no issue on building. (There was no actual change in the code.) However it crashes on runtime with very weird message.
Thread 1: Fatal error: could not detangle key path type from XXXX9SpotConditionIntentCXD
and it pointed the setImage(inImage, forParameterNamed: \.spotName).
I just found that the setImage(,forParameterNamed) doesn't exist in the documentation.
https://developer.apple.com/documentation/sirikit/inintent
Seems like I need to use func keyImage() -> INImage? which is added in iOS 12.
But I have no idea why it works in Swift 4.X and can't find any documentation for the deprecation. Anyone knows about this issue?
As far as I checked...
The two methods are available in Objective-C.
- imageForParameterNamed:
- setImage:forParameterNamed:
And the generated interface for these methods shown as...
// Set an image associated with a parameter on the receiver. This image will be used in display of the receiver throughout the system.
#available(iOS 12.0, *)
open func __setImage(_ image: INImage?, forParameterNamed parameterName: String)
#available(iOS 12.0, *)
open func __image(forParameterNamed parameterName: String) -> INImage?
The docs or code suggestion of Xcode will not show you these, but you can use them in Swift 5 code:
intent.__setImage(image, forParameterNamed: "spotName")
Though, using underscore-leaded methods may be taken as using private APIs when submitting your app to App Store.
This sort of things sometimes happens when Apple is swiftifying some methods and not completed yet.
As for now, what you can do are...
Send a bug report to Apple, immediately
Write an Objective-C wrapper for these methods and import it into Swift
or
Delay submitting your up until new SDK with this issue fixed will be provided
(Can be years...)
I am going to use these language changes as triggers in the code that I'm going to write.
For example:
if languageHasChanged() {
//do something
}
Register for the NSLocale.currentLocaleDidChangeNotification notification.
NotificationCenter.default.addObserver(self, selector: #selector(localeChanged), name: NSLocale.currentLocaleDidChangeNotification, object: nil)
func localeChanged() {
}
If NSLocale.currentLocaleDidChangeNotification is not available, you can store the actual Locale in applicationWillTerminate and applicationWillEnterBackground in a variable, and compare it to the locale in applicationDidBecomeActive.
I am using Here Maps SDK in my project in Swift 3 (in Xcode 8 for iOS 10) to grab the currentPosition from NMAPositioningManager. However, while isActive is true for the NMAPositioningManager, the currentPosition is always nil. I have a valid position and valid credentials H(ERE_MAPS_APPID, HERE_MAPS_APP_CODE, and HERE_MAPS_LICENSE_KEY).
I checked the SDKDemo with Xcode 8 for iOS 10, which runs fine, but their code is in Objective-C. Any comment would be appreciated.
if let posMan : NMAPositioningManager = NMAPositioningManager.shared() {
if let currPos = posMan.currentPosition{
if let geoCoordCenter = currPos.coordinates {
self.mapView.setGeoCenter(geoCoordCenter, with: NMAMapAnimation.none)
}
}
}
Make sure positioning is started (I guess you did, but it's not shown in your snipped, so pasting it here again for reference):
NMAPositioningManager.shared().startPositioning()
Then normally it takes some time till your position is available, so you can't query dirctly PositionManager. I suggest to register for the position update callbacks:
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.positionDidUpdate), name: NSNotification.Name.NMAPositioningManagerDidUpdatePosition, object: NMAPositioningManager.shared())
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.didLosePosition), name: NSNotification.Name.NMAPositioningManagerDidLosePosition, object: NMAPositioningManager.shared())
Then do in the callback with the position whatever you want:
func positionDidUpdate(){
if let p = NMAPositioningManager.shared().currentPosition {
mapView.setGeoCenter(p.coordinates, with: NMAMapAnimation.bow)
}
}
func didLosePosition(){
print("Position lost")
}
Let me know if it's still not working for you like this, and I'll add more complete code.
I thought I have tried everything and read every possible articles in this forum. But nothing seems to work. Here is some code snippet and some settings:
On Extension side:
let thisDefaults = NSUserDefaults(suiteName: "group.request.com")
thisDefaults?.setObject("Hello", forKey: "prevWord")
thisDefaults?.setObject("World", forKey: "word")
let success = thisDefaults?.Synchronize()
NSNotificationCenter.defaultCenter().postNotificationName("ExtensionRequestChanges", object: nil)
On Containingg app side:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("DidReceiveRequest:", name: "ExtensionRequestChanges",
object: nil)
func DidReceiveRequest(notification: NSNotification) {
// Read defaults
let thisDefaults = NSUserDefaults(suiteName: "group.request.com")
let word = thisDefaults?.stringForKey("word")
let prevWord = thisDefaults?.stringForKey("prevWord")
...
}
On the project settings:
. registered "group.request.com" app group to both the extension and containing app
. set "RequestOpenAccess" in NSExtensionAttributes to YES
But my DidReceiveRequest function never seemed to get called! Why???
Please help.
NSNotificationCenter only works in the process it's running in. Your keyboard extension and container app are separate processes. To post interprocess notifications, you should investigate the CFNotificationCenterGetDarwinNotifyCenterAPI call, which returns a Core Foundation notification center that can post notifications between processes, or other forms of interprocess communication.
I already found a lot of approaches for this but no working solution. Here's what I tried and didn't work.
(1) Simply calling primaryLanguage()
UITextInputMode().primaryLanguage
→ always returns nil :-/
(2) Subscribing to UITextInputCurrentInputModeDidChangeNotification notifications
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "changeInputMode:", name: UITextInputCurrentInputModeDidChangeNotification, object: nil)
}
func changeInputMode(sender : NSNotification) {
...?!
}
The notification is getting triggered but it is unclear how I can extract the current language information from the notification.
(3) Using activeInputModes()
let localeIdentifier = UITextInputMode.activeInputModes().first as? UITextInputMode
var locale:NSLocale = NSLocale(localeIdentifier: localeIdentifier.primaryLanguage!)
println("Input Mode Language \(localeIdentifier.primaryLanguage!)")
This always provides the same array of all available keyboards, no information on the actually active one.
How do I get the NSLocale of the currently active keyboard?
You can access the primaryLanguage from every textfield by accessing the textfields textInputMode like that:
var language = textfield.textInputMode?.primaryLanguage