Prevent or detect events passed on from iOS 8 keyboard - ios

In our iOS 8 app, the search screen, which is similar to the search screen of the App Store app, no longer works reliably. When a user taps a key, the keyboard is sometimes closed or even an action executed.
Part of the reason is that the tap event is passed on to lower layers, which is close the keyboard (smoke screen), navigate to a search result (UITableView with search result) or execute the search (UITableView with search term suggestions).
For some unknown reason, it properly works as long as the user stays in the app. However, if he/she goes to a different app and then returns, the events are passed on. This behavior affects all iOS 8 version (8.0.x, 8.1).
How can we prevent the keyboard from passing on tap events or how can we detect such an event (e.g. from tableView:didSelectRowAtIndexPath:)?
The question "Keyboard intermittently disappears when editing using IOS 8" seems to refer to the same problem though I can't figure out how to apply that ugly hack to my situation.
I've just found a similar post in Apple's developer forum. Unfortunately, it has no answers and has been archived in the mean time:
I have overriden -hitTest:withEvent: on a view on my view hierarchy
where I check if it was touched and will forward the touch to its
subviews and fire a selector to dismiss the keyboard.
On iOS 7 (and, more strangely, when the app is launched on iOS 8) this
works perfectly and -hitTest:withEvent: will never be called if the
view is behind the keyboard and the user taps on the keyboard.
But on iOS 8, if the user sends the app to the background and brings
it back to the foreground, tapping anything on the keyboard will
trigger -hitTest:withEvent: as if the view was above the keyboard on
the view hierarchy. I've used Reveal.app to verify that it is not
above the keyboard, it is behind as expected.
Anyone got any ideas of what could be happening? I've created a sample
project and attached it to a radar for Apple as this looks like a bug
on iOS 8 for not working the same way consistently.
Update
My search screen contains two views (on top of each other): a background view visible when no search results are available and a table view visible if search results are available. On top of these, I dynamically add two additional views if the search bar becomes active: a smoke class view that can be tapped to end the search text entry and a table view that displays search text suggestions. All four views are directly contained in the view controller's main view and cover the full area.
The interesting thing now is that the keyboard forwards event the two dynamically added views but not to the two lower views that are always there.

