Reputation: 16522
I created a subclass of a UICollectionViewController that is used as the custom inputAccessoryViewController
in a UITextView
.
https://developer.apple.com/reference/uikit/uiresponder/1621124-inputaccessoryviewcontroller
I want to play the keyboard click sound when you tap a cell in the collection view using playInputClick. https://developer.apple.com/reference/uikit/uidevice/1620050-playinputclick
I cannot figure out how to get this to work in a collection view. It works for a simple view like this using the inputAccessoryView
property of a UITextView
but I'm not sure what view to subclass in the collection view controller hierarchy to get the keyboard click sound to play.
@interface KeyboardClickView : UIView <UIInputViewAudioFeedback>
@end
@implementation KeyboardClickView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[self addGestureRecognizer:tap];
}
return self;
}
- (void)tap:(id)sender
{
[[UIDevice currentDevice] playInputClick];
}
- (BOOL)enableInputClicksWhenVisible
{
return YES;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_inputAccessoryView = [[KeyboardClickView alloc] initWithFrame:CGRectMake(0, 0, 0, 50)];
_inputAccessoryView.backgroundColor = [UIColor redColor];
[[UITextView appearance] setInputAccessoryView:_inputAccessoryView];
// ...
}
@end
I'm also aware that you can play the keyboard click sound using AudioServicesPlaySystemSound(1104)
but this doesn't respect the user's settings if they have the keyboard click sounds disabled.
Upvotes: 7
Views: 900
Reputation: 2037
To use the benefits of playInputClick
in UIViewController
:
Dummy input accessory view:
@interface Clicker : UIView <UIInputViewAudioFeedback>
@end
@implementation Clicker
- (BOOL)enableInputClicksWhenVisible
{
return YES;
}
@end
View with input accessory view:
@interface ControllerView : UIView
@end
@implementation ControllerView
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (UIView *)inputAccessoryView
{
return [[Clicker alloc] init];
}
@end
View Controller with custom view:
@implementation ViewController
- (void)loadView
{
self.view = [[ControllerView alloc] init];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.view becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.view resignFirstResponder];
}
@end
When the responder object becomes the first responder and inputView (or inputAccessoryView) is not nil, UIKit animates the input view into place below the parent view (or attaches the input accessory view to the top of the input view).
There is no visual consequences since the height of Clicker
view is zero, and conforming to UIInputViewAudioFeedback
protocol enables [[UIDevice currentDevice] playInputClick]
functionality within ViewController
.
Look here for responder chains and here for input accessory views.
Upvotes: 4
Reputation: 2966
Here's a working Swift 4.2 (iOS 11 and 12) version of bteapot's answer.
private class ClickerDummyView: UIView, UIInputViewAudioFeedback {
var enableInputClicksWhenVisible: Bool { return true }
}
private class ClickerControllerView: UIView {
override var canBecomeFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
return ClickerDummyView()
}
}
class ClickingViewController: UIViewController {
override func loadView() {
self.view = ClickerControllerView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
view.becomeFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.resignFirstResponder()
}
}
Now call UIDevice.current.playInputClick()
from within the ClickingViewController
class whenever needed and the keyboard click sound will be triggered (with respect to system settings).
Upvotes: 0
Reputation: 6701
Instead of using a UICollectionViewController
, define KeyboardClickView
as a subclass of UICollectionView
and place it on a UIViewController
.
@interface KeyboardClickView : UICollectionView <UIInputViewAudioFeedback>
- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout {
self = [super initWithFrame:frame collectionViewLayout:layout];
// existing implementation
The new view controller could look something like this:
@interface KeyboardClickViewController: UIViewController <UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, strong) KeyboardClickView *clickView;
@end
@implementation KeyboardClickViewController
-(void)viewDidLoad {
[super viewDidLoad];
self.clickView = [[KeyboardClickView alloc] initWithFrame:self.view.bounds collectionViewLayout:[UICollectionViewFlowLayout new]];
self.clickView.delegate = self;
self.clickView.dataSource = self;
[self.view addSubview:self.clickView];
}
// existing UICollectionViewController logic
@end
This allows you to make the call to playInputClick
from a UIView
instead of a UIViewController
.
Upvotes: 1