Jak mogę przesunąć UITextField w górę, gdy klawiatura jest obecna - po rozpoczęciu edycji?


1691

Dzięki pakietowi SDK dla systemu iOS:

Mam UIViewz UITextFieldS, które wprowadzają się do klawiatury. Potrzebuję, aby móc:

  1. Zezwól na przewijanie zawartości, UIScrollViewaby zobaczyć inne pola tekstowe po podniesieniu klawiatury

  2. Automatycznie „skacz” (przewijając w górę) lub skracając

Wiem, że potrzebuję UIScrollView. Próbowałem zmienić klasę UIViewna UIScrollViewale, ale nadal nie mogę przewijać pól tekstowych w górę lub w dół.

Czy potrzebuję zarówno a, jak UIViewi a UIScrollView? Czy jedno wchodzi w drugie?

Co należy zaimplementować, aby automatycznie przewinąć do aktywnego pola tekstowego?

Najlepiej, jak najwięcej ustawień komponentów zostanie przeprowadzonych w Konstruktorze interfejsów. Chciałbym pisać kod tylko dla potrzeb.

Uwaga: UIView(lub UIScrollView), z którym pracuję, jest wywoływane przez tabbar ( UITabBar), który musi działać normalnie.


Edycja: Dodaję pasek przewijania tylko na czas, gdy pojawia się klawiatura. Chociaż nie jest to potrzebne, wydaje mi się, że zapewnia lepszy interfejs, ponieważ wtedy użytkownik może na przykład przewijać i zmieniać pola tekstowe.

Mam to działa tam, gdzie zmieniam rozmiar ramki, UIScrollViewgdy klawiatura idzie w górę i w dół. Po prostu używam:

-(void)textFieldDidBeginEditing:(UITextField *)textField { 
    //Keyboard becomes visible
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
                     scrollView.frame.origin.y, 
scrollView.frame.size.width,
scrollView.frame.size.height - 215 + 50);   //resize
}

-(void)textFieldDidEndEditing:(UITextField *)textField {
   //keyboard will hide
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
       scrollView.frame.origin.y, 
     scrollView.frame.size.width,
      scrollView.frame.size.height + 215 - 50); //resize
}

Nie powoduje to jednak automatycznego „przesunięcia w górę” ani wyśrodkowania dolnych pól tekstowych w widocznym obszarze, czego naprawdę chciałbym.


6
Spójrz na to. Bez kłopotów dla ciebie. TPKeyboardAvoiding
Aruna

21
Jest udokumentowany przez Apple, myślę, że to najlepszy sposób: developer.apple.com/library/ios/#documentation/StringsTextFonts/...
Maik639,

58
Użyj tego kodu. Potrzebujesz tylko 1 linii w pliku appdelegate.m i działa. github.com/hackiftekhar/IQKeyboardManager
Pradeep Mittal

9
Najlepszym sposobem, jaki do tej pory znalazłem, jest ten open source TPKeyboardAvoiding
Mongi Zaidi

2
Innym sposobem jest dodanie takich pól tekstowych zawartości i wszystkich w TableViewController i pozwolenie, aby tableview sobie z tym poradził.
Vicky Dhas,

Odpowiedzi:


1036
  1. Będziesz potrzebował tylko, ScrollViewjeśli zawartość, którą masz teraz, nie mieści się na ekranie iPhone'a. (Jeśli dodajesz ScrollViewjako podgląd elementów tylko po to, aby TextFieldprzewijać w górę, gdy pojawia się klawiatura, nie jest to konieczne).

  2. Standardowym sposobem zapobiegania TextFieldzasłonięciu klawiatury jest przesuwanie widoku w górę / w dół za każdym razem, gdy pokazana jest klawiatura.

Oto przykładowy kod:

#define kOFFSET_FOR_KEYBOARD 80.0

-(void)keyboardWillShow {
    // Animate the current view out of the way
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)keyboardWillHide {
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
    if ([sender isEqual:mailTf])
    {
        //move the main view, so that the keyboard does not hide it.
        if  (self.view.frame.origin.y >= 0)
        {
            [self setViewMovedUp:YES];
        }
    }
}

//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.3]; // if you want to slide up the view

    CGRect rect = self.view.frame;
    if (movedUp)
    {
        // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 
        // 2. increase the size of the view so that the area behind the keyboard is covered up.
        rect.origin.y -= kOFFSET_FOR_KEYBOARD;
        rect.size.height += kOFFSET_FOR_KEYBOARD;
    }
    else
    {
        // revert back to the normal state.
        rect.origin.y += kOFFSET_FOR_KEYBOARD;
        rect.size.height -= kOFFSET_FOR_KEYBOARD;
    }
    self.view.frame = rect;

    [UIView commitAnimations];
}


- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

3
Co robi _textField? Skopiowałem go do mojego kodu, napisano, że _textField nie jest zadeklarowany.
Cocoa Dev

Jest to pole, którego używasz do powiedzenia „gdy użytkownik edytuje tutaj widok powinien się przesunąć w górę” lub coś w tym rodzaju… Jednak możesz to usunąć, jeśli masz więcej pól.
Patrick

czy to nie jest rzadkie, aby zadzwonić - (void) setViewMovedUp: (BOOL) moveUp w zdarzeniach keyBoardWillSHow i KeyBoardWillHide !!
Abduliam Rehmanius

4
Nie jest to szczególnie przydatne, jeśli wspierasz obracanie widoku głównego.
FractalDoctor,

2
Aby to zadziałało, musiałem skomentować tę textFieldDidBeginEditingsekcję.
avance

445

Miałem też duży problem z UIScrollViewkomponowaniem wielu UITextFields, z których jeden lub więcej z nich zostałoby zasłoniętych przez klawiaturę podczas edytowania.

Oto kilka rzeczy do rozważenia, jeśli twoje UIScrollViewprzewijanie nie jest prawidłowe.

1) Upewnij się, że rozmiar contentSize jest większy niż UIScrollViewrozmiar ramki. Można zrozumieć, UIScrollViewsże UIScrollViewjest to okno do oglądania treści zdefiniowanej w contentSize. Gdy więc, UIScrollviewaby przewijać w dowolnym miejscu, contentSize musi być większy niż UIScrollView. W przeciwnym razie przewijanie nie jest wymagane, ponieważ wszystko zdefiniowane w contentSize jest już widoczne. BTW, domyślna treść Rozmiar = CGSizeZero.

2) Teraz, gdy rozumiesz, że UIScrollViewtak naprawdę jest to okno na twoją „treść”, sposobem na upewnienie się, że klawiatura nie zasłania twojego UIScrollView's„okna” oglądania, będzie zmiana rozmiaru UIScrollViewtak, aby gdy klawiatura była obecna, masz UIScrollViewokno rozmiar tylko do oryginalnej UIScrollViewramki. rozmiar. wysokość minus wysokość klawiatury. Zapewni to, że twoje okno jest tylko tym małym widocznym obszarem.

