I'm working on iOS 8 custom keyboard extension right now, and there are some issues that I cannot figure out.
First, I think the UITextInputDelegate Methods are not working as I expected.
Does this sound right: selectionWillChange: and selectionDidChange: methods should be called when user long-presses typing area? And textWillChange: and textDidChange: methods should be called whenever the text is literally changing?
Actually, what I observed is that, when I changed selection in text input area, textWillChange: and textDidChange: are called, and I cannot get a clue that the other two methods are called in what condition. If anyone knows about the usage of these delegate methods, please let me know.
Second, I know the playInputClick: method can be used to virtualize keyboard click sound in custom keyboard. As this is applicable in a normal situation, I found it impossible to apply in iOS 8 custom keyboard extension. My app consists of one keyboard view controller, and custom view that subclasses UIView is added to this view controller. My approach is that UIInputViewAudioFeedback delegate is declared in this custom view, enableInputClicksWhenVisible method is returning YES, class method that calls [[UIDevice currentDevice] playInputClick] is set, then this method is called wherever the keyboard sound is needed: which is not working at all.
Is my approach is wrong in any way? If anyone has succeeded in using playInputClick method, please share your wisdom.
Thank you
It is best to play the Audio on a queue rather than in the UI key handler
func playPressKeySound() {
let PRESS_KEY_DEFAULT_SOUND_ID: SystemSoundID = 1104
dispatch_async(dispatch_get_main_queue()) {
AudioServicesPlaySystemSound(PRESS_KEY_DEFAULT_SOUND_ID)
}
}
NOTE: this only works if the keyboard has Full Access switched on, so best test there is full access before calling, otherwise the keyboard can suffer long pauses.
For the second question, try AudioServicesPlaySystemSound
#define PRESS_KEY_DEFAULT_SOUND_ID 1104
- (void)playPressKeySound {
if (self.openPressSound) {
AudioServicesPlaySystemSound(PRESS_KEY_DEFAULT_SOUND_ID);
}
}
Related
I have a simple app in Swift with just a few views:
A UIWebView
some TableViews
and another view with some data I download from my server
It all works well until when using the app I press the home button, leave there for a while then the iPad goes on sleep mode. A few days later I tap on the app icon and it won't start:
first tap on the icon will select the icon (goes a little darker) and deselect it a few seconds later
second tap will launch the LaunchScreen and crash a few seconds later
double tap the home button and quit the app will sometimes work
I'm just wondering if there is something I need to set on my code to handle idle/long periods of inactivity in something like viewWillDisappear or other methods?
If so I already have this in all my controllers:
override func viewWillDisappear(animated: Bool) {
timer.invalidate()
webView.removeFromSuperview()
}
Maybe I need to call super. in there too? or something else I'm missing?
You should definitely call super in your viewWillDisappear(animated:) method. See UIViewController Class Reference documentation. Also you might want to confirm why you are removing your webView from the view controller's hierarchy.
Discussion
This method is called in response to a view being removed
from a view hierarchy. This method is called before the view is
actually removed and before any animations are configured.
Subclasses can override this method and use it to commit editing
changes, resign the first responder status of the view, or perform
other relevant tasks. For example, you might use this method to revert
changes to the orientation or style of the status bar that were made
in the viewDidDisappear: method when the view was first presented. If
you override this method, you must call super at some point in your
implementation.
You probably have some null pointer exception and crash. Maybe you are calling some variable that is not set (and checked if not null).
Try disabling app funcionality (like downloading, storing and using data from server) and see where you app starts working normal again and then procede from there.
Sorry for vague answer but withouth code and maybe some log it is really hard to give specific answer.
And NO, you dont have to do anything special to handle idle/long periods of inactivity.
I am new to iOS /swift programming and I am working on an app developed by someone else, fixing some bugs.
The app is essentially a music player and the music has to be played also in background, giving the possibility to play/pause/skip from the lock screen. The app has several views, one of them, the main one, contains all the code related to the player itself (player.swift), the other ones containing other additional pages/features.
The commands from the lock screen works only when I lock the screen starting from the main view, if I do it starting from another view (e.g. the help view, which is just a page which is displayed over the player when the help link is tapped) they don't work. Reading several articles here I've realized that the reason is that the related code is in player.swift:
override func remoteControlReceivedWithEvent(event: UIEvent) {
if (event.type == UIEventType.RemoteControl) {
switch (event.subtype) {
case UIEventSubtype.RemoteControlPlay:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlPause:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlTogglePlayPause:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlNextTrack:
onNext(nil)
default:
break
}
}
}
so I have understood the problem, but even if I've read several related articles (including remoteControlReceivedWithEvent called on iOS 7.0 device but not iOS 8.0, Using lock screen for my app?, Swift. Receive remote control events to work with MPNowPLayingInfoCenter) I can't figure out where do I need to move this code and if I need to move something else or make modifications.
EDIT. I moved the code in AppDelegate.swift (deleting the code in player.swift) , as suggested. It seems it now intercepts commands even if I lock the device from a view different than player.swift. I have two problems, though:
1) It seems it works just once, If I click on "next" from the lock screen I can see from a debug string that the command is intercepted, If I do it a second time nothing happens
2) I need to call the methods (onPlayPause and onNext) in player.swift from AppDelegate.swift, I guess those methods expect to have a player object set and/or they refer to variables declared in player.swift and I don't know how to handle this. For example the onNext method is declared as
#IBAction func onNext(sender: AnyObject?) {
oldImage = iAlbumArt.image
.......
and if I call the method as a new instance from AppDelegate
player().onNext(nil)
I get an error because iAlbumArt.image is NIL. iAlbumArt is a variable declared in the Player class as
#IBOutlet weak var iAlbumArt: UIImageView!
Sorry for the naive questions but I've been looking into iOS development just since a couple of weeks ago.
Try adding it to your App Delegate class.
Edit:
To forward remote control events to the view controller, add this code to the app delegate (assuming your player view controller is called PlayerViewController):
let vcs = (self.window!.rootViewController as! UINavigationController).viewControllers
let indexOfPlayer = (vcs as! NSArray).indexOfObjectPassingTest { (vc, idx, stop) in
return (vc.isKindOfClass(PlayerViewController))
}
let playerVC = vcs[indexOfPlayer];
Edit 2:
Place the override func remoteControlReceivedWithEvent method in your App Delegate Class.
At the top of that method, place the code snippet shown above.
In that method, replace self with playerVC.
In your player view controller, add code to respond to onPlayPause and onNext functions.
Note:
The reason why this code:
player().onNext(nil)
was throwing an error was because player() creates a brand-new instance of your player class. You want to use the existing instance so the changes get reflected on the screen.
I'm developing an application that has an Keyboard extension, At some uses of the Keyboard extension (for example: when I use the switch keyboard button) the extension is going through the viewDidDisappear and at this case I know I can clean the keyboard and it's getting deallocated with all it's views and the memory is freed. At other uses (for example: when using the Notes application and pressing the Done/Back button) the extension is going through the viewWillDisappear but not through the viewDidDisappear, So in this case I'm not cleaning the Keyboard as it's not reaching my cleaning methods and therefore it's not getting deallocated.
At this point I would expect that when I return to the keyboard by going back to the note for example I would receive the previous UIInputViewController with the view I already built.
Unfortunately at this point the isViewLoaded() method returns false and my UIInputViewController is going again through the loadView method therefore building again all the views all over again, as a result the application memory grows until at some point I reach the nasty didReceiveMemoryWarning method callback.
I would like to restore somehow the view I created for my keyboard in the UIInputViewController that was initialized first. Does some one know is it possible and how could this be achieved?
I was looking into the restorationIdentifier to accomplish this task, correct me if I'm wrong but this is used with an ordinary UIViewController and can't be used with the UIInputViewController as not my AppDelegate is responsible to launch it.
Update
From further research I made, I came to a conclusion that no matter what the operation you commit:
Use Done/Back button in the Notes application.
Switch to the Messages app and use the keyboard there.
Use the “switch keyboard" button
And other scenarios, the UIInputViewController class always goes through the init method.
From my understanding of the object oriented programming this mean that whoever calls my keyboard to reaper does it using the constructor and basically creates a new instance of the keyboard. That means that I will never have an available view on first reappearance of the keyboard in any of the behaviors and I always have to build the keyboard view for the new instance and cleanup the old one. Is this right? or there is a way to presereve the view of the UIInputViewController?
Unless you're using your own custom application with the keyboard to store its previous instance, there's no way to do this. In everything besides a custom application designed by you, it is beyond your scope
I read a lot of documentations and topics about playing keyboard clicks, but I can't get it to work in my app...
I designed a custom keyboard (as a UIView subview), which adopts the UIInputViewAudioFeedback protocol, and returns YES for the enableInputClicksWhenVisible method.
But when I call [[UIDevice currentDevice] playInputClick], nothing happens (whereas I can hear the click when I use the default keyboards).
In the Apple UIKit framework reference, it's written that it should work "only if the input view is itself enabled and visible". My view is obviously visible, and even if I change the superclass from UIView to UIControl, and set self.enabled = YES, it doesn't work either...
I saw a lot of solutions like AudioServicesPlaySystemSound(1104); but I didn't even tried because I don't want to play clicks if the user disabled the keyboard clicks in settings.
Why don't the "official" solution work? It doesn't seem to be deprecated in iOS 8...
Thanks a lot
Thomas
Did you adopt the UIInputViewAudioFeedback protocol and override enableInputClicksWhenVisible?
To enable a custom input or accessory view for input clicks,
perform the following two steps:
1) Adopt the UIInputViewAudioFeedback protocol in your input view class.
2) Implement the enableInputClicksWhenVisible delegate method to return
true.
https://developer.apple.com/documentation/uikit/uidevice/1620050-playinputclick
I am building an interface, where I can add events like in a calendar.
In the AddAEventViewController I have Buttons to set the starttime, duration and recurrence.
Every time you press a button a viewcontroller comes up with a UIDatePicker, where you can set your time. The picked component is than displayed in a UITextField. Now when I press the Done-Button, it dismisses the ModalViewController and I am back to my AddAEventViewController. Next to the Durationbutton e.g. is a UILabel, where I want to show now the just picked and in the textfield shown duration.
How do I get access to the AddEventViewController out of an other ViewController? I tried to alloc and init a new one there, but it didnt work!
- (IBAction)pressedDoneButton:(id)sender {
_mainAddWishViewController.labelDuration.text=textFieldDuration.text;
[self dismissModalViewControllerAnimated:YES];
}
Can someone help me please!
Thank you Jules
There are several ways you can do this, all of them documented here. Reading and understanding them will help you a lot in iOS software development.
There are many ways to achieve this. Here is one that is fairly straightforward.
In the "child" viewController, add a delegate property and set it to the parent view controller.
Then in your Done button handler, do something like:
[self.delegate performSelector:#selector(didComplete) withObject:self]
In the parent view controller, define a method as follows:
- (void) didComplete: (YourSubViewControllerClass *) sender
{
self.labelDuration.text = sender.textFieldDuration.text
}
Basically, this implements an informal protocol whereby the subViewController informs the main view controller that it is finished and input values are available.
Note that if you cancel out of the subViewController, don't send the didComplete message.