Metin alanlarında gezinme (Sonraki / Bitti Düğmeleri)


487

İPhone Klavyesindeki "İleri" Düğmesiyle tüm metin alanlarımda nasıl gezinebilirim?

Son metin alanı Klavyeyi kapatmalıdır.

Düğmeleri IB ayarladım (Next / Done) ama şimdi sıkıştım.

TextFieldShouldReturn eylemini uyguladım, ancak şimdi İleri ve Bitti Düğmeleri Klavyeyi kapatıyor.


Lütfen aşağıdaki cevabıma bir göz atın. Deneyimlerime göre, tipik olarak verilen cevaptan daha iyi, daha eksiksiz bir çözümdür. Neden herkesin olumsuz bir puan vereceğini bilmiyorum.
memmons

Ben benzer bir sorun var ama Cordova, Phonegap ile çözmek için herhangi bir yolu var mı?

Yanıtlar:


578

Mac OS X için Cocoa'da, metin alanına daha sonra hangi kontrolün odaklanması gerektiğini sorabileceğiniz bir sonraki yanıtlayıcı zinciri vardır. Metin alanları arasında sekme yapılmasını sağlayan şey budur. Ancak iOS cihazlarının bir klavyesi olmadığından, sadece dokunma olduğundan, bu konsept Cocoa Touch'a geçişten kurtuldu.

Bu, iki varsayımla yine de kolayca yapılabilir:

  1. Tüm "sekilebilir" UITextFieldler aynı üst görünümdedir.
  2. Onların "sekme sırası" tag özelliği tarafından tanımlanır.

Bunu varsayarak textFieldShouldReturn: 'u geçersiz kılabilirsiniz:

-(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.
}

Biraz daha kod ekleyin, varsayımlar da göz ardı edilebilir.

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
}

Metin alanının denetimi UITableViewCell olacaksa, bir sonraki yanıtlayıcı

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!

6
Bazen klavyenin üstünde "sonraki" ve "önceki" düğmeleri bulunur. En azından tarayıcıda.
Tim Büthe

31
Sadece bilgiçlikçi olmak için, bu yazıda bazı yanlış bilgileri silmek istiyorum. OS X'te sekme sırası, NSView'ın setNextKeyView: (setNextResponder :) yöntemi ile ayarlanır. Cevaplayıcı zinciri bundan ayrıdır: "hedeflenmemiş" eylemleri işleyen bir yanıtlayıcı nesneleri hiyerarşisidir, örneğin doğrudan bir denetleyici nesnesi yerine ilk yanıtlayıcıya gönderilen eylemler. Yanıtlayıcı zinciri genellikle pencereye ve denetleyicisine kadar uygulama hiyerarşisini ve ardından uygulama temsilcisini izler. Google, çok fazla bilgi için "yanıtlayıcı zinciri".
davehayden

Metin alanının denetimi a UITableViewCell. İlişkili öğeye erişmeniz ve UITableViewaradığınız metin alanını içeren hücreyi almanız gerekir .
Michael Mior

13
UITextFieldDelegateMetin alanı delegelerini eklemeyi ve ayarlamayı unutmayın .
Sal

1
Film şeridinden TAG seçeneğini ayarlamak için kolay hatırlatma (benim durumumda). TextFields etiketini artan sırada manuel olarak ayarlamanız gerekir. (Birincisi 0, ikincisi 1 vb.) Bu çözümün çalışması için.
Joakim

170

Bir Orada çok onu ilk gördüğümde bana uzak patladı daha zarif bir çözüm. Yararları:

  • Bir metin alanının odak noktasının nereye gitmesi gerektiğini bildiği OSX metin alanı uygulamasına daha yakın
  • Bu kullanım durumu için IMO kırılgan olan etiketlerin ayarlanmasına veya kullanılmasına güvenmez
  • Hem işin uzatılabilir UITextFieldve UITextViewya herhangi bir klavye girişi arayüzü denetimi - kontrollere
  • Görünüm denetleyicinizi kaynak plakası UITextField delege kodu ile karıştırmaz
  • IB ile güzel bir şekilde bütünleşir ve çıkışları bağlamak için tanıdık sürükle-bırak yöntemiyle yapılandırılabilir.