3) Oto haczyk: kiedy po raz pierwszy to zaimplementowałem, pomyślałem, że będę musiał pobrać CGRectedytowane pole tekstowe i wywołać UIScrollView'smetodę scrollRecToVisible. Zaimplementowałem UITextFieldDelegatemetodę textFieldDidBeginEditingza pomocą wywołania scrollRecToVisiblemetody To faktycznie zadziałało z dziwnym efektem ubocznym, że przewijanie zatrzasnęło się UITextFieldw pozycji. Najdłużej nie mogłem zrozumieć, co to było. Potem skomentowałem textFieldDidBeginEditingmetodę Delegata i wszystko działa !! (???). Jak się okazało, wierzę, że UIScrollViewfaktycznie niejawnie przenosi aktualnie edytowane UITextFielddo widocznego okna. Moja implementacja UITextFieldDelegatemetody i późniejsze wywołanie jej scrollRecToVisiblebyło zbędne i było przyczyną dziwnego efektu ubocznego.

Oto kroki, które należy wykonać, aby poprawnie przewinąć urządzenie UITextFieldw odpowiednie UIScrollViewmiejsce, gdy pojawi się klawiatura.

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad 
{
    [super viewDidLoad];

    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillShow:) 
                                                 name:UIKeyboardWillShowNotification 
                                               object:self.view.window];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillHide:) 
                                                 name:UIKeyboardWillHideNotification 
                                               object:self.view.window];
    keyboardIsShown = NO;
    //make contentSize bigger than your scrollSize (you will need to figure out for your own use case)
    CGSize scrollContentSize = CGSizeMake(320, 345);
    self.scrollView.contentSize = scrollContentSize;
}

- (void)keyboardWillHide:(NSNotification *)n
{
    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;


    // resize the scrollview
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height += (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];

    keyboardIsShown = NO;
}

- (void)keyboardWillShow:(NSNotification *)n
{
    // This is an ivar I'm using to ensure that we do not do the frame size adjustment on the `UIScrollView` if the keyboard is already shown.  This can happen if the user, after fixing editing a `UITextField`, scrolls the resized `UIScrollView` to another `UITextField` and attempts to edit the next `UITextField`.  If we were to resize the `UIScrollView` again, it would be disastrous.  NOTE: The keyboard notification will fire even when the keyboard is already shown.
    if (keyboardIsShown) {
        return;
    }

    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

    // resize the noteView
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height -= (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];
    keyboardIsShown = YES;
}
  1. Zarejestruj się, aby otrzymywać powiadomienia na klawiaturze pod adresem viewDidLoad
  2. Wyrejestruj, aby uzyskać informacje na temat klawiatury na viewDidUnload
  3. Upewnij się, że contentSizejest ustawiony i większy niż twój UIScrollViewwviewDidLoad
  4. KurczyćUIScrollView gdy klawiatura jest obecny
  5. PowrócićUIScrollView gdy klawiatura odchodzi.
  6. Użyj ivar wykryć, czy klawiatura jest już wyświetlany na ekranie od powiadomienia klawiaturowe są wysyłane za każdym razem, gdy UITextFieldjest kartach nawet jeśli klawiatura jest już obecny, aby uniknąć kurczeniaUIScrollView kiedy jest już skurczony

Jedną rzeczą do odnotowania jest to, że UIKeyboardWillShowNotificationuruchomi się nawet, gdy klawiatura jest już na ekranie, gdy tabulujesz inną UITextField. Zadbałem o to, używając ivara, aby uniknąć zmiany rozmiaru, UIScrollViewgdy klawiatura jest już na ekranie. Nieumyślna zmiana rozmiaru, UIScrollViewgdy klawiatura jest już dostępna, byłaby katastrofalna!

Mam nadzieję, że ten kod zaoszczędzi niektórym z was dużo bólu głowy.


3
Świetnie, ale dwa problemy: 1. UIKeyboardBoundsUserInfoKeyjest przestarzały. 2. KeyboardSize ma „współrzędne ekranu”, więc obliczenia viewFrame zakończą się niepowodzeniem, jeśli ramka zostanie obrócona lub skalowana.
Martin Wickman,

21
@Martin Wickman - Użyj CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;zamiast przestarzałegoUIKeyboardBoundsUserInfoKey
sottenad

1
HI, zrobiłem to samo, ale widok tekstu przesuwa się tylko wtedy, gdy użytkownik zaczyna pisać? Czy to oczekiwane zachowanie, czy coś mi brakuje?

3
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].sizepowinno być [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size. Świetne rozwiązanie!
j7nn7k

1
Podoba mi się twoje rozwiązanie, ale myślę, że mogę to uczynić jeszcze prostszym: nie zawracaj sobie głowy sprawami Notification Observer; zamiast tego wywołać odpowiednie procedury animacji w odpowiednich metodach delegowania - dla UITextView są to textViewDidBeginEditing i textViewDidEndEditing.
AlexChaffee,

270

W rzeczywistości najlepiej jest po prostu korzystać z implementacji Apple, zgodnie z dokumentacją . Podany przez nich kod jest jednak wadliwy. Zamień część znajdującą się keyboardWasShown:tuż pod komentarzami na następujące:

NSDictionary* info = [aNotification userInfo];
CGRect keyPadFrame=[[UIApplication sharedApplication].keyWindow convertRect:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:self.view];
CGSize kbSize =keyPadFrame.size;
CGRect activeRect=[self.view convertRect:activeField.frame fromView:activeField.superview];
CGRect aRect = self.view.bounds;
aRect.size.height -= (kbSize.height);

CGPoint origin =  activeRect.origin;
origin.y -= backScrollView.contentOffset.y;
if (!CGRectContainsPoint(aRect, origin)) {
    CGPoint scrollPoint = CGPointMake(0.0,CGRectGetMaxY(activeRect)-(aRect.size.height));
    [backScrollView setContentOffset:scrollPoint animated:YES];
}

Problemy z kodem Apple są następujące: (1) Zawsze obliczają, czy punkt znajduje się w ramce widoku, ale jest to ScrollView, więc mógł już przewinąć i trzeba uwzględnić to przesunięcie:

origin.y -= scrollView.contentOffset.y

(2) Przesuwają zawartość Offset o wysokość klawiatury, ale chcemy mieć coś przeciwnego (chcemy przesunąć contentOffseto wysokość widoczną na ekranie, a nie to, co nie jest):

activeField.frame.origin.y-(aRect.size.height)

1
W sytuacjach, w których widok przewijania nie wypełnia ekranu, aRect powinien być ustawiony na ramkę widoku przewijania
mblackwell8

2
Czy nie chcesz, aby CGPoint origin = activeField.frame.origin + activeField.frame.size.height ?, ponieważ chcesz, aby całe pole tekstowe było wyświetlane, a jeśli akurat ma tylko kilka pikseli, kod nie wejdzie w stan: schorzenie.
htafoya

1
To rozwiązanie nie działa w orientacji poziomej - pole tekstowe wylatuje z górnej części portu widoku. iPad z iOS 7.1.
Andrew

4
Aby uzyskać lepszą obsługę iOS 8, sugeruję używanie UIKeyboardFrameEndUserInfoKeyzamiast UIKeyboardFrameBeginUserInfoKeyprzy uzyskiwaniu rozmiaru klawiatury, ponieważ spowoduje to przechwycenie takich zmian, jak niestandardowe zmiany klawiatury i włączanie / wyłączanie predykcyjnego tekstu.
Endareth

1
@Egor: Twoja poprawka sprawia, że ​​działa o wiele lepiej - ale ostatnia linia musi być odwrotna:self.scrollView.contentOffset = self.currentSVoffset;
Morten Holmgaard

244

W textFieldDidBeginEdittingi w textFieldDidEndEditingwywołaniu funkcji [self animateTextField:textField up:YES]tak:

-(void)textFieldDidBeginEditing:(UITextField *)textField 
{ 
    [self animateTextField:textField up:YES]; 
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField:textField up:NO];
}