I believe it's a bug that tap events are passed on to views underneath the keyboard. As I've figured out in the mean time that only the dynamically added views are affected and not the ones that are part of the Interface Builder file, I've now come up with a work around: if the keyboard appears, I shrink these two view so they do not extend underneath the keyboard. And I grow them again when the keyboard disappears.
So this is the code. The upper part up to (and excluding) [[NSNotificationCenter defaultCenter] has existed before. The rest is the workaround.
- (BOOL) searchBarShouldBeginEditing: (UISearchBar*) searchBar {
// calculate from for entire area
CGRect frame = ... // omitted
// add smoke screen
_smokeScreen = [UIButton buttonWithType: UIButtonTypeCustom];
_smokeScreen.frame = frame;
_smokeScreen.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_smokeScreen.backgroundColor = [UIColor colorWithWhite: 0.0f alpha: 0.5f];
[_smokeScreen addTarget: self action: #selector(smokeScreenPressed:) forControlEvents: UIControlEventTouchDown];
[self.view addSubview: _smokeScreen];
// add table view for search term suggestions
_suggestionTableView = [[SearchControllerTableView alloc] initWithFrame:frame style:UITableViewStylePlain];
_suggestionTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_suggestionTableView.dataSource = self;
_suggestionTableView.delegate = self;
_suggestionTableView.searchController = self;
_suggestionTableView.hidden = YES;
[self.view addSubview:_suggestionTableView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil];
return YES;
}
- (void) keyboardDidShow:(NSNotification *) notification
{
// shrink the smoke screen area and the table view because otherwise they'll receive tap events from the keyboard
CGRect screenRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect windowRect = [self.view.window convertRect:screenRect fromWindow:nil];
CGRect viewRect = [self.view convertRect:windowRect fromView:nil];
[self setBottom: viewRect.origin.y];
}
- (void) keyboardWillHide:(NSNotification *) notification
{
// grow the views again
[self setBottom: self.view.frame.size.height];
}
- (void) setBottom: (CGFloat)y
{
CGRect frame = _suggestionTableView.frame;
frame.size.height = y - frame.origin.y;
_suggestionTableView.frame = frame;
frame = _smokeScreen.frame;
frame.size.height = y - frame.origin.y;
_smokeScreen.frame = frame;
}

You can try to play with view property userInteractionEnabled or
exclusiveTouch when search view called.

I come across this problem with Xamarin.iOS, this is the solution for Xamarin.iOS use C#.
In WillMoveToSuperview method, observe keyboard show/hide event and store the keyboard frame in variable KeyboardEndFrame:
if (newsuper != null)
{
var wrThis = new WeakReference<XInterceptTouchView> (this);
// add notification observe
keyboardDidShow = UIKeyboard.Notifications.ObserveDidShow ((sender, e) =>
{
XInterceptTouchView interceptTouchView;
if (wrThis.TryGetTarget (out interceptTouchView))
{
interceptTouchView.KeyboardEndFrame = e.FrameEnd;
}
});
keyboardDidHide = UIKeyboard.Notifications.ObserveDidHide ((sender, e) =>
{
XInterceptTouchView interceptTouchView;
if (wrThis.TryGetTarget (out interceptTouchView))
{
interceptTouchView.KeyboardEndFrame = e.FrameEnd;
}
});
}
else
{
// remove notification observe
keyboardDidShow?.Dispose ();
keyboardDidHide?.Dispose ();
}
In HitTest method, get the keyboard window and process touch event with the window:
if (KeyboardEndFrame.Contains (point))
{
IntPtr handle = ObjCRuntime.Class.GetHandle ("UITextEffectsWindow");
if (handle != IntPtr.Zero)
{
var keyboardWindow = UIApplication.SharedApplication.Windows.FirstOrDefault (w => w.IsKindOfClass (new ObjCRuntime.Class ("UITextEffectsWindow")));
if (keyboardWindow != null)
return keyboardWindow.HitTest (point, uievent);
}
}
var hitTestView = base.HitTest (point, uievent);
this.EndEditing (true);
return hitTestView;

I found that keyboard touches trigger hitTest if I show the keyboard, send the application to the background, and come back. This shouldn’t happen so it looks like an iOS bug. I see two solutions:
#1 Send the touch away
Solution #1: hitTest shouldn’t be called for touches on the keyboard, so check against the keyboard frame and send the touch away.
var keyboardFrame: CGRect?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
{
if let keyboardFrame = keyboardFrame {
if keyboardFrame.contains(point){
return super.hitTest(CGPoint(x:-1,y:-1), with: event)
}
}
return super.hitTest(point, with: event)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func subscribeKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(MyView.keyboardDidShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(MyView.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardDidShow(_ notification: NSNotification) {
keyboardFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
}
func keyboardWillHide(_ notification: NSNotification) {
keyboardFrame = nil
}
#2 Pull the keyboard before resigning
Solution 2#: Pull the keyboard before resigning active. Two flaws: you are touching the delegate to fix something elsewhere, and the user is surprised to find the keyboard down when the app comes back.
func applicationWillResignActive(_ application: UIApplication) {
UIApplication.shared.sendAction(#selector(self.resignFirstResponder), to: nil, from: nil, for: nil)
}
Or if you want this done for a particular view controller only:
if let controller = UIApplication.shared.keyWindow?.topmostViewController {
if controller is MyViewController {
NSLog("Pulling the keyboard to prevent touches from falling through after coming back from the background.")
UIApplication.shared.sendAction(#selector(self.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
where topmostViewController is:
extension UIViewController {
static var topmostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.topmostViewController
}
var topmostViewController: UIViewController? {
return presentedViewController?.topmostViewController ?? self
}
}
extension UINavigationController {
override var topmostViewController: UIViewController? {
return visibleViewController?.topmostViewController
}
}
extension UITabBarController {
override var topmostViewController: UIViewController? {
return selectedViewController?.topmostViewController
}
}
extension UIWindow {
var topmostViewController: UIViewController? {
return rootViewController?.topmostViewController
}
}
Leonardo Cardoso wrote the extensions above.

Related

In Swift, how can I detect a tap on the status bar? [duplicate]

I have custom view in my application which can be scrolled by the user. This view, however, does not inherit from UIScrollView. Now I want the user to be able to scroll this view to the top, just as any other scrollable view allows. I figured that there is no direct way to do so.
Google turned up one solution: http://cocoawithlove.com/2009/05/intercepting-status-bar-touches-on.html This no longer works on iOS 4.x. That's a no-go.
I had the idea of creating a scrollview and keeping it around somewhere, just to catch it's notifications and then forward them to my control. This is not a nice way to solve my problem, so I am looking for "cleaner" solutions. I like the general approach of the aforementioned link to subclass UIApplication. But what API can give me reliable info?
Are there any thoughts, help, etc...?
Edit: Another thing I don't like about my current solution is that it only works as long as the current view does not have any scroll views. The scroll-to-top gesture works only if exactly one scroll view is around. As soon as the dummy is added (see my answer below for details) to a view with another scrollview, the gesture is completely disabled. Another reason to look for a better solution...
Finally, i've assembled the working solution from answers here. Thank you guys.
Declare notification name somewhere (e.g. AppDelegate.h):
static NSString * const kStatusBarTappedNotification = #"statusBarTappedNotification";
Add following lines to your AppDelegate.m:
#pragma mark - Status bar touch tracking
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
if (CGRectContainsPoint(statusBarFrame, location)) {
[self statusBarTouchedAction];
}
}
- (void)statusBarTouchedAction {
[[NSNotificationCenter defaultCenter] postNotificationName:kStatusBarTappedNotification
object:nil];
}
Observe notification in the needed controller (e.g. in viewWillAppear):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(statusBarTappedAction:)
name:kStatusBarTappedNotification
object:nil];
Remove observer properly (e.g. in viewDidDisappear):
[[NSNotificationCenter defaultCenter] removeObserver:self name:kStatusBarTappedNotification object:nil];
Implement notification-handling callback:
- (void)statusBarTappedAction:(NSNotification*)notification {
NSLog(#"StatusBar tapped");
//handle StatusBar tap here.
}
Hope it will help.
Swift 3 update
Tested and works on iOS 9+.
Declare notification name somewhere:
let statusBarTappedNotification = Notification(name: Notification.Name(rawValue: "statusBarTappedNotification"))
Track status bar touches and post notification. Add following lines to your AppDelegate.swift:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let statusBarRect = UIApplication.shared.statusBarFrame
guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return }
if statusBarRect.contains(touchPoint) {
NotificationCenter.default.post(statusBarTappedNotification)
}
}
Observe notification where necessary:
NotificationCenter.default.addObserver(forName: statusBarTappedNotification.name, object: .none, queue: .none) { _ in
print("status bar tapped")
}
So this is my current solution, which works amazingly well. But please come with other ideas, as I don't really like it...
Add a scrollview somewhere in your view. Maybe hide it or place it below some other view etc.
Set its contentSize to be larger than the bounds
Set a non-zero contentOffset
In your controller implement a delegate of the scrollview like shown below.
By always returning NO, the scroll view never scrolls up and one gets a notification whenever the user hits the status bar. The problem is, however, that this does not work with a "real" content scroll view around. (see question)
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
// Do your action here
return NO;
}
Adding this to your AppDelegate.swift will do what you want:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
let events = event!.allTouches()
let touch = events!.first
let location = touch!.locationInView(self.window)
let statusBarFrame = UIApplication.sharedApplication().statusBarFrame
if CGRectContainsPoint(statusBarFrame, location) {
NSNotificationCenter.defaultCenter().postNotificationName("statusBarSelected", object: nil)
}
}
Now you can subscribe to the event where ever you need:
NSNotificationCenter.defaultCenter().addObserverForName("statusBarSelected", object: nil, queue: nil) { event in
// scroll to top of a table view
self.tableView!.setContentOffset(CGPointZero, animated: true)
}
Thanks Max, your solution worked for me after spending ages looking.
For information :
dummyScrollView = [[UIScrollView alloc] init];
dummyScrollView.delegate = self;
[view addSubview:dummyScrollView];
[view sendSubviewToBack:dummyScrollView];
then
dummyScrollView.contentSize = CGSizeMake(view.frame.size.width, view.frame.size.height+200);
// scroll it a bit, otherwise scrollViewShouldScrollToTop not called
dummyScrollView.contentOffset = CGPointMake(0, 1);
//delegate :
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
// DETECTED! - do what you need to
NSLog(#"scrollViewShouldScrollToTop");
return NO;
}
Note that I had a UIWebView also which I had to hack a bit with a solution I found somewhere :
- (void)webViewDidFinishLoad:(UIWebView *)wv
{
[super webViewDidFinishLoad:wv];
UIScrollView *scroller = (UIScrollView *)[[webView subviews] objectAtIndex:0];
if ([scroller respondsToSelector:#selector(setScrollEnabled:)])
scroller.scrollEnabled = NO;
}
Found a much better solution which is iOS7 compatible here :http://ruiaureliano.tumblr.com/post/37260346960/uitableview-tap-status-bar-to-scroll-up
Add this method to your AppDelegate:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
if (CGRectContainsPoint([UIApplication sharedApplication].statusBarFrame, location)) {
NSLog(#"STATUS BAR TAPPED!");
}
}
I implemented this by adding a clear UIView over the status bar and then intercepting the touch events
First in your Application delegate application:didFinishLaunchingWithOptions: add these 2 lines of code:
self.window.backgroundColor = [UIColor clearColor];
self.window.windowLevel = UIWindowLevelStatusBar+1.f;
Then in the view controller you wish to intercept status bar taps (or in the application delegate) add the following code
UIView* statusBarInterceptView = [[[UIView alloc] initWithFrame:[UIApplication sharedApplication].statusBarFrame] autorelease];
statusBarInterceptView.backgroundColor = [UIColor clearColor];
UITapGestureRecognizer* tapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(statusBarClicked)] autorelease];
[statusBarInterceptView addGestureRecognizer:tapRecognizer];
[[[UIApplication sharedApplication].delegate window] addSubview:statusBarInterceptView];
In the statusBarClicked selector, do what you need to do, in my case I posted a notification to the notification center so that other view controllers can respond to the status bar tap.
Use an invisible UIScrollView. Tested at iOS 10 & Swift 3.
override func viewDidLoad() {
let scrollView = UIScrollView()
scrollView.bounds = view.bounds
scrollView.contentOffset.y = 1
scrollView.contentSize.height = view.bounds.height + 1
scrollView.delegate = self
view.addSubview(scrollView)
}
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
debugPrint("status bar tapped")
return false
}
You can track status bar tap by using following code:
[[NSNotificationCenter defaultCenter] addObserverForName:#"_UIApplicationSystemGestureStateChangedNotification"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
NSLog(#"Status bar pressed!");
}];
One way, might not be the best, could be to put a clear UIView on top of the status bar and intercept the touches of the UIView, might help you out if nothing else comes up...
If you're just trying to have a UIScrollView scroll to the top when the status bar is tapped, it's worth noting that this is the default behavior IF your view controller has exactly one UIScrollView in its subviews that has scrollsToTop set to YES.
So I just had to go and find any other UIScrollView (or subclasses: UITableView, UICollectionView, and set scrollsToTop to be NO.
To be fair, I found this info in the post that was linked to in the original question, but it's also dismissed as no longer working so I skipped it and only found the relevant piece on a subsequent search.
For iOS 13 this has worked for me, Objective-C category of UIStatusBarManager
#implementation UIStatusBarManager (CAPHandleTapAction)
-(void)handleTapAction:(id)arg1 {
// Your code here
}
#end

How do I detect if keyboard is currently shown, if I transitioned from another view controller that was already showing the keyboard?

I have a view controller that makes a UITextField firstResponder on ViewWillAppear. Normally I could just rely on a UIKeyboardWillShow notification to detect if the keyboard has shown, but this won't trigger if I came into the current view controller while the keyboard was already showing.
Anyone have any ideas?
I noticed while debugging view hierarchy that when keyboard is presented there's UIRemoteKeyboardWindow in hierarchy.
First we can add extension to UIApplication to check window hierarchy for UIRemoteKeyboardWindow:
extension UIApplication {
var isKeyboardPresented: Bool {
if let keyboardWindowClass = NSClassFromString("UIRemoteKeyboardWindow"), self.windows.contains(where: { $0.isKind(of: keyboardWindowClass) }) {
return true
} else {
return false
}
}
}
Then in viewDidLoad, or where needed we can check:
if UIApplication.shared.isKeyboardPresented {
print("Keyboard is presented")
}
Although this method is not fully tested and UIRemoteKeyboardWindow is in private headers that's why NSClassFromString is needed for check. Use it with concern!
When you enter in a textField, it becomes first responder and then the keyboard will appears on your view. You can check the status of the keyboard in your viewWillAppear method [textField isFirstResponder]. If it returns YES, means your keyboard is visible.
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if([textField isFirstResponder]){
//visible keyboard
}
}
Edited
If you want the height than you can store the keyboard height in some class variable when it appears first time and use in viewWillAppear method
#implementation YourClass{
CGFloat keyboardSize;
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if([textField isFirstResponder]){
//user keyboardSize here
}
}

ipad keyboard dismissal callback [duplicate]

I realize that this is the inverse of most posts, but I would like for the keyboard to remain up even if the 'keyboard down' button is pressed.
Specifically, I have a view with two UITextFields. With the following delegate method
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
return NO;
}
I am able to keep the keyboard up even if the user presses the Done button on the keyboard or taps anywhere else on the screen EXCEPT for that pesky keyboard down button on the bottom right of the keyboard.
I am using this view like a modal view (though the view is associated with a ViewController that gets pushed in a UINavigationController), so it really works best from a user perspective to keep the keyboard up all of the time. If anyone knows how to achieve this, please let me know! Thanks!
UPDATE Still no solution! When Done is pressed, it triggers textFieldShouldReturn, but when the Dismiss button is pressed, it triggers textFieldDidEndEditing. I cannot block the textField from ending editing or it never goes away. Somehow, I really want to have a method that detects the Dismiss button and ignores it. If you know a way, please enlighten me!
There IS a way to do this. Because UIKeyboard subclasses UIWindow, the only thing big enough to get in UIKeyboard's way is another UIWindow.
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(coverKey) name:UIKeyboardDidShowNotification object:nil];
[super viewDidLoad];
}
- (void)coverKey {
CGRect r = [[UIScreen mainScreen] bounds];
UIWindow *myWindow = [[UIWindow alloc] initWithFrame:CGRectMake(r.size.width - 50 , r.size.height - 50, 50, 50)];
[myWindow setBackgroundColor:[UIColor clearColor]];
[super.view addSubview:myWindow];
[myWindow makeKeyAndVisible];
}
This works on iPhone apps. Haven't tried it with iPad. You may need to adjust the size of myWindow. Also, I didn't do any mem management on myWindow. So, consider doing that, too.
I think I've found a good solution.
Add a BOOL as instance variable, let's call it shouldBeginCalledBeforeHand
Then implement the following methods:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
shouldBeginCalledBeforeHand = YES;
return YES;
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
return shouldBeginCalledBeforeHand;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
shouldBeginCalledBeforeHand = NO;
}
As well as
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
return NO;
}
to prevent the keyboard from disappearing with the return button. The trick is, a focus switch from one textfield to another will trigger a textFieldShouldBeginEditing beforehand. If the dismiss keyboard button is pressed this doesn't happen. The flag is reset after a textfield has gotten focus.
Old not perfect solution
I can only think of a not perfect solution. Listen for the notification UIKeyboardDidHideNotification and make of the textfields first responder again. This will move the keyboard out of sight and back again. You could keep record of which textfield was the last firstResponder by listening for UIKeyboardWillHideNotification and put focus on it in the didHide.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidHide:)
name:UIKeyboardDidHideNotification
object:nil];
...
- (void)keyboardDidHide:(id)sender
{
[myTextField becomeFirstResponder];
}
For iOS 9/10 and Swift 3, use this to create a rect which overlaps the "Hide keyboard" - Button
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(coverKey), name: .UIKeyboardDidShow, object: nil)
}
func coverKey() {
if let keyboardWindow = UIApplication.shared.windows.last {
let r = UIScreen.main.bounds
let myWindow = UIWindow.init(frame: CGRect(x: r.size.width - 50 , y: r.size.height - 50, width: 50, height: 50))
myWindow.backgroundColor = UIColor.clear
myWindow.isHidden = false
keyboardWindow.addSubview(myWindow)
keyboardWindow.bringSubview(toFront: myWindow)
}
}
Notice that this adds a sub view to the keyboard window instead of the main window
Try adding a custom on top of the keyboard dismiss button so that the user won't be able to tab the dismiss button. I have used this method in one of my application.
- (void)addButtonToKeyboard {
// create custom button
UIButton *blockButton = [UIButton buttonWithType:UIButtonTypeCustom];
blockButton.frame = //set the frame here, I don't remember the exact frame
[blockButton setImage:[UIImage imageNamed:#"block_button.png"] forState:UIControlStateNormal];
// locate keyboard view
UIWindow *appWindows = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
UIView *keyboard;
for (int i=0; i<[appWindows.subviews count]; i++) {
keyboard = [appWindows.subviews objectAtIndex:i];
// keyboard found, add the button
if ([[keyboard description] hasPrefix:#"<UIPeripheralHost"] == YES && [self.textField isFirstResponder]) {
[keyboard addSubview:doneButton];
}
}
}
Try this...
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField{
return NO;
}
You can use notification as mentioned by Nick Weaver.

How can I click a button behind a transparent UIView?

Let's say we have a view controller with one sub view. the subview takes up the center of the screen with 100 px margins on all sides. We then add a bunch of little stuff to click on inside that subview. We are only using the subview to take advantage of the new frame ( x=0, y=0 inside the subview is actually 100,100 in the parent view).
Then, imagine that we have something behind the subview, like a menu. I want the user to be able to select any of the "little stuff" in the subview, but if there is nothing there, I want touches to pass through it (since the background is clear anyway) to the buttons behind it.
How can I do this? It looks like touchesBegan goes through, but buttons don't work.
Create a custom view for your container and override the pointInside: message to return false when the point isn't within an eligible child view, like this:
Swift:
class PassThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
return true
}
}
return false
}
}
Objective C:
#interface PassthroughView : UIView
#end
#implementation PassthroughView
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView *view in self.subviews) {
if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
return YES;
}
return NO;
}
#end
Using this view as a container will allow any of its children to receive touches but the view itself will be transparent to events.
I also use
myView.userInteractionEnabled = NO;
No need to subclass. Works fine.
From Apple:
Event forwarding is a technique used by some applications. You forward touch events by invoking the event-handling methods of another responder object. Although this can be an effective technique, you should use it with caution. The classes of the UIKit framework are not designed to receive touches that are not bound to them .... If you want to conditionally forward touches to other responders in your application, all of these responders should be instances of your own subclasses of UIView.
Apples Best Practise:
Do not explicitly send events up the responder chain (via nextResponder); instead, invoke the superclass implementation and let the UIKit handle responder-chain traversal.
instead you can override:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
in your UIView subclass and return NO if you want that touch to be sent up the responder chain (I.E. to views behind your view with nothing in it).
A far simpler way is to "Un-Check" User Interaction Enabled in the interface builder. "If you are using a storyboard"
Lately I wrote a class that will help me with just that. Using it as a custom class for a UIButton or UIView will pass touch events that were executed on a transparent pixel.
This solution is a somewhat better than the accepted answer because you can still click a UIButton that is under a semi transparent UIView while the non transparent part of the UIView will still respond to touch events.
As you can see in the GIF, the Giraffe button is a simple rectangle but touch events on transparent areas are passed on to the yellow UIButton underneath.
Link to class
Top voted solution was not fully working for me, I guess it was because I had a TabBarController into the hierarchy (as one of the comments points out) it was in fact passing along touches to some parts of the UI but it was messing with my tableView's ability to intercept touch events, what finally did it was overriding hitTest in the view I want to ignore touches and let the subviews of that view handle them
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
if (view == self) {
return nil; //avoid delivering touch events to the container view (self)
}
else{
return view; //the subviews will still receive touch events
}
}
Building on what John posted, here is an example that will allow touch events to pass through all subviews of a view except for buttons:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// Allow buttons to receive press events. All other views will get ignored
for( id foundView in self.subviews )
{
if( [foundView isKindOfClass:[UIButton class]] )
{
UIButton *foundButton = foundView;
if( foundButton.isEnabled && !foundButton.hidden && [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
return YES;
}
}
return NO;
}
Swift 3
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if subview.frame.contains(point) {
return true
}
}
return false
}
According to the 'iPhone Application Programming Guide':
Turning off delivery of touch events.
By default, a view receives touch
events, but you can set its userInteractionEnabled property to NO
to turn off delivery of events. A view also does not receive events if it’s hidden
or if it’s transparent.
http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html
Updated: Removed example - reread the question...
Do you have any gesture processing on the views that may be processing the taps before the button gets it? Does the button work when you don't have the transparent view over it?
Any code samples of non-working code?
As far as I know, you are supposed to be able to do this by overriding the hitTest: method. I did try it but could not get it to work properly.
In the end I created a series of transparent views around the touchable object so that they did not cover it. Bit of a hack for my issue this worked fine.
Taking tips from the other answers and reading up on Apple's documentation, I created this simple library for solving your problem:
https://github.com/natrosoft/NATouchThroughView
It makes it easy to draw views in Interface Builder that should pass touches through to an underlying view.
I think method swizzling is overkill and very dangerous to do in production code because you are directly messing with Apple's base implementation and making an application-wide change that could cause unintended consequences.
There is a demo project and hopefully the README does a good job explaining what to do. To address the OP, you would change the clear UIView that contains the buttons to class NATouchThroughView in Interface Builder. Then find the clear UIView that overlays the menu that you want to be tap-able. Change that UIView to class NARootTouchThroughView in Interface Builder. It can even be the root UIView of your view controller if you intend those touches to pass through to the underlying view controller. Check out the demo project to see how it works. It's really quite simple, safe, and non-invasive
I created a category to do this.
a little method swizzling and the view is golden.
The header
//UIView+PassthroughParent.h
#interface UIView (PassthroughParent)
- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;
#end
The implementation file
#import "UIView+PassthroughParent.h"
#implementation UIView (PassthroughParent)
+ (void)load{
Swizz([UIView class], #selector(pointInside:withEvent:), #selector(passthroughPointInside:withEvent:));
}
- (BOOL)passthroughParent{
NSNumber *passthrough = [self propertyValueForKey:#"passthroughParent"];
if (passthrough) return passthrough.boolValue;
return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
[self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:#"passthroughParent"];
}
- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
// Allow buttons to receive press events. All other views will get ignored
if (self.passthroughParent){
if (self.alpha != 0 && !self.isHidden){
for( id foundView in self.subviews )
{
if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
return YES;
}
}
return NO;
}
else {
return [self passthroughPointInside:point withEvent:event];// Swizzled
}
}
#end
You will need to add my Swizz.h and Swizz.m
located Here
After that, you just Import the UIView+PassthroughParent.h in your {Project}-Prefix.pch file, and every view will have this ability.
every view will take points, but none of the blank space will.
I also recommend using a clear background.
myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];
EDIT
I created my own property bag, and that was not included previously.
Header file
// NSObject+PropertyBag.h
#import <Foundation/Foundation.h>
#interface NSObject (PropertyBag)
- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;
#end
Implementation File
// NSObject+PropertyBag.m
#import "NSObject+PropertyBag.h"
#implementation NSObject (PropertyBag)
+ (void) load{
[self loadPropertyBag];
}
+ (void) loadPropertyBag{
#autoreleasepool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Swizz([NSObject class], NSSelectorFromString(#"dealloc"), #selector(propertyBagDealloc));
});
}
}
__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
[[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:#"%p",self]];
if (propBag == nil){
propBag = [NSMutableDictionary dictionary];
[self setPropertyBag:propBag];
}
return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
[_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:#"%p",self]];
}
- (void)propertyBagDealloc{
[self setPropertyBag:nil];
[self propertyBagDealloc];//Swizzled
}
#end
Try set a backgroundColor of your transparentView as UIColor(white:0.000, alpha:0.020). Then you can get touch events in touchesBegan/touchesMoved methods. Place the code below somewhere your view is inited:
self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
Try this
class PassthroughToWindowView: UIView {
override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var view = super.hitTest(point, with: event)
if view != self {
return view
}
while !(view is PassthroughWindow) {
view = view?.superview
}
return view
}
}
I use that instead of override method point(inside: CGPoint, with: UIEvent)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard self.point(inside: point, with: event) else { return nil }
return self
}
If you can't bother to use a category or subclass UIView, you could also just bring the button forward so that it is in front of the transparent view. This won't always be possible depending on your application, but it worked for me. You can always bring the button back again or hide it.