IBOutletNextField adlı bir özelliğe sahip bir UITextField alt sınıfı oluşturun . İşte başlık:

@interface SOTextField : UITextField

@property (weak, nonatomic) IBOutlet UITextField *nextField; 

@end

Ve işte uygulama:

@implementation SOTextField

@end

Görünüm denetleyicinizde -textFieldShouldReturn:temsilci yöntemini oluşturacaksınız :

- (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;
}

IB'de, SOTextFieldsınıfı kullanmak için UITextField'lerinizi değiştirin . Daha sonra, ayrıca IB'de, 'SOTextFields' öğelerinin her biri için temsilci ayarını 'File's Owner' (tam olarak temsilci yöntemi - textFieldShouldReturn için kod koyduğunuz yer) olarak ayarlayın. Bu tasarımın güzelliği, artık herhangi bir textField öğesine sağ tıklayıp SOTextFieldsonraki yanıtlayıcı olmak istediğiniz bir sonraki nesneye nextField çıkışını atayabilmenizdir .

IB'de nextField atama

Dahası, textField öğelerini döngü gibi havalı şeyler yapabilirsiniz, böylece sonuncusu odağı kaybettikten sonra, birincisi tekrar odaklanır.

Bu kolayca otomatik atamak için uzatılabilir returnKeyTypeait SOTextFieldbir karşı UIReturnKeyNexten küçük bir şey elle yapılandırmak - Bir nextField atanmış olup olmadığını.


3
Etiketleri kullanma konusunda IB entegrasyonunu gerçekten çok seviyorum - özellikle IB'yi IDE'nin geri kalanıyla güzel bir şekilde bütünleştiren Xcode'un daha yeni sürümleri ile - ve denetleyicimi ortak metinli metin ile karıştırmak zorunda değilsinizField delege kodu bir bonus.
16'da memmons

4
Etiketler daha kolay, ancak bu şekilde daha iyi ve iyi bir OO tasarımı için daha uygun
Brain2000

1
Bu çözümü gerçekten seviyorum, ancak her metin alanını ayrı hücre görünümlerinde bir UITableView. Her IBOutletmetin alanı için görünüm denetleyicisinde özellikleri olmasına rağmen , bu nextFieldözelliği Dosya Sahibindeki bu özelliklere bağlamama izin vermiyor . Bu senaryoda çalışmasını nasıl sağlayabilirim?
Jay Imerman

1
@JayImerman IB, hücreler arasındaki çıkışları bağlamanıza izin vermez. Yani, IB bu senaryo için bir hareket olmayacak. Ancak, nextField özelliklerini kod kullanarak bağlayabilirsiniz. textField1.nextField = textField2; textField2.nextField = textField3;vb.
16'da hatırlar

2
Bu cevabın daha yeni xcode ve ios sürümleri için güncellenmesi ihtimali var mı?
lostintranslation

82

İşte temsilci olmayan biri:

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];

(Çoğunlukla bilinmeyen) UIControlEventEditingDidEndOnExit UITextFieldeylemi kullanarak çalışır .

Bunu kolayca film şeridine de bağlayabilirsiniz, böylece bir temsilci seçme veya kod gerekmez.

Düzenleme: Aslında bunu storyboard'a nasıl bağlayacağımı anlayamıyorum. becomeFirstResponderüzücü olan bu kontrol olayı için önerilen bir eylem gibi görünmüyor. Yine de, tüm metin alanlarınızı ViewController'ınızda tek bir eyleme bağlayabilirsiniz becomeFirstResponder, bu da göndericiye göre hangi textField öğesinin kullanılacağını belirler (o zaman yukarıdaki programatik çözüm kadar zarif değildir, bu nedenle IMO yukarıdaki kodla bunu yapar viewDidLoad).


18
Çok basit ve etkili bir çözüm. Zincirdeki sonuncusu bile tetikleyebilir örn [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];. Teşekkürler mxcl.
msrdjan

Bunu film şeridinde nasıl yapardınız?
Pedro Borges

Bu muhtemelen en iyi cevap.
daspianist

Swift 2'de bile bu benim için mükemmel bir şekilde çalıştı.
ton