-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
    const int movementDistance = -130; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? movementDistance : -movementDistance); 

    [UIView beginAnimations: @"animateTextField" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

Mam nadzieję, że ten kod ci pomoże.

W Swift 2

func animateTextField(textField: UITextField, up: Bool) 
{
     let movementDistance:CGFloat = -130
     let movementDuration: Double = 0.3

     var movement:CGFloat = 0
     if up 
     {
         movement = movementDistance
     }
     else 
     {
         movement = -movementDistance
     }
     UIView.beginAnimations("animateTextField", context: nil)
     UIView.setAnimationBeginsFromCurrentState(true)
     UIView.setAnimationDuration(movementDuration)
     self.view.frame = CGRectOffset(self.view.frame, 0, movement)
     UIView.commitAnimations()
}


func textFieldDidBeginEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:true)
}

func textFieldDidEndEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:false)
}

SWIFT 3

 func animateTextField(textField: UITextField, up: Bool)
    {
        let movementDistance:CGFloat = -130
        let movementDuration: Double = 0.3

        var movement:CGFloat = 0
        if up
        {
            movement = movementDistance
        }
        else
        {
            movement = -movementDistance
        }
        UIView.beginAnimations("animateTextField", context: nil)
        UIView.setAnimationBeginsFromCurrentState(true)
        UIView.setAnimationDuration(movementDuration)
        self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
        UIView.commitAnimations()
    }


    func textFieldDidBeginEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:true)
    }

    func textFieldDidEndEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:false)
    }

1
dlaczego nie użyć [UIView animateWithDuration: animations:^{ }];?
Andre Cytryn,

2
działa to dobrze, chociaż const int moveDistance = -130; // zmiana w razie potrzeby musi zostać zmieniona na bardziej elastyczną
Hammer

7
Niezwykle proste w przypadku małych wdrożeń. Żadnych chowańców dzięki ScrollViews i dwuznacznym problemom z automatycznym układem.
James Perih

4
Erm ... w ogóle nie używasz parametru textField. Dlaczego więc ma to jako parametr funkcji? Dodatkowo możesz użyć operatora trójskładnikowego również w Swift. Sprawia, że ​​kod jest mniej gadatliwy.
stk

1
Jeśli kolor tła widoku jest inny niż czarny, upewnij się, że ustawiłeś kolor okna tak, aby pasował do twojego widoku, aby użytkownik nie widział za nim. tj. self.window.backgroundColor = [UIColor whiteColor];
bvmobileapps

134

Po prostu używając TextFields:

1a) Używając Interface Builder: Wybierz wszystkie pola tekstowe => Edytuj => Osadź w => ScrollView

1b) Ręcznie osadzaj TextFields w UIScrollView o nazwie scrollView

2) Ustaw UITextFieldDelegate

3) Ustaw każdy textField.delegate = self;(lub wykonaj połączenia Interface Builder)

4) Kopiuj / Wklej:

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    CGPoint scrollPoint = CGPointMake(0, textField.frame.origin.y);
    [scrollView setContentOffset:scrollPoint animated:YES];
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    [scrollView setContentOffset:CGPointZero animated:YES];
}

8
Ale przesuwa się również w górę widoku, gdy textFieldjest już widoczny.
TheTiger

1
Musisz zmienić CGPointMake(0, textField.frame.origin.y);naCGPointMake(0, textField.frame.origin.y + scrollView.contentInset.top);
Fury

@Egor Nawet po twoim komentarzu to nie działa. Podobnie jak wspomniany „TheTiger”, przesuwa się w górę widoku, nawet gdy pole tekstowe jest widoczne.
rak appdev

Zmień na XCode 10: „Wybierz wszystkie pola tekstowe => Edytor => Osadź w => Przewiń widok”
tibalt

116

W przypadku rozwiązania uniwersalnego oto moje podejście do implementacji IQKeyboardManager .

wprowadź opis zdjęcia tutaj

Krok 1: - dodałem globalne notyfikacje UITextField, UITextVieworaz UIKeyboardw klasie singleton. Nazywam to IQKeyboardManager .

Krok 2: - W przypadku stwierdzenia UIKeyboardWillShowNotification, UITextFieldTextDidBeginEditingNotificationczy UITextViewTextDidBeginEditingNotificationpowiadomienia, staram się dostać topMostViewControllerwystąpienie z UIWindow.rootViewControllerhierarchii. W celu prawidłowego odkrycia UITextField/ UITextViewna nim, topMostViewController.viewrama musi zostać dostosowana.

Krok 3: - Obliczyłem spodziewaną odległość ruchu topMostViewController.vieww stosunku do pierwszej odpowiedzi UITextField/ UITextView.

Krok 4: - Poruszałem się w topMostViewController.view.framegórę / w dół zgodnie z oczekiwaną odległością ruchu.

Krok 5: - W przypadku stwierdzenia UIKeyboardWillHideNotification, UITextFieldTextDidEndEditingNotificationczy UITextViewTextDidEndEditingNotificationzgłoszenie, znowu postarać się topMostViewControllerwystąpienie z UIWindow.rootViewControllerhierarchii.

Krok 6: - Obliczyłem zakłóconą odległość, topMostViewController.viewktórą należy przywrócić do pierwotnej pozycji.

Krok 7: - Przywróciłem w topMostViewController.view.framedół zgodnie z zakłóconą odległością.

Krok 8: - Utworzyłem instancję klasy singleton IQKeyboardManager podczas ładowania aplikacji, więc każda UITextField/ UITextVieww aplikacji dostosuje się automatycznie zgodnie z oczekiwaną odległością ruchu.

To wszystko, co IQKeyboardManager robi dla Ciebie bez NO LINE OF CODE naprawdę !! wystarczy przeciągnąć i upuścić powiązany plik źródłowy do projektu. IQKeyboardManager obsługuje również orientację urządzenia , automatyczne zarządzanie paskiem narzędzi UITool , KeybkeyboardDistanceFromTextField i wiele więcej niż myślisz.