iPhone Keyboard Covers UITextField

I have an app where, in Interface Builder, I set up a UIView that has a text field near the bottom of the view. When I run the app and try to enter text into that field, the keyboard slides up overtop of the field so I can't see what I'm typing until I hide the keyboard again.
Has anyone else run into this problem and found a good way to solve it without either making the parent view scrollable or moving the text field farther up the screen?
The usual solution is to slide the field (and everything above it) up with an animation, and then back down when you are done. You may need to put the text field and some of the other items into another view and slide the view as a unit. (I call these things "plates" as in "tectonic plates", but that's just me). But here is the general idea if you don't need to get fancy.
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self animateTextField: textField up: YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self animateTextField: textField up: NO];
}
- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
const int movementDistance = 80; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? -movementDistance : movementDistance);
[UIView beginAnimations: #"anim" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
This worked wonders for me sliding uitextfields
In particular it has the benefit of calculating the slide animation distance depending on the position of the text field.
IQKeyboardManager do this for you with NO LINE OF CODE, only need to drag and drop related source file to project. IQKeyboardManager also support Device Orientation, Automatic UIToolbar Management, keyboardDistanceFromTextField and much more than you think.
Here is the Control Flow Chart:
Step1:- Added global notifications of UITextField, UITextView, and UIKeyboard in a singleton class. I called it IQKeyboardManager.
Step2:- If found UIKeyboardWillShowNotification, UITextFieldTextDidBeginEditingNotification or UITextViewTextDidBeginEditingNotification notifications, then try to get topMostViewController instance from the UIWindow.rootViewController hierarchy. In order to properly uncover UITextField/UITextView on it, topMostViewController.view's frame needs to be adjusted.
Step3:- Calculated expected move distance of topMostViewController.view with respect to first responded UITextField/UITextView.
Step4:- Moved topMostViewController.view.frame up/down according to the expected move distance.
Step5:- If found UIKeyboardWillHideNotification, UITextFieldTextDidEndEditingNotification or UITextViewTextDidEndEditingNotification notification, then again try to get topMostViewController instance from the UIWindow.rootViewController hierarchy.
Step6:- Calculated disturbed distance of topMostViewController.view which needs to be restored to it's original position.
Step7:- Restored topMostViewController.view.frame down according to the disturbed distance.
Step8:- Instantiated singleton IQKeyboardManager class instance on app load, so every UITextField/UITextView in the app will adjust automatically according to the expected move distance.
That's all
To expand on Amagrammer answer, here is a sample class:
LoginViewController.h
#interface LoginViewController : UIViewController <UITextFieldDelegate> {
}
#property (nonatomic, retain) IBOutlet UITextField *emailTextField;
#property (nonatomic, retain) IBOutlet UITextField *passwordTextField;
Notice we are implementing the "UITextFieldDelegate"
LoginViewController.m
#implementation LoginViewController
#synthesize emailTextField=_emailTextField;
#synthesize passwordTextField=_passwordTextField;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
//Register to receive an update when the app goes into the backround
//It will call our "appEnteredBackground method
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appEnteredBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
return self;
}
- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
const int movementDistance = 80; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? -movementDistance : movementDistance);
[UIView beginAnimations: #"anim" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self animateTextField: textField up: YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self animateTextField: textField up: NO];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
//This is called when the app goes into the background.
//We must reset the responder because animations will not be saved
- (void)appEnteredBackground{
[self.emailTextField resignFirstResponder];
[self.passwordTextField resignFirstResponder];
}
How about the official solution: Moving Content That Is Located Under the Keyboard
Adjusting your content typically involves temporarily resizing one or
more views and positioning them so that the text object remains
visible. The simplest way to manage text objects with the keyboard is
to embed them inside a UIScrollView object (or one of its subclasses
like UITableView). When the keyboard is displayed, all you have to do
is reset the content area of the scroll view and scroll the desired
text object into position. Thus, in response to a
UIKeyboardDidShowNotification, your handler method would do the
following:
Get the size of the keyboard.
Adjust the bottom content inset of your scroll view by the keyboard
height.
Scroll the target text field into view.
// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, activeField.frame.origin) ) {
[self.scrollView scrollRectToVisible:activeField.frame animated:YES];
}
}
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
}
I have face the same issue in UITableView textField cells. I solve this issue by implementing following method to listen the keyboard notification.
Observer for the notifications here:
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(keyboardWasShown:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];
Handle those notification by using below function:
(void)keyboardWasShown:(NSNotification*)aNotification
(void)keyboardWillBeHidden:(NSNotification*)aNotification
Check this out.
No hassle for you.
This solution is very neat. All you have to do is to add your textfields in a UIScrollView and change its class to TPKeyboardAvoidingScollView, if you are using storyboards. The scroll view is extended in such a way that it would detect when keyboard is visible and will move itself above keyboard at a reasonable distance. It is perfect solution because its independent of your UIViewController. Every necessary thing is done within the the above mentioned class. Thanks Michael Tyson et all.
TPKeyboardAvoiding
Below is a swift version of Amagrammer's answer. Also, a variation using the UIKeyboardWillShowNotification event since I needed to know the keyboards size before moving the view out of the way.
var keyboardHeight:CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillChange:", name: UIKeyboardWillShowNotification, object: nil)
}
func textFieldDidBeginEditing(textField: UITextField) {
//keyboardWillChange (below) is used instead of textFieldDidBeginEditing because textFieldDidBeginEditing
//is called before the UIKeyboardWillShowNotification necessary to determine the keyboard height.
}
func textFieldDidEndEditing(textField: UITextField) {
animateTextField(false)
}
func animateTextField(textFieldUp:Bool) {
let movementDistance:CGFloat = keyboardHeight
let movementDuration = 0.3
let movement:CGFloat = (textFieldUp ? -movementDistance : movementDistance)
UIView.beginAnimations("anim", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
UIView.commitAnimations()
}
func keyboardWillChange(notification:NSNotification) {
let keyboardRect:CGRect = ((notification.userInfo![UIKeyboardFrameEndUserInfoKey])?.CGRectValue)!
keyboardHeight = keyboardRect.height
animateTextField(true)
}
There was a great walkthrough at editing textfields without obscuring (link dead now, here's a Wayback link: https://web.archive.org/web/20091123074029/http://acts-as-geek.blogspot.com/2009/11/editing-textfields-without-obscuring.html). It shows how to move an existing UIView onto a UIScrollView, and to scroll it automatically when the keyboard appears.
I've updated it a bit to calculate the correct height for the UIScrollView when there are controls (such as a UITabBar) below the UIScrollBar. See post updating uiview.
Here's a solution using Xcode5, iOS7:
Use the UITextfieldDelegate and animation blocks.
This is nearly all the code for the ViewController but I wanted to include the delegate code for those still somewhat unfamiliar with the delegate pattern (like me). I also included code to hide the keyboard when you tap away from the textview.
You can move the views(buttons, textfields, etc) as high as you'd like just make sure to put them back in place (+100 then later -100).
#interface ViewController () <UITextFieldDelegate>
#property (strong, nonatomic) IBOutlet UITextField *MyTextField;
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.MyTextField.delegate = self;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
NSLog(#"text began editing");
CGPoint MyPoint = self.MyTextField.center;
[UIView animateWithDuration:0.3
animations:^{
self.MyTextField.center = CGPointMake(MyPoint.x, MyPoint.y - 100);
}];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
NSLog(#"text ENDED editing");
CGPoint MyPoint = self.MyTextField.center;
[UIView animateWithDuration:0.3
animations:^{
self.MyTextField.center = CGPointMake(MyPoint.x, MyPoint.y + 100);
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
I guess one way would be to move your whole views position from (x,y) to (x,y-keybaardHeight) when the textfield is clicked and put it back when the keyboard is dismissed , might look a little odd as the view just comes up (maybe it wouldnt be bad if you animate it).
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
CGRect frame=self.view.frame;
frame.origin=CGPointMake(x...//set point here
self.view.frame=frame;
}
In addition to Amagrammer's solution, if you are using cocos2d in portrait mode change this line:
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
to this:
[CCDirector sharedDirector].openGLView.frame = CGRectOffset([CCDirector sharedDirector].openGLView.frame, movement, 0);
If you are using cocos2d in landscape mode, make the above change and switch the up values in textFieldDidBeginEditing: and textFieldDidEndEditing:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
[self animateTextField:textField up:NO];
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self animateTextField:textField up:YES];
}
I had the same problem and found GTKeyboardHelper to be an easy way out.
After drag and drop the framework in your project, include the header file.
Download and open the example project, then drag the "Keyboard Helper" object from the objects section in the xib to the objects section in your project's interface builder.
Drag and drop all your views to be children of the "Keyboard Helper".
Drag and drop framework that I use in my projects. Supports automatic dismissal when you tap outside of a first responder or when you scroll.
GTKeyboardHelper
Just slide the view up and down as needed :
- (void)textFieldDidEndEditing:(UITextField *)textField {
self.currentTextField = nil;
[self animateTextField: textField up: NO];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self.currentTextField resignFirstResponder];
return YES;
}
- (void) animateTextField:(UITextField*) textField up:(BOOL)up {
const int movementDistance = 80; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? -movementDistance : movementDistance);
[UIView animateWithDuration:movementDuration animations:^{
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
}];
}
Don't forget to set self as a UITextFieldDelegate and as the actual textField delegate.
(Thanks to Ammagrammer, this is just a shorter answer using blocks for animations)
I have something else if you want. The point here is that you want to set the center your UIView on the text field you are editing.
Before that, you have to save your INITIAL_CENTER, as a CGPoint, from self.view.center and your INITIAL_VIEW as a CGRect from self.view.frame in a const property.
You can create a method like this :
- (void) centerOn: (CGRect) fieldFrame {
// Set up the center by taking the original view center
CGPoint center = CGPointMake(INITIAL_CENTER.x,
INITIAL_CENTER.y - ((fieldFrame.origin.y + fieldFrame.size.height/2) - INITIAL_CENTER.y));
[UIView beginAnimations:#"centerViewOnField" context:nil];
[UIView setAnimationDuration:0.50];
if (CGRectEqualToRect(fieldFrame,INITIAL_VIEW)) {
self.view.frame = INITIAL_VIEW;
[self.view setCenter:INITIAL_CENTER];
} else {
[self.view setCenter:center];
}
[UIView commitAnimations];
}
Then, on your UITextFieldDelegate, you have to call centerOn:(CGRect) in following methods :
textFieldDidBeginEditing:(UITextField*) with, as a parameter, the frame of the text field you want to center on.
And you have to call it in your event handler, where you close your keyboard,
textFieldDidEndEditing:(UITextField*) can be one of the ways to do it, putting the INITIAL_VIEW as a parameter of centerOn:(CGRect).
I believe on newer versions of iOS (6.1+, possibly even earlier), the underlying view, at least for UITableView, auto-shrinks when the keyboard pops up. So you only need to make the text field visible in that view. In init:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification
object:nil];
then:
- (void)keyboardWasShown:(NSNotification*)notification
{
// Scroll the text field into view so it's not under the keyboard.
CGRect rect = [self.tableView convertRect:inputView.bounds fromView:inputView];
[self.tableView scrollRectToVisible:rect animated:YES];
}
https://github.com/ZulwiyozaPutra/Shift-Keyboard-Example I hope this solution helped. They are all Swift 3 written.
//
// ViewController.swift
// Shift Keyboard Example
//
// Created by Zulwiyoza Putra on 11/23/16.
// Copyright © 2016 Zulwiyoza Putra. All rights reserved.
//
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
//connecting textfield from storyboard
#IBOutlet weak var textField: UITextField!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
subscribeToKeyboardNotifications()
}
override func viewDidAppear(_ animated: Bool) {
self.textField.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
unsubscribeFromKeyboardNotifications()
}
//Hide keyboard after finished editing
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
//Setup view before keyboard appeared
func keyboardWillAppear(_ notification:Notification) {
view.frame.origin.y = 0 - getKeyboardHeight(notification)
}
//Setup view before keyboard disappeared
func keyboardWillDisappear(_ notification: Notification) {
view.frame.origin.y = 0
}
//Getting keyboard height
func getKeyboardHeight(_ notification:Notification) -> CGFloat {
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue // of CGRect
return keyboardSize.cgRectValue.height
}
//Subscribing to notifications to execute functions
func subscribeToKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(_:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear(_:)), name: .UIKeyboardWillHide, object: nil)
}
//Unsubscribing from notifications
func unsubscribeFromKeyboardNotifications() {
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)
}
}

Resources