2
Swift 4: txtFirstname.addTarget (txtLastname, eylem: #selector (becameFirstResponder), için: UIControlEvents.editingDidEndOnExit)
realtimez

78

İşte bu sorunun çözümü.

Bunu çözmek için (ve bir şeyler yapmak için etiketlere güvenmekten nefret ettiğimden) UITextField nesnesine özel bir özellik eklemeye karar verdim. Başka bir deyişle, UITextField üzerinde böyle bir kategori oluşturdum:

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

Şimdi, bunu nasıl kullanırım:

UITextField *textField1 = ...init your textfield
UITextField *textField2 = ...init your textfield
UITextField *textField3 = ...init your textfield

textField1.nextTextField = textField2;
textField2.nextTextField = textField3;
textField3.nextTextField = nil;

Ve textFieldShouldReturn yöntemini uygulayın:

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {

    UITextField *next = theTextField.nextTextField;
    if (next) {
        [next becomeFirstResponder];
    } else {
        [theTextField resignFirstResponder];
    }

    return NO; 
}

Şimdi UITextField, her biri sırada kim olduğunu bilmek bağlantılı bir listesi var.

Umarım yardımcı olur.


11
Bu en iyi çözümdür ve cevap olarak seçilmelidir.
Giovanni

4
Bu çözüm benim için çalıştı, değiştirdiğim tek şey nextTextField ve IBOutlet'i yapmaktı, böylece Storyboard'umdan zincirleme kurabiliyordum.
zachzurn

En sevdiğim cevapları kaydedebilseydim, bu onlardan biri olurdu. Temiz çözüm için teşekkürler!
Matt Becker

1
IB size aitse, bu özellikleri IBOutlets olarak da ayarlayabileceğinizi unutmayın. . isim çarpışmalarına dikkat edin - Apple bunu sonunda UIResponder'a ekleyebilir.
Jasper Blues

Bu çözüm arayüz oluşturucu ile uyumlu mu? Yani nextTextField programlı ayarlamak yerine IBOutlets ve film şeridi referansları kullanabilir miyim? IB, UITextField sınıfını bir kategoriye ayarlamanıza izin vermiyor gibi görünüyor.
Pedro Borges

46

Bunu özellikle kolaylaştırmak için mxcl'nin cevabını uygulayan hızlı bir uzantı (Traveler tarafından hızlı 2.3'e uyarlanmıştır):

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)
    }
}

Kullanımı kolaydır:

UITextField.connectFields([field1, field2, field3])

Uzantı, dönüş düğmesini son alan dışındaki herkes için "İleri" ve son alan için "Bitti" olarak ayarlayacak ve dokunulduğunda klavyeyi kaydırıp odaklamayı bırakacaktır.

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: şu şekilde kullanın -

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)
        }
    }

5
BTW, Done'un bir eylem gerçekleştirmesini istiyorsanız, View Controller'ınızın uyum sağlamasını UITextFieldDelegateve uygulamasını sağlayabilirsiniz func textFieldDidEndEditing(textField: UITextField). Sadece erken dön textField != field3.
Ryan

25

Daha tutarlı ve sağlam bir yol NextResponderTextField kullanmaktır Temsilciyi ayarlamaya veya kullanmaya gerek kalmadan tamamen arayüz oluşturucudan yapılandırabilirsiniz view.tag.

Tek yapman gereken

  1. Aramalarınızdan sınıf türünü ayarlayın UITextFieldolmakNextResponderTextField resim açıklamasını buraya girin
  2. Sonra 'nın çıkışını bir nextResponderFieldsonraki yanıtlayıcıya işaret edecek şekilde ayarlayın, UITextFieldherhangi bir şey veya herhangi bir UIResponderalt sınıf olabilir. Aynı zamanda bir UIButton olabilir ve kütüphane TouchUpInsidesadece etkin olduğunda düğmenin olayını tetikleyecek kadar akıllıdır . resim açıklamasını buraya girin resim açıklamasını buraya girin

İşte kütüphane:

resim açıklamasını buraya girin


Evet. Swift 3.1, çok zarif bir çözüm, IB'nin kutudan nasıl çıkması gerektiği ile gerçekten iyi çalışıyor.
Richard

Üzgünüm, soruyu anlamadım.
mohamede1945

1
NextResponderTextField benim için çalıştı. Hızlı ve kullanımı kolay, sadece bir hızlı dosya ve tam olarak hiçbir karışıklık olmadan ne gerekli yaptı.
Pat McG