Dodaj katalog IQKeyBoardManagerSwift do mojego projektu i nie działają. Nie można włączyć, ponieważ nie jest rozpoznawany w AppDelegate ...
użytkownik3722523,

2
wygląda na to, że phishing nie pokazuje rzeczywistego rozwiązania, ale zamiast tego widzimy reklamę dla tego konta GitHub.
Brian

101

Stworzyłem uniwersalną, rozwijaną UIScrollView, UITableViewa nawet UICollectionViewpodklasę, która zajmuje się przenoszeniem wszystkich pól tekstowych poza klawiaturę.

Kiedy klawiatura ma się pojawić, podklasa znajdzie widok podrzędny, który ma być edytowany, i dostosuje przesunięcie ramki i zawartości, aby upewnić się, że widok jest widoczny, z animacją dopasowaną do wyskakującego okna klawiatury. Kiedy klawiatura znika, przywraca jej poprzedni rozmiar.

Powinien działać w zasadzie z dowolną konfiguracją, UITableViewopartą na interfejsie lub opartą na widokach umieszczanych ręcznie.

Oto rozwiązanie : przenoszenie pól tekstowych z klawiatury


To jest to! To najlepsze, najbardziej wydajne i idealne rozwiązanie! Obsługuje również obroty poprawnie w widokach przewijania. Jeśli obracasz, pamiętaj o automatycznej zmianie rozmiaru w pionie, ale nie zakotwiczaj na dole. Dodałem UITextView do widoku przewijania w moim przypadku. Dzięki pęczki!
Christopher

Bardzo dobra robota! Jasne, jestem leniwy w używaniu twojego rozwiązania zamiast DIY, ale mój szef jest szczęśliwszy, więc tak! Nawet jeśli ktoś chce zrobić to sam, podoba mi się podejście do podklasy, zamiast dodawania kodu do każdego kontrolera. Byłem zszokowany iOS nie zrobił tego domyślnie, podobnie jak Android - z drugiej strony znajduję wiele rzeczy, których brakuje w iOS i MacOS :(
eselk

Dziwne rzeczy, takie jak mój widok przewijania, mieszczą się na ekranie, więc nie można ich przewijać. Po otwarciu i zamknięciu klawiatury zawartość jest teraz większa (wygląda na to, że coś niewidocznego zostało dodane i nie zostało usunięte na dole strony) i można ją przewijać.
Almo

90

Dla programistów Swift :

To zrobi wszystko za Ciebie, po prostu umieść je w klasie kontrolera widoku i zaimplementuj w UITextFieldDelegatekontrolerze widoku i ustaw delegata textField naself

textField.delegate = self // Setting delegate of your UITextField to self

Wdróż delegowane metody wywołania zwrotnego:

func textFieldDidBeginEditing(textField: UITextField) {
    animateViewMoving(true, moveValue: 100)
}

func textFieldDidEndEditing(textField: UITextField) {
    animateViewMoving(false, moveValue: 100)
}

// Lifting the view up
func animateViewMoving (up:Bool, moveValue :CGFloat){
    let movementDuration:NSTimeInterval = 0.3
    let movement:CGFloat = ( up ? -moveValue : moveValue)
    UIView.beginAnimations( "animateView", context: nil)
    UIView.setAnimationBeginsFromCurrentState(true)
    UIView.setAnimationDuration(movementDuration )
    self.view.frame = CGRectOffset(self.view.frame, 0,  movement)
    UIView.commitAnimations()
}

Dla Swift 4, 4.2, 5: zmiana

self.view.frame = CGRectOffset(self.view.frame, 0,  movement)

do

self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)

Ostatnia uwaga na temat tej implementacji: jeśli wepchniesz inny kontroler widoku na stos, gdy klawiatura jest wyświetlona, ​​spowoduje to błąd, gdy widok zostanie przywrócony do środkowej ramki, ale przesunięcie klawiatury nie zostanie zresetowane. Na przykład klawiatura jest pierwszą odpowiedzią dla nameField, ale następnie naciskasz przycisk, który wpycha kontroler widoku pomocy na stos. Aby naprawić błąd przesunięcia, pamiętaj o wywołaniu nameField.resignFirstResponder () przed opuszczeniem kontrolera widoku, upewniając się, że wywoływana jest również metoda delegowania textFieldDidEndEditing. Robię to w metodzie viewWillDisappear.


3
SwiftLint nie lubił, self.view.frame = CGRectOffset(self.view.frame, 0, movement)więc zmieniłem tę linię naself.view.frame.offsetInPlace(dx: 0, dy: movement)
levibostian

2
Swift 4 zmień self.view.frame = CGRectOffset (self.view.frame, 0, ruch) na self.view.frame.offsetBy (dx: 0, dy: ruch)
Asinox

Do Twojej wiadomości, aby to zadziałało, musisz to zrobić. self.view.frame = self.view.frame.offsetBy (dx: 0, dy: ruch)
Josh Wolff

64

Jest już wiele odpowiedzi, ale wciąż żadne z powyższych rozwiązań nie zawierało wszystkich wymyślnych elementów pozycjonowania wymaganych do „idealnej” bezbłędnej, kompatybilnej wstecz i bez migotania animacji. (błąd podczas animacji ramki / granic i zawartości razem, różne ustawienia interfejsu, podzielona klawiatura iPada, ...)
Pozwól, że podzielę się moim rozwiązaniem:
(zakładając, że skonfigurowałeś UIKeyboardWill(Show|Hide)Notification)

// Called when UIKeyboardWillShowNotification is sent
- (void)keyboardWillShow:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = [notification userInfo];

    CGRect keyboardFrameInWindow;
    [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameInWindow];

    // the keyboard frame is specified in window-level coordinates. this calculates the frame as if it were a subview of our view, making it a sibling of the scroll view
    CGRect keyboardFrameInView = [self.view convertRect:keyboardFrameInWindow fromView:nil];

    CGRect scrollViewKeyboardIntersection = CGRectIntersection(_scrollView.frame, keyboardFrameInView);
    UIEdgeInsets newContentInsets = UIEdgeInsetsMake(0, 0, scrollViewKeyboardIntersection.size.height, 0);

    // this is an old animation method, but the only one that retains compaitiblity between parameters (duration, curve) and the values contained in the userInfo-Dictionary.
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    _scrollView.contentInset = newContentInsets;
    _scrollView.scrollIndicatorInsets = newContentInsets;

    /*
     * Depending on visual layout, _focusedControl should either be the input field (UITextField,..) or another element
     * that should be visible, e.g. a purchase button below an amount text field
     * it makes sense to set _focusedControl in delegates like -textFieldShouldBeginEditing: if you have multiple input fields
     */
    if (_focusedControl) {
        CGRect controlFrameInScrollView = [_scrollView convertRect:_focusedControl.bounds fromView:_focusedControl]; // if the control is a deep in the hierarchy below the scroll view, this will calculate the frame as if it were a direct subview
        controlFrameInScrollView = CGRectInset(controlFrameInScrollView, 0, -10); // replace 10 with any nice visual offset between control and keyboard or control and top of the scroll view.

        CGFloat controlVisualOffsetToTopOfScrollview = controlFrameInScrollView.origin.y - _scrollView.contentOffset.y;
        CGFloat controlVisualBottom = controlVisualOffsetToTopOfScrollview + controlFrameInScrollView.size.height;

        // this is the visible part of the scroll view that is not hidden by the keyboard
        CGFloat scrollViewVisibleHeight = _scrollView.frame.size.height - scrollViewKeyboardIntersection.size.height;

        if (controlVisualBottom > scrollViewVisibleHeight) { // check if the keyboard will hide the control in question
            // scroll up until the control is in place
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y += (controlVisualBottom - scrollViewVisibleHeight);

            // make sure we don't set an impossible offset caused by the "nice visual offset"
            // if a control is at the bottom of the scroll view, it will end up just above the keyboard to eliminate scrolling inconsistencies
            newContentOffset.y = MIN(newContentOffset.y, _scrollView.contentSize.height - scrollViewVisibleHeight);

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        } else if (controlFrameInScrollView.origin.y < _scrollView.contentOffset.y) {
            // if the control is not fully visible, make it so (useful if the user taps on a partially visible input field
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y = controlFrameInScrollView.origin.y;

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        }
    }

    [UIView commitAnimations];
}


// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillHide:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = notification.userInfo;

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    // undo all that keyboardWillShow-magic
    // the scroll view will adjust its contentOffset apropriately
    _scrollView.contentInset = UIEdgeInsetsZero;
    _scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;

    [UIView commitAnimations];
}

Wielkie ulepszenia odpowiedzi @Shiun. Ale po zniknięciu klawiatury widok nie wraca na 1. pozycję. To wciąż świetna robota :)
Lucien

2
Dzięki, to najlepsze rozwiązanie dla mnie w 2017 roku. Pamiętaj, że nie musisz samodzielnie śledzić focusedControl, możesz to ustalić za pomocą UIApplication.shared.sendAction(...). Oto twoja odpowiedź Swift 3 (bez części willHide), z sendActionzaimplementowanym: gist.github.com/xaphod/7aab1302004f6e933593a11ad8f5a72d
xaphod

@xaphod w moim przypadku musiałem skupić więcej kontroli - np. przycisk poniżej pola wprowadzania. ale tak, ten kod ma już 4 lata i może skorzystać z ulepszeń.
Martin Ullrich,

To chyba właściwe rozwiązanie. Powiadomienie z klawiatury przenosi dane animacji, delegacje pól tekstowych nie wiedzą o czasie trwania animacji, to tylko zgadywanie.
XY

62

Shiun powiedział: „Jak się okazało, uważam, że UIScrollView pośrednio przenosi obecnie edytowany UITextField do widocznego okna”. Wydaje się, że tak jest w przypadku iOS 3.1.3, ale nie 3.2, 4.0 lub 4.1. Musiałem dodać wyraźny scrollRectToVisible, aby UITextField był widoczny w iOS> = 3.2.


To nie UIScrollView przewija niejawnie edytowany UITextField do widoku, to UITextField, który wywołuje [UITextField scrollTextFieldToVisibleIfNecessary]metodę prywatną , która z kolei wywołuje, [UIScrollView scrollRectToVisible]gdy [UITextField becomeFirstResponder]jest wywoływana. Zobacz github.com/leopatras/ios_textfields_on_scrollview . Jeśli ograniczenia i kontrolery widoku są poprawnie skonfigurowane, tak naprawdę nie ma potrzeby scrollRectToVisiblejawnego wywoływania (przynajmniej od IOS 11).
Leo

48

Jedną z rzeczy do rozważenia jest to, czy kiedykolwiek będziesz chciał używać UITextFieldwłasnego. Nie spotkałem dobrze zaprojektowanych aplikacji na iPhone'a, które faktycznie używają UITextFieldspoza UITableViewCells.

To będzie dodatkowa praca, ale zalecam wdrożenie wszystkich widoków wprowadzania danych w widokach tabel. Dodaj UITextViewdo swojego UITableViewCells.


1
Jedna z moich aplikacji musi umożliwiać użytkownikom dodawanie dowolnych notatek, więc tak, czasami użyteczne jest użycie UITextField.
Peter Johnson

1
Zgadzam się z tą metodą Zero pracy lub kodu w ten sposób. Nawet jeśli potrzebujesz notatki w dowolnym formularzu, nadal możesz to zrobić z komórką
stołową

UITableViewjest niestety jedyną drogą do przejścia. Powiadomienia z klawiatury są kruche i z czasem uległy zmianie. Przykładowy kod dotyczący przepełnienia stosu: stackoverflow.com/a/32390936/218152
SwiftArchitect

Ta odpowiedź jest około pięciu lat nieaktualna. Jedynym nowoczesnym rozwiązaniem jest coś takiego ... stackoverflow.com/a/41808338/294884
Fattie

47

W tym dokumencie szczegółowo opisano rozwiązanie tego problemu. Spójrz na kod źródłowy w części „Przenoszenie treści znajdujących się pod klawiaturą”. To całkiem proste.

EDYCJA: Zauważyłem usterkę wee w przykładzie. Prawdopodobnie będziesz chciał słuchać UIKeyboardWillHideNotificationzamiast UIKeyboardDidHideNotification. W przeciwnym razie widok przewijania za klawiaturą zostanie przycięty na czas animacji zamykania klawiatury.


32

Najłatwiejsze znalezione rozwiązanie

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    [self animateTextField: textField up: YES];
}


- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField: textField up: NO];
}

- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
    const int movementDistance = 80; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? -movementDistance : movementDistance);

    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

Ekran przesuwa się w górę, nawet jeśli nie znajduje się na dole. tzn. jeśli pole tekstowe znajduje się na górze, przesuwa się poza ekran. Jak kontrolować tę sprawę?
MELWIN

@MELWIN Po prostu dodaj po tym wierszu: int movement = (up ? -movementDistance : movementDistance); if (textField.frame.origin.y < self.view.frame.size.height - keyboard.height) { movementDistance = 0 }Proszę nie keyboardpamiętać, że zmienna jest CGRect klawiatury, która się wyskakuje, którą otrzymujesz, wykonując:let keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue())!
CapturedTree

31

Mała poprawka, która działa dla wielu UITextFields

#pragma mark UIKeyboard handling

#define kMin 150

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
   if (currTextField) {
      [currTextField release];
   }
   currTextField = [sender retain];
   //move the main view, so that the keyboard does not hide it.
   if (self.view.frame.origin.y + currTextField.frame.origin. y >= kMin) {
        [self setViewMovedUp:YES]; 
   }
}



