iOS 7 UIWebView keyboard issue - ios

I have to remove this bar as here link but for iOS 7 this code does not work.

We remove this bar with some Objective C runtime trickery.
We have a class which has one method:
#interface _SwizzleHelper : NSObject #end
#implementation _SwizzleHelper
-(id)inputAccessoryView
{
return nil;
}
#end
Once we have a web view which we want to remove the bar from, we iterate its scroll view's subviews and take the UIWebDocumentView class. We then dynamically make the superclass of the class we created above to be the subview's class (UIWebDocumentView - but we cannot say that upfront because this is private API), and replace the subview's class to our class.
#import "objc/runtime.h"
-(void)__removeInputAccessoryView
{
UIView* subview;
for (UIView* view in self.scrollView.subviews) {
if([[view.class description] hasPrefix:#"UIWeb"])
subview = view;
}
if(subview == nil) return;
NSString* name = [NSString stringWithFormat:#"%#_SwizzleHelper", subview.class.superclass];
Class newClass = NSClassFromString(name);
if(newClass == nil)
{
newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
if(!newClass) return;
Method method = class_getInstanceMethod([_SwizzleHelper class], #selector(inputAccessoryView));
class_addMethod(newClass, #selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
objc_registerClassPair(newClass);
}
object_setClass(subview, newClass);
}
The equivalent of the above in Swift 3.0:
import UIKit
import ObjectiveC
var swizzledClassMapping = [AnyClass]()
extension UIWebView {
func noInputAccessoryView() -> UIView? {
return nil
}
public func removeInputAccessoryView() {
var subview: AnyObject?
for (_, view) in scrollView.subviews.enumerated() {
if NSStringFromClass(type(of: view)).hasPrefix("UIWeb") {
subview = view
}
}
guard subview != nil else {
return
}
//Guard in case this method is called twice on the same webview.
guard !(swizzledClassMapping as NSArray).contains(type(of: subview!)) else {
return;
}
let className = "\type(of: subview!)_SwizzleHelper"
var newClass : AnyClass? = NSClassFromString(className)
if newClass == nil {
newClass = objc_allocateClassPair(type(of: subview!), className, 0)
guard newClass != nil else {
return;
}
let method = class_getInstanceMethod(type(of: self), #selector(UIWebView.noInputAccessoryView))
class_addMethod(newClass!, #selector(getter: UIResponder.inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method))
objc_registerClassPair(newClass!)
swizzledClassMapping += [newClass!]
}
object_setClass(subview!, newClass!)
}
}

I've made a cocoapod based on this blog post from #bjhomer.
You can replace the inputaccessoryview and not just hide it. I hope this will help people with the same issue.
https://github.com/lauracpierre/FA_InputAccessoryViewWebView
You can find the cocoapod page right here.

I've came across this awesome solution, but I needed to get the inputAccessoryView back as well. I added this method:
- (void)__addInputAccessoryView {
UIView* subview;
for (UIView* view in self.scrollView.subviews) {
if([[view.class description] hasSuffix:#"SwizzleHelper"])
subview = view;
}
if(subview == nil) return;
Class newClass = subview.superclass;
object_setClass(subview, newClass);
}
It does seem to work as intended with no side effects, but I can't get rid of the feeling that my pants are on fire.
If you want Leo Natan's solution to work with WKWebView instead of UIWebView just change prefix from "UIWeb" to "WKContent".

I created a gist to accomplish this:
https://gist.github.com/kgaidis/5f9a8c7063b687cc3946fad6379c1a66
It's a UIWebView category where all you do is change the customInputAccessoryView property:
#interface UIWebView (CustomInputAccessoryView)
#property (strong, nonatomic) UIView *customInputAccessoryView;
#end
You can either set it to nil to remove it or you can set a new view on it to change it.
Keep in mind, this also uses private API's, so use at your own risk, but it seems like a lot of apps do similar things nonetheless.

Related

How can I hide the input accessory view of the keyboard when using a WKWebView?

We have a webview in our app to edit some text in a semi-wysiwyg style. It's just a fullscreen text area so we don't want those next/previous/done buttons on the keyboard taking in precious screen space. Previously, the following worked to remove the whole bar where those buttons are shown:
-(void)__removeInputAccessoryView {
UIView* subview;
for (UIView* view in _editorView.scrollView.subviews) {
if([[view.class description] hasPrefix:#"UIWeb"])
subview = view;
}
if(subview == nil) {
return;
}
NSString* name = [NSString stringWithFormat:#"%#RWWebviewHelper", subview.class.superclass];
Class newClass = NSClassFromString(name);
if(newClass == nil) {
newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
if(!newClass) {
return;
}
Method method = class_getInstanceMethod([RWWebviewHelper class], #selector(inputAccessoryView));
class_addMethod(newClass, #selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
objc_registerClassPair(newClass);
}
object_setClass(subview, newClass);
}
I found similar snippets on SO and Github, both in Objective-C and Swift. I'm currently converting this class from Objective-C to Swift and from using UIWebView to using a WKWebView. I found this on SO somewhere:
private func removeInputAccessoryView() {
final class FauxBarHelper: NSObject {
var inputAccessoryView: AnyObject? { return nil }
}
editorView.scrollView.subviews.forEach { view in
if String(describing: type(of: view)).hasPrefix("WKcon") {
let noInputAccessoryViewClassName = "\(view.superclass!)_NoInputAccessoryView"
var newClass: AnyClass? = NSClassFromString(noInputAccessoryViewClassName)
if newClass == nil {
let targetClass: AnyClass = object_getClass(view)
newClass = objc_allocateClassPair(targetClass, noInputAccessoryViewClassName.cString(using: String.Encoding.ascii)!, 0)
}
let originalMethod = class_getInstanceMethod(FauxBarHelper.self, #selector(getter: FauxBarHelper.inputAccessoryView))
class_addMethod(newClass!.self, #selector(getter: FauxBarHelper.inputAccessoryView), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
object_setClass(view, newClass)
}
}
}
But it doesn't hide the bar. I'm not too well versed in hacking the private API and after playing around with the values in that snippet I can't find a way to hide the bar. I know this is a store rejection risk, but you know how clients are. I need to get this working. I can work with an Objective-C snippet, but prefer Swift.
Does anyone have an idea? I feel it shouldn't be very hard to hide a button bar from an onscreen keyboard, am I missing something simple?

UIWebView Bug: -[UIWebView cut:]: unrecognized selector sent to instance

In the UIWebView, if an input element containing text has focus, and a button is pressed that causes the input to lose focus, then subsequently double-tapping on the input to regain focus and selecting Cut (or Copy or Paste) from the popup bar that appears causes the UIWebView to crash with the error:
-[UIWebView cut:]: unrecognized selector sent to instance 0x10900ca60
Demo project: https://github.com/guarani/WebViewDoubleTapTestTests.git
I think this must be a UIWebView bug, any ideas?
For completeness, here are the contents of my web view,
<html>
<head>
</head>
<body>
<br><br>
<input type="text">
<input type="button">
</body>
</html>
Filed a Bug Report at Apple: 15894403
Update 2019/05/30: Bug still present in iOS 12.0 (16E226)
This is an Apple bug. The problem is the cut: action is sent incorrectly in the responder chain, and ends up being sent to the UIWebView instance instead of the internal UIWebDocumentView, which implements the method.
Until Apple fixes the bug, let's have some fun with the Objective C runtime.
Here, I subclass UIWebView with the purpose of supporting all UIResponderStandardEditActions methods, by forwarding them to the correct internal instance.
#import ObjectiveC;
#interface CutCopyPasteFixedWebView : UIWebView #end
#implementation CutCopyPasteFixedWebView
- (UIView*)_internalView
{
UIView* internalView = objc_getAssociatedObject(self, "__internal_view_key");
if(internalView == nil && self.subviews.count > 0)
{
for (UIView* view in self.scrollView.subviews) {
if([view.class.description hasPrefix:#"UIWeb"])
{
internalView = view;
objc_setAssociatedObject(self, "__internal_view_key", view, OBJC_ASSOCIATION_ASSIGN);
break;
}
}
}
return internalView;
}
void webView_implement_UIResponderStandardEditActions(id self, SEL selector, id param)
{
void (*method)(id, SEL, id) = (void(*)(id, SEL, id))[[self _internalView] methodForSelector:selector];
//Call internal implementation.
method([self _internalView], selector, param);
}
- (void)_prepareForNoCrashes
{
NSArray* selectors = #[#"cut:", #"copy:", #"paste:", #"select:", #"selectAll:", #"delete:", #"makeTextWritingDirectionLeftToRight:", #"makeTextWritingDirectionRightToLeft:", #"toggleBoldface:", #"toggleItalics:", #"toggleUnderline:", #"increaseSize:", #"decreaseSize:"];
for (NSString* selName in selectors)
{
SEL selector = NSSelectorFromString(selName);
//This is safe, the method will fail if there is already an implementation.
class_addMethod(self.class, selector, (IMP)webView_implement_UIResponderStandardEditActions, "");
}
}
- (void)awakeFromNib
{
[self _prepareForNoCrashes];
[super awakeFromNib];
}
#end
Use this subclass in your storyboard.
Have fun.
If you don't mind that there is no callout for cut/paste/etc. in the case, when the UIWebview is wrongly becoming first responder, then you can also fix it with this category. This does not prohibit cut/paste/etc. when the UIWebDocumentView (correctly) becomes first responder.
#implementation UIWebView (NoWrongPerformWebview)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
}
#end
// Swift 4 compliant version
import UIKit
extension UIWebView {
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// Should be respond to a certain Selector ??
return responds(to: action)
}
}
If anyone is interested, here's the swift version of Leo Natans method :
import Foundation
import ObjectiveC
var AssociatedObjectHandle: UInt8 = 0
class CustomWebView: UIWebView {
func _internalView() -> UIView? {
var internalView:UIView? = objc_getAssociatedObject(self, "__internal_view_key") as? UIView
if internalView == nil && self.subviews.count > 0 {
for view: UIView in self.scrollView.subviews {
if view.self.description.hasPrefix("UIWeb") {
internalView = view
objc_setAssociatedObject(self, "__internal_view_key", view, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN)
}
}
}
return internalView
}
override func awakeFromNib() {
super.awakeFromNib()
self._prepareForNoCrashes()
}
func _prepareForNoCrashes() {
let selectors = ["cut:", "copy:", "paste:", "select:", "selectAll:", "delete:", "makeTextWritingDirectionLeftToRight:", "makeTextWritingDirectionRightToLeft:", "toggleBoldface:", "toggleItalics:", "toggleUnderline:", "increaseSize:", "decreaseSize:"]
for selName: String in selectors {
let selector = NSSelectorFromString(selName)
//This is safe, the method will fail if there is already an implementation.
let swizzledMethod:IMP = class_getInstanceMethod(CustomWebView.self, #selector(CustomWebView.webView_implement_UIResponderStandardEditActions))
class_addMethod(CustomWebView.self, selector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
}
}
func webView_implement_UIResponderStandardEditActions(this:AnyObject, selector:Selector, param:AnyObject)
{
let method = {(val1: UIView?, val2: Selector, val3: AnyObject) -> Void in
self._internalView()?.methodForSelector(selector)
}
method(self._internalView(), selector, param);
}
}
- (UIView *)_internalView {
UIView *internalView = nil;
if (internalView == nil && self.subviews.count > 0) {
for (UIView *view in self.scrollView.subviews) {
if([view.class.description hasPrefix:#"UIWeb"]) {
internalView = view;
break;
}
}
}
return internalView;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
struct objc_method_description methodDescription = protocol_getMethodDescription(#protocol(UIResponderStandardEditActions), aSelector, NO, YES);
if (methodDescription.name == aSelector) {
UIView *view = [self _internalView];
if ([view respondsToSelector:aSelector]) {
return view;
}
}
return [super forwardingTargetForSelector:aSelector];
}

Access the UIPageControl created by iOS6 UIPageViewController?

I'm using a UIPageViewController with Navigation set to Horizontal, Transition Style set to Scroll (in InterfaceBuilder), and no spine. Which gives me a lovely UIPageControl integrated. Now I want to be able to toggle whether it's displaying (because there's artwork underneath it).
I've tried setting presentationCountForPageViewController and presentationIndexForPageViewController to return 0 when the UIPageControl is supposed to be hidden, but those methods aren't being called when I want.
Pausing for stacktrace, I see them being called by [UIPageViewController _updatePageControlViaDataSourceIfNecessary]...I assume my app would be rejected if I tried to use that method.
Should I hunt through subviews for it, or roll my own so I have control over it, or is there some better way to toggle its visibility?
Thanks!
I would say, hunt through the subviews. This code successfully finds the UIPageControl in the subviews hierarchy:
NSArray *subviews = pageController.view.subviews;
UIPageControl *thisControl = nil;
for (int i=0; i<[subviews count]; i++) {
if ([[subviews objectAtIndex:i] isKindOfClass:[UIPageControl class]]) {
thisControl = (UIPageControl *)[subviews objectAtIndex:i];
}
}
I'm using this to customize the color of the dots, I imagine you could do the same with the alpha value or send it to the back or something.
Apple provides no direct interface to the UIPageControl through the UIPageViewController class, but there are no illegal method calls required in order to get to it... I don't see why this would result in an app rejection.
You can access this for all PageControl objects by using appearance (see the UIAppearance protocol), but to get a specific instance you'd have to use recursion. Swift code:
let pageControl = UIPageControl.appearance()
Swift 3 Extension:
extension UIPageViewController {
var pageControl: UIPageControl? {
for view in view.subviews {
if view is UIPageControl {
return view as? UIPageControl
}
}
return nil
}
}
In Swift:
let subviews: Array = self.pageViewController.view.subviews
var pageControl: UIPageControl! = nil
for (var i = 0; i < subviews.count; i++) {
if (subviews[i] is UIPageControl) {
pageControl = subviews[i] as! UIPageControl
break
}
}
I implemented a category to handle this for me which gets the mess out of my code and allows me to access the pageControl via "pageController.pageControl"
Objective-C
// Header
#interface UIPageViewController (PageControl)
#property (nonatomic, readonly) UIPageControl *pageControl;
#end
I also used recursion (handled by blocks) in case Apple decides to change the implementation causing the UIPageControl to not be in the first layer of subviews.
// Implementation
#import "UIPageViewController+PageControl.h"
#implementation UIPageViewController (PageControl)
- (UIPageControl *)pageControl
{
__block UIPageControl *pageControl = nil;
void (^pageControlAssignBlock)(UIPageControl *) = ^void(UIPageControl *blockPageControl) {
pageControl = blockPageControl;
};
[self recurseForPageControlFromSubViews:self.view.subviews withAssignBlock:pageControlAssignBlock];
return pageControl;
}
- (void)recurseForPageControlFromSubViews:(NSArray *)subViews withAssignBlock:(void (^)(UIPageControl *))assignBlock
{
for (UIView *subView in subViews) {
if ([subView isKindOfClass:[UIPageControl class]]) {
assignBlock((UIPageControl *)subView);
break;
} else {
[self recurseForPageControlFromSubViews:subView.subviews withAssignBlock:assignBlock];
}
}
}
#end
This may be overkill for your needs but it worked well for mine
How about a nice, up to date Swift 1-liner?
let pageControl = view.subviews.first { $0 is UIPageControl } as? UIPageControl
Or if you like an extension:
extension UIPageViewController {
var pageControl: UIPageControl? {
return view.subviews.first { $0 is UIPageControl } as? UIPageControl
}
}
For Swift
To get the dots in the page control we can use
//dots will be an array of the dots views
let dots = pageControl.subviews
To get the current dot view
let currentDot = dots[pageControl.currentPage]
To get the other dots views
for i in 0..<dots.count {
let dot = dots[i]
if i == pageControl.currentPage {
//dot => current dot
} else {
//dot => other dot
}
}
After we get the dot view we can change whatever we want like
dot.layer.borderColor = .green
dot.layer.borderWidth = 1
C# extension:
public static class PageViewControllerExtension{
public static UIPageControl GetPageControl(this UIPageViewController pageViewController){
foreach (var view in pageViewController.View.Subviews){
var subView = view as UIPageControl;
if (subView != null){
return subView;
}
}
return null;
}
}

Get to UIViewController from UIView?

Is there a built-in way to get from a UIView to its UIViewController? I know you can get from UIViewController to its UIView via [self view] but I was wondering if there is a reverse reference?
Using the example posted by Brock, I modified it so that it is a category of UIView instead UIViewController and made it recursive so that any subview can (hopefully) find the parent UIViewController.
#interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
#end
#implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
UIResponder *responder = [self nextResponder];
while (responder != nil) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = [responder nextResponder];
}
return nil;
}
#end
To use this code, add it into an new class file (I named mine "UIKitCategories") and remove the class data... copy the #interface into the header, and the #implementation into the .m file. Then in your project, #import "UIKitCategories.h" and use within the UIView code:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView is a subclass of UIResponder. UIResponder lays out the method -nextResponder with an implementation that returns nil. UIView overrides this method, as documented in UIResponder (for some reason instead of in UIView) as follows: if the view has a view controller, it is returned by -nextResponder. If there is no view controller, the method will return the superview.
Add this to your project and you're ready to roll.
#interface UIView (APIFix)
- (UIViewController *)viewController;
#end
#implementation UIView (APIFix)
- (UIViewController *)viewController {
if ([self.nextResponder isKindOfClass:UIViewController.class])
return (UIViewController *)self.nextResponder;
else
return nil;
}
#end
Now UIView has a working method for returning the view controller.
Since this has been the accepted answer for a long time, I feel I need to rectify it with a better answer.
Some comments on the need:
Your view should not need to access the view controller directly.
The view should instead be independent of the view controller, and be able to work in different contexts.
Should you need the view to interface in a way with the view controller, the recommended way, and what Apple does across Cocoa is to use the delegate pattern.
An example of how to implement it follows:
#protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
#end
#interface MyView : UIView
#property (nonatomic, assign) MyViewDelegate delegate;
#end
#interface MyViewController < MyViewDelegate >
#end
The view interfaces with its delegate (as UITableView does, for instance) and it doesn't care if its implemented in the view controller or in any other class that you end up using.
My original answer follows: I don't recommend this, neither the rest of the answers where direct access to the view controller is achieved
There is no built-in way to do it. While you can get around it by adding a IBOutlet on the UIView and connecting these in Interface Builder, this is not recommended. The view should not know about the view controller. Instead, you should do as #Phil M suggests and create a protocol to be used as the delegate.
I would suggest a more lightweight approach for traversing the complete responder chain without having to add a category on UIView:
#implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
#end
Combining several already given answers, I'm shipping on it as well with my implementation:
#implementation UIView (AppNameAdditions)
- (UIViewController *)appName_viewController {
/// Finds the view's view controller.
// Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
Class vcc = [UIViewController class];
// Traverse responder chain. Return first found view controller, which will be the view's view controller.
UIResponder *responder = self;
while ((responder = [responder nextResponder]))
if ([responder isKindOfClass: vcc])
return (UIViewController *)responder;
// If the view controller isn't found, return nil.
return nil;
}
#end
The category is part of my ARC-enabled static library that I ship on every application I create. It's been tested several times and I didn't find any problems or leaks.
P.S.: You don't need to use a category like I did if the concerned view is a subclass of yours. In the latter case, just put the method in your subclass and you're good to go.
I modified de answer so I can pass any view, button, label etc. to get it's parent UIViewController. Here is my code.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Edit Swift 3 Version
class func viewController(_ view: UIView) -> UIViewController {
var responder: UIResponder? = view
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
Edit 2:- Swift Extention
extension UIView
{
//Get Parent View Controller from any view
func parentViewController() -> UIViewController {
var responder: UIResponder? = self
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
}
Even though this can technically be solved as pgb recommends, IMHO, this is a design flaw. The view should not need to be aware of the controller.
Don't forget that you can get access to the root view controller for the window that the view is a subview of. From there, if you are e.g. using a navigation view controller and want to push a new view onto it:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
You will need to set up the rootViewController property of the window properly first, however. Do this when you first create the controller e.g. in your app delegate:
-(void) applicationDidFinishLaunching:(UIApplication *)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RootViewController *controller = [[YourRootViewController] alloc] init];
[window setRootViewController: controller];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[controller release];
[window addSubview:[[self navigationController] view]];
[window makeKeyAndVisible];
}
I stumbled upon a situation where I have a small component I want to reuse, and added some code in a reusable view itself(it's really not much more than a button that opens a PopoverController).
While this works fine in the iPad (the UIPopoverController presents itself, therefor needs no reference to a UIViewController), getting the same code to work means suddenly referencing your presentViewController from your UIViewController. Kinda inconsistent right?
Like mentioned before, it's not the best approach to have logic in your UIView. But it felt really useless to wrap the few lines of code needed in a separate controller.
Either way, here's a swift solution, which adds a new property to any UIView:
extension UIView {
var viewController: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let responder = responder as? UIViewController {
return responder
}
responder = responder?.nextResponder()
}
return nil
}
}
While these answers are technically correct, including Ushox, I think the approved way is to implement a new protocol or re-use an existing one. A protocol insulates the observer from the observed, sort of like putting a mail slot in between them. In effect, that is what Gabriel does via the pushViewController method invocation; the view "knows" that it is proper protocol to politely ask your navigationController to push a view, since the viewController conforms to the navigationController protocol. While you can create your own protocol, just using Gabriel's example and re-using the UINavigationController protocol is just fine.
I don't think it's "bad" idea to find out who is the view controller for some cases. What could be a bad idea is to save the reference to this controller as it could change just as superviews change.
In my case I have a getter that traverses the responder chain.
//.h
#property (nonatomic, readonly) UIViewController * viewController;
//.m
- (UIViewController *)viewController
{
for (UIResponder * nextResponder = self.nextResponder;
nextResponder;
nextResponder = nextResponder.nextResponder)
{
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController *)nextResponder;
}
// Not found
NSLog(#"%# doesn't seem to have a viewController". self);
return nil;
}
Swift 4
(more concise than the other answers)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
My use case for which I need to access the view first UIViewController: I have an object that wraps around AVPlayer / AVPlayerViewController and I want to provide a simple show(in view: UIView) method that will embed AVPlayerViewController into view. For that, I need to access view's UIViewController.
Two solutions as of Swift 5.2:
More on the functional side
No need for the return keyword now 🤓
Solution 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Solution 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
This solution requires iterating through each responder first, so may not be the most performant.
The simplest do while loop for finding the viewController.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
This doesn't answer the question directly, but rather makes an assumption about the intent of the question.
If you have a view and in that view you need to call a method on another object, like say the view controller, you can use the NSNotificationCenter instead.
First create your notification string in a header file
#define SLCopyStringNotification #"ShaoloCopyStringNotification"
In your view call postNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Then in your view controller you add an observer. I do this in viewDidLoad
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
Now (also in the same view controller) implement your method copyString: as depicted in the #selector above.
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
I'm not saying this is the right way to do this, it just seems cleaner than running up the first responder chain. I used this code to implement a UIMenuController on a UITableView and pass the event back up to the UIViewController so I can do something with the data.
It's surely a bad idea and a wrong design, but I'm sure we can all enjoy a Swift solution of the best answer proposed by #Phil_M:
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.nextResponder() {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder)
}
If your intention is to do simple things, as showing a modal dialog or tracking data, that doesn't justify the use of a protocol. I personally store this function in an utility object, you can use it from anything that implement the UIResponder protocol as:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
All credit to #Phil_M
Maybe I'm late here. But in this situation I don't like category (pollution). I love this way:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Swiftier solution
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
Swift 4 version
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
Usage example
if let parent = self.view.parentViewController{
}
Updated version for swift 4 : Thanks for #Phil_M and #paul-slm
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.next {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(responder: nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder: responder)
}
To Phil's answer:
In line: id nextResponder = [self nextResponder]; if self(UIView) is not a subview of ViewController's view, if you know hierarchy of self(UIView) you can use also: id nextResponder = [[self superview] nextResponder];...
If you aren't going to upload this to the App Store, you can also use a private method of UIView.
#interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
#end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}
My solution would probably be considered kind of bogus but I had a similar situation as mayoneez (I wanted to switch views in response to a gesture in an EAGLView), and I got the EAGL's view controller this way:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
I think there is a case when the observed needs to inform the observer.
I see a similar problem where the UIView in a UIViewController is responding to a situation and it needs to first tell its parent view controller to hide the back button and then upon completion tell the parent view controller that it needs to pop itself off the stack.
I have been trying this with delegates with no success.
I don't understand why this should be a bad idea?
Another easy way is to have your own view class and add a property of the view controller in the view class. Usually the view controller creates the view and that is where the controller can set itself to the property. Basically it is instead of searching around (with a bit of hacking) for the controller, having the controller to set itself to the view - this is simple but makes sense because it is the controller that "controls" the view.
To get the controller of a given view, one can use UIFirstResponder chain.
customView.target(forAction: Selector("viewDidLoad"), withSender: nil)
If your rootViewController is UINavigationViewController, which was set up in AppDelegate class, then
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
Where c required view controllers class.
USAGE:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
There is no way.
What I do is pass the UIViewController pointer to the UIView (or an appropriate inheritance). I'm sorry I can't help with the IB approach to the problem because I don't believe in IB.
To answer the first commenter: sometimes you do need to know who called you because it determines what you can do. For example with a database you might have read access only or read/write ...

How to navigate through textfields (Next / Done Buttons)

How can I navigate through all my text fields with the "Next" Button on the iPhone Keyboard?
The last text field should close the Keyboard.
I've setup the IB the Buttons (Next / Done) but now I'm stuck.
I implemented the textFieldShouldReturn action but now the Next and Done Buttons close the Keyboard.
In Cocoa for Mac OS X, you have the next responder chain, where you can ask the text field what control should have focus next. This is what makes tabbing between text fields work. But since iOS devices do not have a keyboard, only touch, this concept has not survived the transition to Cocoa Touch.
This can be easily done anyway, with two assumptions:
All "tabbable" UITextFields are on the same parent view.
Their "tab-order" is defined by the tag property.
Assuming this you can override textFieldShouldReturn: as this:
-(BOOL)textFieldShouldReturn:(UITextField*)textField
{
NSInteger nextTag = textField.tag + 1;
// Try to find next responder
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (nextResponder) {
// Found next responder, so set it.
[nextResponder becomeFirstResponder];
} else {
// Not found, so remove keyboard.
[textField resignFirstResponder];
}
return NO; // We do not want UITextField to insert line-breaks.
}
Add some more code, and the assumptions can be ignored as well.
Swift 4.0
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
// Try to find next responder
let nextResponder = textField.superview?.viewWithTag(nextTag) as UIResponder!
if nextResponder != nil {
// Found next responder, so set it
nextResponder?.becomeFirstResponder()
} else {
// Not found, so remove keyboard
textField.resignFirstResponder()
}
return false
}
If the superview of the text field will be a UITableViewCell then next responder will be
let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!
There is a much more elegant solution which blew me away the first time I saw it. Benefits:
Closer to OSX textfield implementation where a textfield knows where the focus should go next
Does not rely on setting or using tags -- which are, IMO fragile for this use case
Can be extended to work with both UITextField and UITextView controls -- or any keyboard entry UI control
Doesn't clutter your view controller with boilerplate UITextField delegate code
Integrates nicely with IB and can be configured through the familiar option-drag-drop to connect outlets.
Create a UITextField subclass which has an IBOutlet property called nextField. Here's the header:
#interface SOTextField : UITextField
#property (weak, nonatomic) IBOutlet UITextField *nextField;
#end
And here's the implementation:
#implementation SOTextField
#end
In your view controller, you'll create the -textFieldShouldReturn: delegate method:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ([textField isKindOfClass:[SOTextField class]]) {
UITextField *nextField = [(SOTextField *)textField nextField];
if (nextField) {
dispatch_async(dispatch_get_current_queue(), ^{
[nextField becomeFirstResponder];
});
}
else {
[textField resignFirstResponder];
}
}
return YES;
}
In IB, change your UITextFields to use the SOTextField class. Next, also in IB, set the delegate for each of the 'SOTextFields'to 'File's Owner' (which is right where you put the code for the delegate method - textFieldShouldReturn). The beauty of this design is that now you can simply right-click on any textField and assign the nextField outlet to the next SOTextField object you want to be the next responder.
Moreover, you can do cool things like loop the textFields so that after the last one loses focus, the first one will receive focus again.
This can easily be extended to automatically assign the returnKeyType of the SOTextField to a UIReturnKeyNext if there is a nextField assigned -- one less thing manually configure.
Here's one without delegation:
tf1.addTarget(tf2, action: #selector(becomeFirstResponder), for: .editingDidEndOnExit)
tf2.addTarget(tf3, action: #selector(becomeFirstResponder), for: .editingDidEndOnExit)
ObjC:
[tf1 addTarget:tf2 action:#selector(becomeFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit];
[tf2 addTarget:tf3 action:#selector(becomeFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit];
Works using the (mostly unknown) UIControlEventEditingDidEndOnExit UITextField action.
You can also easily hook this up in the storyboard, so no delegation or code is required.
Edit: actually I cannot figure out how to hook this up in storyboard. becomeFirstResponder does not seem to be a offered action for this control-event, which is a pity. Still, you can hook all your textfields up to a single action in your ViewController which then determines which textField to becomeFirstResponder based on the sender (though then it is not as elegant as the above programmatic solution so IMO do it with the above code in viewDidLoad).
Here is my solution for this problem.
To solve this (and because I hate relying on tags to do stuff) I decided to add a custom property to the UITextField object. In other words I created a category on UITextField like this :
UITextField+Extended.h
#interface UITextField (Extended)
#property(retain, nonatomic)UITextField* nextTextField;
#end
UITextField+Extended.m
#import "UITextField+Extended.h"
#import <objc/runtime.h>
static char defaultHashKey;
#implementation UITextField (Extended)
- (UITextField*) nextTextField {
return objc_getAssociatedObject(self, &defaultHashKey);
}
- (void) setNextTextField:(UITextField *)nextTextField{
objc_setAssociatedObject(self, &defaultHashKey, nextTextField, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
Now, here is how I use it :
UITextField *textField1 = ...init your textfield
UITextField *textField2 = ...init your textfield
UITextField *textField3 = ...init your textfield
textField1.nextTextField = textField2;
textField2.nextTextField = textField3;
textField3.nextTextField = nil;
And implement the textFieldShouldReturn method :
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
UITextField *next = theTextField.nextTextField;
if (next) {
[next becomeFirstResponder];
} else {
[theTextField resignFirstResponder];
}
return NO;
}
I now have kind of a linked list of UITextField, each one knowing who's next in the line.
Hope it'll help.
A swift extension that applies mxcl's answer to make this particularly easy (adapted to swift 2.3 by Traveler):
extension UITextField {
class func connectFields(fields:[UITextField]) -> Void {
guard let last = fields.last else {
return
}
for i in 0 ..< fields.count - 1 {
fields[i].returnKeyType = .Next
fields[i].addTarget(fields[i+1], action: "becomeFirstResponder", forControlEvents: .EditingDidEndOnExit)
}
last.returnKeyType = .Done
last.addTarget(last, action: #selector(UIResponder.resignFirstResponder), forControlEvents: .EditingDidEndOnExit)
}
}
It's easy to use:
UITextField.connectFields([field1, field2, field3])
The extension will set the return button to "Next" for all but the last field and to "Done" for the last field, and shift focus / dismiss the keyboard when these are tapped.
Swift < 2.3
extension UITextField {
class func connectFields(fields:[UITextField]) -> Void {
guard let last = fields.last else {
return
}
for var i = 0; i < fields.count - 1; i += 1 {
fields[i].returnKeyType = .Next
fields[i].addTarget(fields[i+1], action: "becomeFirstResponder", forControlEvents: .EditingDidEndOnExit)
}
last.returnKeyType = .Done
last.addTarget(last, action: "resignFirstResponder", forControlEvents: .EditingDidEndOnExit)
}
}
SWIFT 3:
use like this -
UITextField.connectFields(fields: [field1, field2])
Extension:
extension UITextField {
class func connectFields(fields:[UITextField]) -> Void {
guard let last = fields.last else {
return
}
for i in 0 ..< fields.count - 1 {
fields[i].returnKeyType = .next
fields[i].addTarget(fields[i+1], action: #selector(UIResponder.becomeFirstResponder), for: .editingDidEndOnExit)
}
last.returnKeyType = .go
last.addTarget(last, action: #selector(UIResponder.resignFirstResponder), for: .editingDidEndOnExit)
}
}
A more consistent and robust way is to use NextResponderTextField
You can configure it totally from interface builder with no need for setting the delegate or using view.tag.
All you need to do is
Set the class type of your UITextField to be NextResponderTextField
Then set the outlet of the nextResponderField to point to the next responder it can be anything UITextField or any UIResponder subclass. It can be also a UIButton and the library is smart enough to trigger the TouchUpInside event of the button only if it's enabled.
Here is the library in action:
I like the OO solutions that have already been suggested by Anth0 and Answerbot. However, I was working on a quick and small POC, so I didn't want to clutter things with subclasses and categories.
Another simple solution is to create an NSArray of fields and lookup the next field when you press next. Not an OO solution, but quick, simple, and easy to implement. Also, you can see and modify the ordering at a glance.
Here's my code (built upon other answers in this thread):
#property (nonatomic) NSArray *fieldArray;
- (void)viewDidLoad {
[super viewDidLoad];
fieldArray = [NSArray arrayWithObjects: firstField, secondField, thirdField, nil];
}
- (BOOL) textFieldShouldReturn:(UITextField *) textField {
BOOL didResign = [textField resignFirstResponder];
if (!didResign) return NO;
NSUInteger index = [self.fieldArray indexOfObject:textField];
if (index == NSNotFound || index + 1 == fieldArray.count) return NO;
id nextField = [fieldArray objectAtIndex:index + 1];
activeField = nextField;
[nextField becomeFirstResponder];
return NO;
}
I always return NO because I don't want a line break inserted. Just thought I'd point that out since when I returned YES it would automatically exit the subsequent fields or insert a line break in my TextView. It took me a bit of time to figure that out.
activeField keeps track of the active field in case scrolling is necessary to unobscure the field from the keyboard. If you have similar code, make sure you assign the activeField before changing the first responder. Changing first responder is immediate and will fire the KeyboardWasShown event immediately.
Here is an implementation of tabbing using a category on UIControl. This solution has all of the advantages of the methods from Michael and Anth0, but works for all UIControls, not just UITextFields. It also works seamlessly with Interface Builder and storyboards.
Source and sample app: GitHub repository for UIControlsWithTabbing
Usage:
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField transferFirstResponderToNextControl];
return NO;
}
Header:
//
// UIControl+NextControl.h
// UIControlsWithTabbing
//
#import <UIKit/UIKit.h>
#interface UIControl (NextControl)
#property (nonatomic, weak) IBOutlet UIControl *nextControl;
- (BOOL)transferFirstResponderToNextControl;
#end
Implementation:
#import "UIControl+NextControl.h"
#import <objc/runtime.h>
static char defaultHashKey;
#implementation UIControl (NextControl)
- (UIControl *)nextControl
{
return objc_getAssociatedObject(self, &defaultHashKey);
}
- (void)setNextControl:(UIControl *)nextControl
{
objc_setAssociatedObject(self, &defaultHashKey, nextControl, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)transferFirstResponderToNextControl
{
if (self.nextControl)
{
[self.nextControl becomeFirstResponder];
return YES;
}
[self resignFirstResponder];
return NO;
}
#end
I have tried many codes and finally, this worked for me in Swift 3.0 Latest [March 2017]
The ViewController class should be inherited the UITextFieldDelegate for making this code working.
class ViewController: UIViewController,UITextFieldDelegate
Add the Text field with the Proper Tag number and this tag number is used to take the control to appropriate text field based on incremental tag number assigned to it.
override func viewDidLoad() {
userNameTextField.delegate = self
userNameTextField.tag = 0
userNameTextField.returnKeyType = UIReturnKeyType.next
passwordTextField.delegate = self
passwordTextField.tag = 1
passwordTextField.returnKeyType = UIReturnKeyType.go
}
In the above code, the returnKeyType = UIReturnKeyType.next where will make the Key pad return key to display as Next you also have other options as Join/Go etc, based on your application change the values.
This textFieldShouldReturn is a method of UITextFieldDelegate controlled and here we have next field selection based on the Tag value incrementation
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
return true;
}
return false
}
After you exit from one text field, you call [otherTextField becomeFirstResponder] and the next field gets focus.
This can actually be a tricky problem to deal with since often you'll also want to scroll the screen or otherwise adjust the position of the text field so it's easy to see when editing. Just make sure to do a lot of testing with coming into and out of the text fields in different ways and also leaving early (always give the user an option to dismiss the keyboard instead of going to the next field, usually with "Done" in the nav bar)
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
return YES;
}
I am surprised by how many answers here fail to understand one simple concept: navigating through controls in your app is not something the views themselves should do. It's the controller's job to decide which control to make the next first responder.
Also most answers only applied to navigating forward, but users may also want to go backwards.
So here's what I've come up with. Your form should be managed by a view controller, and view controllers are part of the responder chain. So you're perfectly free to implement the following methods:
#pragma mark - Key Commands
- (NSArray *)keyCommands
{
static NSArray *commands;
static dispatch_once_t once;
dispatch_once(&once, ^{
UIKeyCommand *const forward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:0 action:#selector(tabForward:)];
UIKeyCommand *const backward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:UIKeyModifierShift action:#selector(tabBackward:)];
commands = #[forward, backward];
});
return commands;
}
- (void)tabForward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.firstObject becomeFirstResponder];
}
- (void)tabBackward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls.reverseObjectEnumerator) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.lastObject becomeFirstResponder];
}
Additional logic for scrolling offscreen responders visible beforehand may apply.
Another advantage of this approach is that you don't need to subclass all kinds of controls you may want to display (like UITextFields) but can instead manage the logic at controller level, where, let's be honest, is the right place to do so.
A very easy method for dismissing the keyboard when the 'Done' button is pressed is:
Create a new IBAction in the header
- (IBAction)textFieldDoneEditing:(id)sender;
In the implementation file (.m file) add the following method:
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
}
Then, when you come to link the IBAction to the textfield - link to the 'Did End On Exit' event.
First set keyboard return key in xib, otherwise you can write code in viewdidload:
passWord.returnKeyType = UIReturnKeyNext;
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
if(textField == eMail) {
[textField resignFirstResponder];
[userName becomeFirstResponder];
}
if (textField==userName) {
[textField resignFirstResponder];
[passWord becomeFirstResponder];
}
if (textField==passWord) {
[textField resignFirstResponder];
[country becomeFirstResponder];
}
if (textField==country) {
[textField resignFirstResponder];
}
return YES;
}
If someone wants like this. I think this is the closest to the requirements asked for in question
Here is how I have implemented this one
Add accessory view for each text field for which you want the setup, using
func setAccessoryViewFor(textField : UITextField) {
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
toolBar.sizeToFit()
// Adds the buttons
// Add previousButton
let prevButton = UIBarButtonItem(title: "<", style: .plain, target: self, action: #selector(previousPressed(sender:)))
prevButton.tag = textField.tag
if getPreviousResponderFor(tag: textField.tag) == nil {
prevButton.isEnabled = false
}
// Add nextButton
let nextButton = UIBarButtonItem(title: ">", style: .plain, target: self, action: #selector(nextPressed(sender:)))
nextButton.tag = textField.tag
if getNextResponderFor(tag: textField.tag) == nil {
nextButton.title = "Done"
}
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
toolBar.setItems([prevButton,spaceButton,nextButton], animated: false)
toolBar.isUserInteractionEnabled = true
textField.inputAccessoryView = toolBar
}
Use following functions to handle taps
func nextPressed(sender : UIBarButtonItem) {
if let nextResponder = getNextResponderFor(tag: sender.tag) {
nextResponder.becomeFirstResponder()
} else {
self.view.endEditing(true)
}
}
func previousPressed(sender : UIBarButtonItem) {
if let previousResponder = getPreviousResponderFor(tag : sender.tag) {
previousResponder.becomeFirstResponder()
}
}
func getNextResponderFor(tag : Int) -> UITextField? {
return self.view.viewWithTag(tag + 1) as? UITextField
}
func getPreviousResponderFor(tag : Int) -> UITextField? {
return self.view.viewWithTag(tag - 1) as? UITextField
}
You will need to give the textFields tags in sequence in which you want the next/prev button to respond.
Solution in Swift 3.1, After connecting your textfields IBOutlets set your textfields delegate in viewDidLoad, And then navigate your action in textFieldShouldReturn
class YourViewController: UIViewController,UITextFieldDelegate {
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var phoneTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.passwordTextField.delegate = self
self.phoneTextField.delegate = self
// Set your return type
self.phoneTextField.returnKeyType = .next
self.passwordTextField.returnKeyType = .done
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool{
if textField == self.phoneTextField {
self.passwordTextField.becomeFirstResponder()
}else if textField == self.passwordTextField{
// Call login api
self.login()
}
return true
}
}
I have added to PeyloW's answer in case you're looking to implement a previous/next button functionality:
- (IBAction)moveThroughTextFields:(UIBarButtonItem *)sender
{
NSInteger nextTag;
UITextView *currentTextField = [self.view findFirstResponderAndReturn];
if (currentTextField != nil) {
// I assigned tags to the buttons. 0 represent prev & 1 represents next
if (sender.tag == 0) {
nextTag = currentTextField.tag - 1;
} else if (sender.tag == 1) {
nextTag = currentTextField.tag + 1;
}
}
// Try to find next responder
UIResponder* nextResponder = [self.view viewWithTag:nextTag];
if (nextResponder) {
// Found next responder, so set it.
// I added the resign here in case there's different keyboards in place.
[currentTextField resignFirstResponder];
[nextResponder becomeFirstResponder];
} else {
// Not found, so remove keyboard.
[currentTextField resignFirstResponder];
}
}
Where you subclass the UIView like this:
#implementation UIView (FindAndReturnFirstResponder)
- (UITextView *)findFirstResponderAndReturn
{
for (UITextView *subView in self.subviews) {
if (subView.isFirstResponder){
return subView;
}
}
return nil;
}
#end
Hi to everyone please see this one
- (void)nextPrevious:(id)sender
{
UIView *responder = [self.view findFirstResponder];
if (nil == responder || ![responder isKindOfClass:[GroupTextField class]]) {
return;
}
switch([(UISegmentedControl *)sender selectedSegmentIndex]) {
case 0:
// previous
if (nil != ((GroupTextField *)responder).previousControl) {
[((GroupTextField *)responder).previousControl becomeFirstResponder];
DebugLog(#"currentControl: %i previousControl: %i",((GroupTextField *)responder).tag,((GroupTextField *)responder).previousControl.tag);
}
break;
case 1:
// next
if (nil != ((GroupTextField *)responder).nextControl) {
[((GroupTextField *)responder).nextControl becomeFirstResponder];
DebugLog(#"currentControl: %i nextControl: %i",((GroupTextField *)responder).tag,((GroupTextField *)responder).nextControl.tag);
}
break;
}
}
I tried to solve this problem using a more sophisticated approach based on assigning each cell (or UITextField) in a UITableView a unique tag value that can be later retrieved:
activate-next-uitextfield-in-uitableview-ios
I hope this helps!
I've just created new Pod when dealing with this stuff GNTextFieldsCollectionManager. It automatically handles next/last textField problem and is very easy to use:
[[GNTextFieldsCollectionManager alloc] initWithView:self.view];
Grabs all textfields sorted by appearing in view hierarchy (or by tags), or you can specify your own array of textFields.
A safer and more direct way, assuming:
the text field delegates are set to your view controller
all of the text fields are subviews of the same view
the text fields have tags in the order you want to progress (e.g., textField2.tag = 2, textField3.tag = 3, etc.)
moving to the next text field will happen when you tap the return button on the keyboard (you can change this to next, done, etc.)
you want the keyboard to dismiss after the last text field
Swift 4.1:
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
guard let nextTextField = textField.superview?.viewWithTag(nextTag) else {
textField.resignFirstResponder()
return false
}
nextTextField.becomeFirstResponder()
return false
}
}
I rather prefer to:
#interface MyViewController : UIViewController
#property (nonatomic, retain) IBOutletCollection(UIView) NSArray *inputFields;
#end
In the NIB file I hook the textFields in the desired order into this inputFields array. After that I do a simple test for the index of the UITextField that reports that the user tapped return:
// for UITextField
-(BOOL)textFieldShouldReturn:(UITextField*)textField {
NSUInteger index = [_inputFields indexOfObject:textField];
index++;
if (index < _inputFields.count) {
UIView *v = [_inputFields objectAtIndex:index];
[v becomeFirstResponder];
}
return NO;
}
// for UITextView
-(BOOL)textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {
if ([#"\n" isEqualToString:text]) {
NSUInteger index = [_inputFields indexOfObject:textView];
index++;
if (index < _inputFields.count) {
UIView *v = [_inputFields objectAtIndex:index];
[v becomeFirstResponder];
} else {
[self.view endEditing:YES];
}
return NO;
}
return YES;
}
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
txt_Input = [[ UITextField alloc] initWithFrame:CGRectMake(0, 10, 150, 30)];
txt_Input.tag = indexPath.row+1;
[self.array_Textfields addObject:txt_Input]; // Initialize mutable array in ViewDidLoad
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
int tag = ( int) textField.tag ;
UITextField * txt = [ self.array_Textfields objectAtIndex:tag ] ;
[ txt becomeFirstResponder] ;
return YES ;
}
I had about 10+ UITextField in my story board and the way I enabled next functionality was by creating an array of UITextField and making the next UITextField the firstResponder. Here's the implementation file:
#import "RegistrationTableViewController.h"
#interface RegistrationTableViewController ()
#property (weak, nonatomic) IBOutlet UITextField *fullNameTextField;
#property (weak, nonatomic) IBOutlet UITextField *addressTextField;
#property (weak, nonatomic) IBOutlet UITextField *address2TextField;
#property (weak, nonatomic) IBOutlet UITextField *cityTextField;
#property (weak, nonatomic) IBOutlet UITextField *zipCodeTextField;
#property (weak, nonatomic) IBOutlet UITextField *urlTextField;
#property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
#property (weak, nonatomic) IBOutlet UITextField *emailTextField;
#property (weak, nonatomic) IBOutlet UITextField *passwordTextField;
#property (weak, nonatomic) IBOutlet UITextField *confirmPWTextField;
#end
NSArray *uiTextFieldArray;
#implementation RegistrationTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"view did load");
uiTextFieldArray = #[self.fullNameTextField,self.addressTextField,self.address2TextField,self.cityTextField,self.zipCodeTextField,self.urlTextField,self.usernameTextField,self.emailTextField,self.passwordTextField,self.confirmPWTextField];
for(UITextField *myField in uiTextFieldArray){
myField.delegate = self;
}
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
long index = [uiTextFieldArray indexOfObject:textField];
NSLog(#"%ld",index);
if(index < (uiTextFieldArray.count - 1)){
[uiTextFieldArray[++index] becomeFirstResponder];
}else{
[uiTextFieldArray[index] resignFirstResponder];
}
return YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
This worked for me in Xamarin.iOS / Monotouch.
Change the keyboard button to Next, pass the control to the next UITextField and hide the keyboard after the last UITextField.
private void SetShouldReturnDelegates(IEnumerable<UIView> subViewsToScout )
{
foreach (var item in subViewsToScout.Where(item => item.GetType() == typeof (UITextField)))
{
(item as UITextField).ReturnKeyType = UIReturnKeyType.Next;
(item as UITextField).ShouldReturn += (textField) =>
{
nint nextTag = textField.Tag + 1;
var nextResponder = textField.Superview.ViewWithTag(nextTag);
if (null != nextResponder)
nextResponder.BecomeFirstResponder();
else
textField.Superview.EndEditing(true);
//You could also use textField.ResignFirstResponder();
return false; // We do not want UITextField to insert line-breaks.
};
}
}
Inside the ViewDidLoad you'll have:
If your TextFields haven't a Tag set it now:
txtField1.Tag = 0;
txtField2.Tag = 1;
txtField3.Tag = 2;
//...
and just the call
SetShouldReturnDelegates(yourViewWithTxtFields.Subviews.ToList());
//If you are not sure of which view contains your fields you can also call it in a safer way:
SetShouldReturnDelegates(txtField1.Superview.Subviews.ToList());
//You can also reuse the same method with different containerViews in case your UITextField are under different views.
This is a simple solution in swift, with no tag using, no storyboard tricks...
Just use this extension :
extension UITextField{
func nextTextFieldField() -> UITextField?{
//field to return
var returnField : UITextField?
if self.superview != nil{
//for each view in superview
for (_, view) in self.superview!.subviews.enumerate(){
//if subview is a text's field
if view.isKindOfClass(UITextField){
//cast curent view as text field
let currentTextField = view as! UITextField
//if text field is after the current one
if currentTextField.frame.origin.y > self.frame.origin.y{
//if there is no text field to return already
if returnField == nil {
//set as default return
returnField = currentTextField
}
//else if this this less far than the other
else if currentTextField.frame.origin.y < returnField!.frame.origin.y{
//this is the field to return
returnField = currentTextField
}
}
}
}
}
//end of the mdethod
return returnField
}
}
And call it like this (for example) with your textfield delegate:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
textField.nextTextFieldField()?.becomeFirstResponder()
return true
}
Here is a Swift 3 version of Anth0's answer. I'm posting it here to help any swift developers in wanting to take advantage of his great answer! I took the liberty of adding a return key type of "Next" when you set the associated object.
extension UITextField {
#nonobjc static var NextHashKey: UniChar = 0
var nextTextField: UITextField? {
get {
return objc_getAssociatedObject(self,
&UITextField.NextHashKey) as? UITextField
}
set(next) {
self.returnKeyType = UIReturnKeyType.next
objc_setAssociatedObject(self,
&UITextField.NextHashKey,next,.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Here is another extension that shows a possibility of using the above code to cycle through a list of UITextFields.
extension UIViewController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard let next = textField.nextTextField else {
textField.resignFirstResponder()
return true
}
next.becomeFirstResponder()
return false
}
}
And then in your ViewController or wherever, you can setup your textfields like so...
#IBOutlet fileprivate weak var textfield1: UITextField!
#IBOutlet fileprivate weak var textfield2: UITextField!
#IBOutlet fileprivate weak var textfield3: UITextField!
...
[textfield1, textfield2, textfield3].forEach{ $0?.delegate = self }
textfield1.nextTextField = textfield2
textfield2.nextTextField = textfield3
// We don't assign a nextTextField to textfield3 because we want
// textfield3 to be the last one and resignFirstResponder when
// the return button on the soft keyboard is tapped.
in textFieldShouldReturn you should check that the textfield you are currently on is not the last one when they click next and if its n ot dont dismiss the keyboard..
This is an old post, but has a high page rank so I'll chime in with my solution.
I had a similar issue and ended up creating a subclass of UIToolbar to manage the next/previous/done functionality in a dynamic tableView with sections: https://github.com/jday001/DataEntryToolbar
You set the toolbar as inputAccessoryView of your text fields and add them to its dictionary. This allows you to cycle through them forwards and backwards, even with dynamic content. There are delegate methods if you want to trigger your own functionality when textField navigation happens, but you don't have to deal with managing any tags or first responder status.
There are code snippets & an example app at the GitHub link to help with the implementation details. You will need your own data model to keep track of the values inside the fields.
Without usings tags and without adding a property for nextField/nextTextField, you can try this to emulate TAB, where "testInput" is your current active field:
if ([textInput isFirstResponder])
[textInput.superview.subviews enumerateObjectsAtIndexes:
[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange([textInput.superview.subviews indexOfObject:textInput]+1,
[textInput.superview.subviews count]-[textInput.superview.subviews indexOfObject:textInput]-1)]
options:0 usingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
*stop = !obj.hidden && [obj becomeFirstResponder];
}];
if ([textInput isFirstResponder])
[textInput.superview.subviews enumerateObjectsAtIndexes:
[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(0,
[textInput.superview.subviews indexOfObject:textInput])]
options:0 usingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
*stop = !obj.hidden && [obj becomeFirstResponder];
}];

Resources