Harika çalışıyor! Not: Dönüş tuşunu, örnekte olduğu gibi "İleri" ve "Tamamlandı" görüntülemesini sağlamak için manuel olarak değiştirmek zorunda kaldım.
Mike Miller

14

Anth0 ve Answerbot tarafından önerilen OO çözümlerini seviyorum. Ancak, hızlı ve küçük bir POC üzerinde çalışıyordum, bu yüzden alt sınıflar ve kategorilerle işleri karıştırmak istemedim.

Başka bir basit çözüm, bir NSArray alanı oluşturmak ve bir sonraki alana bastığınızda bir sonraki alanı aramaktır. OO çözümü değil, hızlı, basit ve uygulaması kolaydır. Ayrıca, siparişi bir bakışta görebilir ve değiştirebilirsiniz.

İşte benim kod (bu konudaki diğer cevaplar üzerine inşa edilmiş):

@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;
}
  • Her zaman NO döndürürüm çünkü bir satır sonu eklemek istemiyorum. Sadece EVET döndüğümde otomatik olarak sonraki alanlardan çıkmak veya benim TextView benim satır sonu eklemek beri işaret düşündüm. Bunu anlamak biraz zamanımı aldı.
  • activeField, alanı klavyeden çıkarmak için kaydırma yapılması gerektiğinde etkin alanı izler. Benzer bir kodunuz varsa, ilk yanıtlayıcıyı değiştirmeden önce activeField öğesini atadığınızdan emin olun. İlk yanıtlayıcının değiştirilmesi hemen gerçekleşir ve hemen KeyboardWasShown olayını başlatır.

Güzel! Basit ve kod tek bir modelde. AnthO's da çok güzel.
Seamus

Bu kodla döngüleri koruma riski taşıdığınızı unutmayın. FieldArray öğeniz, tüm IBOutlet'lerinizde kullanılan zayıf başvuruları dizinin kendisindeki güçlü başvurulara dönüştürüyor.
Michael Long

11

İşte UIControl üzerinde bir kategori kullanarak sekme uygulaması. Bu çözüm, Michael ve Anth0'ın yöntemlerinin tüm avantajlarına sahiptir, ancak sadece UITextFields için değil, tüm UIController için çalışır . Ayrıca Interface Builder ve film şeritleriyle sorunsuz bir şekilde çalışır.

Kaynak ve örnek uygulama: UIControlsWithTabbing için GitHub deposu

Kullanımı:

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField transferFirstResponderToNextControl];
    return NO;
}

Interface Builder'da nextControl atama

Başlık:

//
// UIControl+NextControl.h
// UIControlsWithTabbing
//

#import <UIKit/UIKit.h>

@interface UIControl (NextControl)

@property (nonatomic, weak) IBOutlet UIControl *nextControl;

- (BOOL)transferFirstResponderToNextControl;

@end

Uygulama:

#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

1
Bazen bir sonraki widget bir kontrol değildir yani UITextView ve orada da sekmek isteyebilirsiniz. Türü UITextField yerine UIResponder olarak değiştirmeyi düşünün.
Pedro Borges

1
Harika ve harika belgelerle çalışır. Keşke birkaç saat önce bu soruya geçsem. :)
Javier Cadiz

10

Birçok kod denedim ve son olarak, bu benim için Swift 3.0'da çalıştı En son [Mart 2017]

ViewControllerSınıf miras edilmelidir UITextFieldDelegatebu kod çalışma yapmak için.

class ViewController: UIViewController,UITextFieldDelegate  

Uygun Etiket numarasıyla Metin alanını ekleyin; bu etiket numarası, denetimi kendisine atanan artımlı etiket numarasına göre uygun metin alanına götürmek için kullanılır.

override func viewDidLoad() {
    userNameTextField.delegate = self
    userNameTextField.tag = 0
    userNameTextField.returnKeyType = UIReturnKeyType.next
    passwordTextField.delegate = self
    passwordTextField.tag = 1
    passwordTextField.returnKeyType = UIReturnKeyType.go
}