//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
   [UIView beginAnimations:nil context:NULL];
   [UIView setAnimationDuration:0.3]; // if you want to slide up the view

   CGRect rect = self.view.frame;
   if (movedUp)
   {
      // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 
      // 2. increase the size of the view so that the area behind the keyboard is covered up.
      rect.origin.y = kMin - currTextField.frame.origin.y ;
   }
   else
   {
      // revert back to the normal state.
      rect.origin.y = 0;
   }
   self.view.frame = rect;

   [UIView commitAnimations];
}


- (void)keyboardWillShow:(NSNotification *)notif
{
   //keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately

   if ([currTextField isFirstResponder] && currTextField.frame.origin.y + self.view.frame.origin.y >= kMin)
   {
      [self setViewMovedUp:YES];
   }
   else if (![currTextField isFirstResponder] && currTextField.frame.origin.y  + self.view.frame.origin.y < kMin)
   {
      [self setViewMovedUp:NO];
   }
}

- (void)keyboardWillHide:(NSNotification *)notif
{
   //keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately
   if (self.view.frame.origin.y < 0 ) {
      [self setViewMovedUp:NO];
   }

}


- (void)viewWillAppear:(BOOL)animated
{
   // register for keyboard notifications
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) 
                                                name:UIKeyboardWillShowNotification object:self.view.window]; 
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) 
                                                name:UIKeyboardWillHideNotification object:self.view.window]; 
}

- (void)viewWillDisappear:(BOOL)animated
{
   // unregister for keyboard notifications while not visible.
   [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 
}

rect.origin.y=+currTextField.frame.origin.ydziała dobrze dziękuję
u.gen

30

Kod RPDP skutecznie usuwa pole tekstowe z klawiatury. Ale po przewinięciu do góry po użyciu i zwolnieniu klawiatury góra została przewinięta w górę z widoku. Dotyczy to symulatora i urządzenia. Aby przeczytać treść u góry tego widoku, należy go ponownie załadować.

Czy jego poniższy kod nie powinien obniżać widoku?

else
{
    // revert back to the normal state.
    rect.origin.y += kOFFSET_FOR_KEYBOARD;
    rect.size.height -= kOFFSET_FOR_KEYBOARD;
}

23

Nie jestem pewien, czy przesunięcie widoku w górę jest właściwym podejściem, zrobiłem to w inny sposób, zmieniając rozmiar UIScrollView. Wyjaśniłem to szczegółowo w małym artykule


Link do artykułu jest martwy.
Teo,

22

Aby przywrócić pierwotny stan widoku, dodaj:

-(void)textFieldDidEndEditing:(UITextField *)sender

{
    //move the main view, so that the keyboard does not hide it.
    if  (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

20

Wypróbuj tę krótką sztuczkę.

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    [self animateTextField: textField up: YES];
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField: textField up: NO];
}

- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
    const int movementDistance = textField.frame.origin.y / 2; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? -movementDistance : movementDistance);

    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

19

Jest tak wiele rozwiązań, ale spędziłem kilka godzin, zanim zacznie działać. Więc umieszczam ten kod tutaj (wystarczy wkleić do projektu, żadne modyfikacje nie muszą):

@interface RegistrationViewController : UIViewController <UITextFieldDelegate>{
    UITextField* activeField;
    UIScrollView *scrollView;
}
@end

- (void)viewDidLoad
{
    [super viewDidLoad];

    scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];

    //scrool view must be under main view - swap it
    UIView* natView = self.view;
    [self setView:scrollView];
    [self.view addSubview:natView];

    CGSize scrollViewContentSize = self.view.frame.size;
    [scrollView setContentSize:scrollViewContentSize];

    [self registerForKeyboardNotifications];
}

- (void)viewDidUnload {
    activeField = nil;
    scrollView = nil;
    [self unregisterForKeyboardNotifications];
    [super viewDidUnload];
}

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShown:)
                                                 name:UIKeyboardWillShowNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];

}

-(void)unregisterForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillShowNotification
                                                  object:nil];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];
}

- (void)keyboardWillShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    CGRect frame = self.view.frame;
    frame.size.height -= kbSize.height;
    CGPoint fOrigin = activeField.frame.origin;
    fOrigin.y -= scrollView.contentOffset.y;
    fOrigin.y += activeField.frame.size.height;
    if (!CGRectContainsPoint(frame, fOrigin) ) {
        CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y + activeField.frame.size.height - frame.size.height);
        [scrollView setContentOffset:scrollPoint animated:YES];
    }
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
     [scrollView setContentOffset:CGPointZero animated:YES];
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    activeField = textField;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    activeField = nil;
}

-(BOOL) textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}

PS: Mam nadzieję, że kod pomoże komuś szybko uzyskać pożądany efekt. (Xcode 4.5)


Cześć Hotjard, dostaję EXE_BAD_ACCESS w [self.view addSubview: natView];
Bala

18

@ user271753

Aby przywrócić widok do oryginalnego, dodaj:

-(BOOL)textFieldShouldReturn:(UITextField *)textField{
   [textField resignFirstResponder];
   [self setViewMovedUp:NO];
   return YES;
}

16

Przesunięcie ramki widoku nie wymaga widoku przewijania. Możesz zmienić ramkę viewcontroller'swidoku, aby cały widok przesunął się w górę na tyle, aby umieścić pierwsze pole tekstowe nad klawiaturą. Kiedy napotkałem ten problem, stworzyłem podklasę UIViewController, która to robi. Zauważa, że ​​na klawiaturze pojawi się powiadomienie i znajdzie widok pierwszego reagującego i (w razie potrzeby) animuje widok główny w górę tak, aby pierwszy odpowiadający znalazł się nad klawiaturą. Gdy klawiatura się chowa, animuje widok z powrotem tam, gdzie była.

Aby użyć tej podklasy, ustaw niestandardowy kontroler widoku na podklasę GMKeyboardVC i dziedziczy ona tę funkcję (upewnij się, że ją zaimplementowałeś viewWillAppeari viewWillDisappearże musi ona wywoływać super). Klasa jest na githubie .


Jaka licencja? Niektóre z twoich plików mają licencję typu open source, a niektóre nie.
jaime

Ostrzeżenie: ten kod nie jest przyjazny dla projektów ARC.
Almo

Po prostu dodajesz opcję kompilacji, aby określić, że są to pliki inne niż ARC, lub możesz przekonwertować ją na ARC i przesłać żądanie ściągnięcia.
progrmr

14

Szybki 4 .

Można łatwo poruszać się w górę iw dół UITextFieldOr UIViewWith UIKeyBoardWithAnimation wprowadź opis zdjęcia tutaj

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var textField: UITextField!
    @IBOutlet var chatView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: .UIKeyboardWillChangeFrame, object: nil)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        textField.resignFirstResponder()
    }

    @objc func keyboardWillChange(notification: NSNotification) {

        let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
        let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
        let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let deltaY = targetFrame.origin.y - curFrame.origin.y
        print("deltaY",deltaY)

        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
            self.chatView.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
        },completion: nil)
    }

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

}