Yukarıdaki kodda, uygulamanızın değerleri değiştirmesine bağlı olarak vb. Gibi diğer seçeneklere sahip olduğunuzda returnKeyType = UIReturnKeyType.nextTuş takımı dönüş tuşunun görüntülenmesini sağlayacaktır .NextJoin/Go

Bu textFieldShouldReturn, UITextFieldDelegate kontrollü bir yöntemdir ve burada Etiket değeri artışına dayalı bir sonraki alan seçimimiz var

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
 }

Bu, giriş alanının olması dışında çalışır UITextView. Bunu birlikte nasıl yapacağınız hakkında bir fikriniz var mı?
T9b

@ T9b Tam olarak neye ihtiyacınız var? uygulamanızda ne tür kontrol kullanıyorsunuz?
BHUVANESH MOHANKUMAR

İki tür metin girişi vardır, birincisi UITextField(tek bir metin satırı girin), diğeri UITextView(birden çok metin satırı girin). UITextViewAçıkçası bu koda cevap vermez ancak formlarda kullanılabilir.
T9b

Evet, kodun bu kısmı sadece UITextField için ve UITextView için olayı değiştirmeyi denediniz mi?
BHUVANESH MOHANKUMAR

9

Bir metin alanından çıktıktan sonra [otherTextField becameFirstResponder] öğesini çağırırsınız ve sonraki alan odaklanır.

Bu genellikle başa çıkmak için zor bir sorun olabilir, çünkü sıklıkla ekranı kaydırmak veya metin alanının konumunu başka bir şekilde ayarlamak istersiniz, böylece düzenleme sırasında kolayca görülebilir. Metin alanlarına farklı şekillerde girip çıkarak ve aynı zamanda erken bırakarak çok fazla test yaptığınızdan emin olun (her zaman kullanıcıya bir sonraki alana gitmek yerine genellikle klavyeyi kapatmak için bir seçenek verin, genellikle gezinme çubuğu)


9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}

Etiketleri bu amaçla kullanmamalısınız.
Christian Schnorr

7

'Bitti' düğmesine basıldığında klavyeyi kapatmak için çok kolay bir yöntem:

Üstbilgide yeni bir IBAction oluşturun

- (IBAction)textFieldDoneEditing:(id)sender;

Uygulama dosyasına (.m dosyası) aşağıdaki yöntemi ekleyin:

- (IBAction)textFieldDoneEditing:(id)sender 
{ 
  [sender resignFirstResponder];
}

Ardından, IBAction'ı metin alanına bağlamak için geldiğinizde - 'Çıkışta Bitti' olayına bağlantı.


1
Hoş geldiniz, metin alanlarında gezinmiyor, ancak klavyeyi kapatıyor ve başka bir alan seçmenize izin veriyor.
jcrowson

7

Önce klavye dönüş tuşunu xib olarak ayarlayın, aksi takdirde kodu yazabilirsiniz 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;
}

7

Burada kaç cevabın basit bir kavramı anlayamadığına şaşırdım: uygulamanızdaki kontrollerde gezinmek, görünümlerin kendilerinin yapması gereken bir şey değil. Bir sonraki ilk yanıtlayıcıyı hangi kontrolün yapacağına karar vermek kontrolörün görevidir.

Ayrıca, çoğu yanıt yalnızca ileriye doğru gitmek için geçerlidir, ancak kullanıcılar geri gitmek de isteyebilir.

İşte ortaya koyduğum şey. Formunuz bir görünüm denetleyicisi tarafından yönetilmelidir ve görünüm denetleyicileri yanıtlayıcı zincirinin bir parçasıdır. Dolayısıyla, aşağıdaki yöntemleri uygulamakta tamamen özgürsünüz:

#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];
}

Önceden görülebilen ekran dışı yanıt verenleri kaydırmak için ek mantık uygulanabilir.

Bu yaklaşımın bir başka avantajı, görüntülemek isteyebileceğiniz her türlü kontrolü alt sınıflara ayırmanıza gerek olmamasıdır ( UITextField s) ama onun yerine, dürüst olalım denetleyici düzeyinde mantığı yönetebilir olduğu doğru yer bunu .


En uygun olanın cevabını buluyorum, ama belki de temizleyebileceğiniz komik bir şey fark ettim. Bir UIViewController tarafından yönetilmeyen UITextField'leri kullanırken, sekme tuşuna basıldığında bir sonraki UITextField öğesi varsayılan olarak ilk yanıtlayıcı olur. Ancak, her biri bir VC tarafından yönetildiğinde, bu doğru değildir. VC'lerim hiçbir şey yapmıyor, ancak yerleşik sekme işlevi kayboldu. VC'nin keyCommands'ının görünümün yanıtlayıcı zincirine eklendiğini fark ettim. Bu, bir VC kullanıyorsanız, keyCommands uygulamasının zorunlu olduğuna inanmamı sağlıyor.
Joey Carson

4

Bir önceki / sonraki düğme işlevselliğini uygulamak istiyorsanız PeyloW'un cevabına ekledim:

- (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];

    }
}

UIView'i şu şekilde alt sınıflandırdığınız yer:

@implementation UIView (FindAndReturnFirstResponder)
- (UITextView *)findFirstResponderAndReturn
{
    for (UITextView *subView in self.subviews) {
        if (subView.isFirstResponder){
            return subView;
        }
    }
    return nil;
}
@end

4

Merhaba herkese bakın bu bir

- (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;    
  }
}

UIView * responder yerine UIRespoder * responder kullanmak daha doğru olur
Pedro Borges


4

GNTextFieldsCollectionManager ile uğraşırken yeni bir Pod oluşturdum . Sonraki / son textField sorununu otomatik olarak işler ve kullanımı çok kolaydır:

[[GNTextFieldsCollectionManager alloc] initWithView:self.view];

Görünüm hiyerarşisinde (veya etiketlere göre) görünerek sıralanan tüm metin alanlarını alır veya kendi textFields dizinizi belirtebilirsiniz.


4

Swift 3.1'deki çözüm, Metin alanlarınızı bağladıktan sonra IBOutlets metin alanları temsilcinizi viewDidLoad'da ayarlayın ve ardından eyleminizde textFieldShouldReturn içinde gezinin

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
        }

    }

4

Birisi böyle isterse. Bence bu talep edilen gereksinimlere en yakın olanı

resim açıklamasını buraya girin

İşte bunu nasıl uyguladım

Kurulumunu yapmak istediğiniz her metin alanı için aksesuar görünümünü kullanarak

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
}

Muslukları işlemek için aşağıdaki işlevleri kullanın

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
}

TextFields etiketlerini, sonraki / önceki düğmesinin yanıt vermesini istediğiniz sırayla vermeniz gerekir.


3

Daha çok:

@interface MyViewController : UIViewController
@property (nonatomic, retain) IBOutletCollection(UIView) NSArray *inputFields;
@end

NIB dosyasında textField'leri istenen sırayla bu inputFields dizisine bağlarım. Bundan sonra kullanıcının dokundu dönüş bildirir UITextField dizini için basit bir test yapmak:

// 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;
}

3
eğer (hücre == nil)
{
    hücre = [[UITableViewCell ayırma] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: cellIdentifier];
    txt_Input = [[UITextField ayırma] initWithFrame: CGRectMake (0, 10, 150, 30)];
    txt_Input.tag = indexPath.row + 1;
    [self.array_Textfields addObject: txt_Input]; // ViewDidLoad'da değiştirilebilir diziyi başlat
}

- (BOOL) textFieldShouldReturn: (UITextField *) textField
{

    int tag = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: tag];
    [txt, İlkSüreşçi olur];
    dönüş YES;
}

3

Hikaye panomda yaklaşık 10+ UITextField vardı ve bir sonraki işlevselliği etkinleştirmenin yolu bir UITextField dizisi oluşturmak ve bir sonraki UITextField firstResponder yaparak oldu. Uygulama dosyası şöyledir:

#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

3

Bu benim için Xamarin.iOS / Monotouch'da çalıştı. Klavye düğmesini İleri olarak değiştirin, denetimi bir sonraki UITextField öğesine aktarın ve son UITextField öğesinden sonra klavyeyi gizleyin.

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.
    };
  }
}

ViewDidLoad içinde şunlara sahip olacaksınız:

TextField'lerinizde bir Etiket yoksa şimdi ayarlayın:

txtField1.Tag = 0;
txtField2.Tag = 1;
txtField3.Tag = 2;
//...

ve sadece çağrı

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.

3