2
Prawie idealnie. Na iPhonie X występuje dziwna luka między klawiaturą a polem tekstowym.
Houman

12

Oto rozwiązanie hakerskie, które wymyśliłem dla konkretnego układu. To rozwiązanie jest podobne do rozwiązania Matta Gallaghera, ponieważ przewija sekcję do widoku. Nadal jestem nowicjuszem w projektowaniu iPhone'ów i nie znam sposobu działania układów. Tak więc ten hack.

Moja implementacja musiała obsługiwać przewijanie po kliknięciu pola, a także przewijanie, gdy użytkownik wybierze następny na klawiaturze.

Miałem UIView o wysokości 775. Elementy sterujące są rozmieszczone zasadniczo w grupach po 3 na dużej przestrzeni. Skończyło się na następującym układzie IB.

UIView -> UIScrollView -> [UI Components]

Nadchodzi hack

Ustawiłem wysokość UIScrollView na 500 jednostek większą niż rzeczywisty układ (1250). Następnie utworzyłem tablicę z absolutnymi pozycjami, do których muszę przewijać, oraz prostą funkcją, aby uzyskać je na podstawie numeru znacznika IB.

static NSInteger stepRange[] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 140, 140, 140, 140, 410
};

NSInteger getScrollPos(NSInteger i) {
    if (i < TXT_FIELD_INDEX_MIN || i > TXT_FIELD_INDEX_MAX) {
        return 0 ;
    return stepRange[i] ;
}

Teraz wystarczy użyć dwóch następujących wierszy kodu w textFieldDidBeginEditing i textFieldShouldReturn (ten ostatni, jeśli tworzysz nawigację po kolejnych polach)

CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
[self.scrollView setContentOffset:point animated:YES] ;

Przykład.

- (void) textFieldDidBeginEditing:(UITextField *)textField
{
    CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
    [self.scrollView setContentOffset:point animated:YES] ;
}


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

    NSInteger nextTag = textField.tag + 1;
    UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];

    if (nextResponder) {
        [nextResponder becomeFirstResponder];
        CGPoint point = CGPointMake(0, getScrollPos(nextTag)) ;
        [self.scrollView setContentOffset:point animated:YES] ;
    }
    else{
        [textField resignFirstResponder];
    }

    return YES ;
}

Ta metoda nie „przewija do tyłu”, jak inne metody. To nie był wymóg. Znów było to dla dość „wysokiego” UIView i nie miałem dni na naukę wewnętrznych mechanizmów układu.


12

Zgodnie z dokumentacją , w iOS 3.0 UITableViewControllerklasa automatycznie zmienia rozmiar i zmienia położenie widoku tabeli, gdy istnieje bezpośrednia edycja pól tekstowych. Myślę, że nie wystarczy umieścić pole tekstowe w polu, UITableViewCelljak niektórzy wskazywali.

Z dokumentów :

Kontroler widoku tabeli obsługuje bezpośrednią edycję wierszy widoku tabeli; jeśli na przykład wiersze mają osadzone pola tekstowe w trybie edycji, przewija edytowany wiersz nad wyświetlaną klawiaturą wirtualną.


Znalazłem ten sam komentarz. Tak to jest prawda. Dziwne jest to, że działa w jednym UITabelViewController, a w drugim nie. Ale nie mogłem znaleźć żadnych różnic w mojej implementacji.
Morpheus78

11

Tutaj znalazłem najprostsze rozwiązanie do obsługi klawiatury.

Musisz po prostu skopiować i wkleić poniższy przykładowy kod i zmienić pole tekstowe lub dowolny widok, który chcesz przenieść w górę.

Krok 1

Wystarczy skopiować i wkleić poniżej dwóch metod w kontrolerze

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];
}

- (void)deregisterFromKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

Krok 2

zarejestruj i wyrejestruj Powiadomienia z klawiatury odpowiednio w metodach viewWillAppear i viewWillDisappear .

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self registerForKeyboardNotifications];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [self deregisterFromKeyboardNotifications];
    [super viewWillDisappear:animated];
}

Krok 3

Nadchodzi część duszy, po prostu zastąp swoje pole tekstowe i zmień wysokość, o ile chcesz przejść do góry.

- (void)keyboardWasShown:(NSNotification *)notification
{
    NSDictionary* info = [notification userInfo];
    CGSize currentKeyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    //you need replace your textfield instance here
    CGPoint textFieldOrigin = self.tokenForPlaceField.frame.origin;
    CGFloat textFieldHeight = self.tokenForPlaceField.frame.size.height;

    CGRect visibleRect = self.view.frame;
    visibleRect.size.height -= currentKeyboardSize.height;

    if (!CGRectContainsPoint(visibleRect, textFieldOrigin))
    {
        //you can add yor desired height how much you want move keypad up, by replacing "textFieldHeight" below

        CGPoint scrollPoint = CGPointMake(0.0, textFieldOrigin.y - visibleRect.size.height  + textFieldHeight); //replace textFieldHeight to currentKeyboardSize.height, if you want to move up with more height
        [self.scrollView setContentOffset:scrollPoint animated:YES];
    }
}

- (void)keyboardWillBeHidden:(NSNotification *)notification
{
    [self.scrollView setContentOffset:CGPointZero animated:YES];
}

Referencje : cóż, proszę docenić tego faceta , który udostępnił to piękne wycięcie kodu, czyste rozwiązanie.

Mam nadzieję, że byłoby to bardzo pomocne komuś tam.


Nie sądzę, że to jest najlepsze. Ithink @Dheeraj VS ma rację: można to zrobić łatwo i automatycznie, jeśli to pole tekstowe znajduje się w komórce tabeli (nawet jeśli table.scrollable = NO). UWAGA : pozycja i rozmiar stołu muszą być rozsądne. np .: - jeśli pozycja y tabeli wynosi 100, liczona od dołu widoku, klawiatura o wysokości 300 nakłada się na cały stół. - jeśli wysokość tabeli = 10, a pole tekstowe w nim musi zostać przewinięte w górę o 100, gdy pojawi się klawiatura, aby było widoczne, to pole tekstowe będzie poza granicami stołu.
samthui7

@ samthui7 Odpowiedź Dheeraj działa tylko wtedy, gdy używasz TableViewController, a nie tylko widoku tabeli. To sprawia, że ​​jest to ograniczenie, które czasem nie jest odpowiednie.
Ben G

10

Szukając dobrego samouczka dla początkujących na ten temat, znalazłem najlepszy samouczek tutaj .

W MIScrollView.hprzykładzie na dole samouczka umieść spację w

@property (nonatomic, retain) id backgroundTapDelegate;

jak widzicie.


Cześć savagenoob, dziękuję za podany link i witamy w Stackoverflow. Spróbuj odpowiedzieć na (przyszłe) pytania i podać jak najwięcej informacji - proste linki są trochę kruche. To powiedziawszy, jeśli odpowiedź jest linkiem do dobrego samouczka, który może zostać przeoczony.
Maarten Bodewes

10