Bu, etiket kullanmadan, film şeridi hileleri olmadan hızlı bir şekilde basit bir çözümdür ...

Sadece bu uzantıyı kullanın:

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
    }

}

Ve metin alanı temsilcinizle bu şekilde (örneğin) deyin:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    textField.nextTextFieldField()?.becomeFirstResponder()
    return true
}

Bununla ilgili bir yorum, yönteminizin tüm alanların tek bir denetimde dikey olarak istiflendiğini varsaymasıdır. Gruplandırılmış alanlara izin vermez ve metin alanları yan yana olduğunda çalışmaz. Daha sonra, yatay görünümler için formları yeniden düzenlemek için boyut sınıflarını kullandığında ve formlar tabletlerde görüntülendiğinde bu gün özellikle önemlidir.
Michael Long

3

Aşağıdakileri varsayarak daha güvenli ve daha doğrudan bir yol:

  • metin alanı delegeleri görünüm denetleyicinize ayarlanmış
  • tüm metin alanları aynı görünümün alt görünümleridir
  • metin alanlarında ilerlemek istediğiniz sırada etiketler bulunur (örneğin, textField2.tag = 2, textField3.tag = 3 vb.)
  • klavyedeki dönüş düğmesine dokunduğunuzda bir sonraki metin alanına geçmek gerçekleşir (bunu bir sonraki , tamamlanmış vb. olarak değiştirebilirsiniz)
  • klavyenin son metin alanından sonra kapanmasını istiyorsunuz

Hızlı 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

    }
}

2

textFieldShouldReturn öğesinde, şu anda bulunduğunuz metin alanının bir sonraki tıklattıklarında son alan olmadığından ve n klavyeyi devre dışı bırakmadığından emin olmalısınız.


Hey, tamam bu işe yarıyor, ama "İleri" Düğmesi ile nasıl bir sonraki metin alanına atlayabilirim?
phx

2

Bu eski bir gönderi, ancak yüksek bir sayfa sıralamasına sahip, bu yüzden çözümümle karşılaşacağım.

Benzer bir sorun vardı ve UIToolbardinamik bir tabloda sonraki / önceki / tamamlanan işlevsellik yönetmek için bir alt sınıf oluşturma sona erdi Bölümleri ile https://github.com/jday001/DataEntryToolbar

Araç çubuğunu metin alanlarınızın inputAccessoryView olarak ayarlayıp sözlüğüne eklersiniz. Bu, dinamik içerikle bile aralarında ileri ve geri dolaşmanıza olanak tanır. TextField gezinti gerçekleştiğinde kendi işlevselliğinizi tetiklemek istiyorsanız temsilci yöntemler vardır, ancak herhangi bir etiketi veya ilk yanıtlayıcı durumunu yönetmeyle uğraşmanız gerekmez.

Uygulama ayrıntılarına yardımcı olmak için GitHub bağlantısında kod parçacıkları ve örnek bir uygulama vardır. Alanların içindeki değerleri takip etmek için kendi veri modelinize ihtiyacınız olacaktır.


2

Kullanıcı etiketleri olmadan ve nextField / nextTextField için bir özellik eklemeden, "testInput" öğesinin geçerli etkin alanınız olduğu TAB'ı taklit etmek için bunu deneyebilirsiniz:

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];
                                                    }];

2

Michael G. Emmons'un cevabını yaklaşık bir yıldır kullanıyorum, harika çalışıyor. Kısa süre önce resignFirstResponder çağrıldıktan sonra beFirstResponder çağrılmasının klavyenin "hata yapmasına", kaybolmasına ve hemen görünmesine neden olabileceğini fark ettim. NextField varsa resignFirstResponder atlamak için onun sürümünü biraz değiştirdim.

- (BOOL) textFieldShouldReturn: (UITextField *) textField
{ 

    if ([textField isKindOfClass: [NRTextField class]]))
    {
        NRTextField * nText = (NRTextField *) textField;
        if ([nText nextField]! = nil) {
            dispatch_async (dispatch_get_main_queue (),
                           ^ {[[nText nextField] halineFirstResponder]; });

        }
        Başka{
            [textField resignFirstResponder];
        }
    }
    Başka{
        [textField resignFirstResponder];
    }

    geri dönüş;

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.