Kiedy UITextFieldjest w UITableViewCellprzewijanie powinno być ustawione automatycznie.

Jeśli tak nie jest, prawdopodobnie przyczyną jest niewłaściwy kod / ustawienie widoku tabeli.

Na przykład, kiedy ponownie załadowałem mój długi stół z jednym UITextFieldna dole w następujący sposób,

-(void) viewWillAppear:(BOOL)animated
{
   [self.tableview reloadData];
}

wtedy moje pole tekstowe u dołu zostało zasłonięte przez klawiaturę, która pojawiła się po kliknięciu wewnątrz pola tekstowego.

Aby to naprawić, musiałem to zrobić -

-(void) viewWillAppear:(BOOL)animated
{
    //add the following line to fix issue
    [super viewWillAppear:animated];
    [self.tableview reloadData];
}

Nie wiem, do czego służy ten kod? Gdy pokazana jest klawiatura, viewWillAppearnie jest wywoływana. I reloadDatanie powoduje, że zasłonięte rzędy stają się widoczne.
Adam Johns


8

Uwaga : ta odpowiedź zakłada, że ​​pole tekstowe znajduje się w scrollView.

Wolę sobie z tym poradzić, używając scrollContentInset i scrollContentOffset zamiast mieszać się z ramkami mojego widoku.

Najpierw posłuchajmy powiadomień z klawiatury

//call this from viewWillAppear
-(void)addKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification
                                               object:nil];
}
//call this from viewWillDisappear
-(void)removeKeyboardNotifications{
    [[NSNotificationCenter default
    Center] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

Następnym krokiem jest zachowanie właściwości reprezentującej bieżącego pierwszego respondera (UITextfield / UITextVIew, która obecnie ma klawiaturę).

Używamy metod delegowania, aby ustawić tę właściwość. Jeśli używasz innego komponentu, będziesz potrzebować czegoś podobnego.

Zauważ, że dla pola tekstowego ustawiamy go w didBeginEditing, a dla textView w shouldBeginEditing. Wynika to z tego, że textViewDidBeginEditing jest wywoływany po UIKeyboardWillShowNotification z jakiegoś powodu.

-(BOOL)textViewShouldBeginEditing:(UITextView * )textView{
    self.currentFirstResponder = textView;
    return YES;
}

-(void)textFieldDidBeginEditing:(UITextField *)textField{
    self.currentFirstResponder = textField;
}

Wreszcie, oto magia

- (void)keyboardWillShow:(NSNotification*)aNotification{
    NSDictionary* info = [aNotification userInfo];
    CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];


    /*if currentFirstResponder is overlayed by the keyboard, move it so it bottom ends where the keyboard begins*/
    if(self.currentFirstResponder){

        //keyboard origin in currentFirstResponderFrame
        CGPoint keyboardOrigin = [self.currentFirstResponder convertPoint:kbFrame.origin fromView:nil];

        float spaceBetweenFirstResponderAndKeyboard = abs(self.currentFirstResponder.frame.size.height-keyboardOrigin.y);

        //only scroll the scrollview if keyboard overlays the first responder
        if(spaceBetweenFirstResponderAndKeyboard>0){
            //if i call setContentOffset:animate:YES it behaves differently, not sure why
            [UIView animateWithDuration:0.25 animations:^{
                [self.scrollView setContentOffset:CGPointMake(0,self.scrollView.contentOffset.y+spaceBetweenFirstResponderAndKeyboard)];
            }];
        }
    }

    //set bottom inset to the keyboard height so you can still scroll the whole content

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbFrame.size.height, 0.0);
    _scrollView.contentInset = contentInsets;
    _scrollView.scrollIndicatorInsets = contentInsets;

}

- (void)keyboardWillHide:(NSNotification*)aNotification{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    _scrollView.contentInset = contentInsets;
    _scrollView.scrollIndicatorInsets = contentInsets;
}

8

To jest rozwiązanie wykorzystujące Swift.

import UIKit

class ExampleViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var scrollView: UIScrollView!

    @IBOutlet var textField1: UITextField!
    @IBOutlet var textField2: UITextField!
    @IBOutlet var textField3: UITextField!
    @IBOutlet var textField4: UITextField!
    @IBOutlet var textField5: UITextField!

    var activeTextField: UITextField!

    // MARK: - View
    override func viewDidLoad() {
        super.viewDidLoad()
        self.textField1.delegate = self
        self.textField2.delegate = self
        self.textField3.delegate = self
        self.textField4.delegate = self
        self.textField5.delegate = self
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.registerForKeyboardNotifications()
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        self.unregisterFromKeyboardNotifications()
    }

    // MARK: - Keyboard

    // Call this method somewhere in your view controller setup code.
    func registerForKeyboardNotifications() {
        let center:  NSNotificationCenter = NSNotificationCenter.defaultCenter()
        center.addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
        center.addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
    }

    func unregisterFromKeyboardNotifications () {
        let center:  NSNotificationCenter = NSNotificationCenter.defaultCenter()
        center.removeObserver(self, name: UIKeyboardDidShowNotification, object: nil)
        center.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
    }

    // Called when the UIKeyboardDidShowNotification is sent.
    func keyboardWasShown (notification: NSNotification) {
        let info : NSDictionary = notification.userInfo!
        let kbSize = (info.objectForKey(UIKeyboardFrameBeginUserInfoKey)?.CGRectValue() as CGRect!).size

        let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
        scrollView.contentInset = contentInsets;
        scrollView.scrollIndicatorInsets = contentInsets;

        // If active text field is hidden by keyboard, scroll it so it's visible
        // Your app might not need or want this behavior.
        var aRect = self.view.frame
        aRect.size.height -= kbSize.height;
        if (!CGRectContainsPoint(aRect, self.activeTextField.frame.origin) ) {
            self.scrollView.scrollRectToVisible(self.activeTextField.frame, animated: true)
        }
    }

    // Called when the UIKeyboardWillHideNotification is sent
    func keyboardWillBeHidden (notification: NSNotification) {
        let contentInsets = UIEdgeInsetsZero;
        scrollView.contentInset = contentInsets;
        scrollView.scrollIndicatorInsets = contentInsets;
    }

    // MARK: -  Text Field

    func textFieldDidBeginEditing(textField: UITextField) {
        self.activeTextField = textField
    }

    func textFieldDidEndEditing(textField: UITextField) {
        self.activeTextField = nil
    }

}

Prawidłowa odpowiedź, ale nie mam problemu przy korzystaniu zarówno z TextField, jak i TextView. Jakaś pomoc?
Thiha Aung

@Thiha Aung, Czy twoje zmienne IBOutlet w kodzie źródłowym są podłączone do IB?
Homam

Tak, oni też są połączeni. Po prostu występuje ten błąd podczas używania UITextView w tym wierszu: if (! CGRectContainsPoint (aRect, self.activeTextField.frame.origin)) {
Thiha Aung

Oznacza self.activeTextField jest zero
Thiha Aung
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.