UIView na klawiaturze podobny do aplikacji iMessage

Obecnie próbuję w zasadzie zaimplementować i dokładnie skopiować aplikację Apples iMessage.

Oznacza to, że potrzebuję UITextView, który jest zadokowany u dołu ekranu i porusza się w górę, gdy staje się firstResponder. - To całkiem proste. Istnieje wiele sposobów na to, a dwa z najczęstszych to oczywiście animowanie widoku w górę lub w dół, jeśli otrzymano powiadomienie. Drugim jest to, aby zrobić to poprzez inputAccessoryView . Niestety niektóre z cechy, które jedno ma, a drugie nie, i wydają się wzajemnie się wykluczać.

Dużym problemem jest rotacja .

Przekopałem się przez mniej więcej siedem różnych projektów Githuba, wszystkie z nich ponownie implementują tę samą funkcjonalność / zachowanie,które próbuję osiągnąć, ale dosłownie wszystkie z nich zawodzą.

HPGrowingTextView na przykład, z którego korzystają oficjalne aplikacje Facebook / FacebookMessenger/(i ewentualnie WhatsApp), jest jednym dużym kawałkiem śmieciowy kod. Weź iDevice, otwórz aplikację Facebook, przejdź do czatu, pop klawiaturę i obróć urządzenie. Jeśli zwrócisz uwagę, zauważysz, że pasek wejściowy przeskakuje lekko i pozostawia puste miejsce między ramką klawiatury a jej własną. Następnie przyjrzyj się implementacji jabłek w iMessage, gdy wyświetlona jest klawiatura. Idealnie.

Poza tym contentOffset i EdgeInset-hacking, z których korzysta Biblioteka HPGrowingTextView, przyprawiają mnie o koszmary.

Więc Ja chciałem to zrobić sam i zacząć od zera.

W tej chwili mam bardzo zgrabną, elegancką i bezhakerową implementację rosnącego UITextView, ale brakuje jednej części.

Elegancka rotacja.

Kiedy po prostu dostosowuję ramki do ich nowych pozycji w metodzie willrotatetointerface orientation: duration:, wszystko kończy się idealnie, ale mam ten sam problem, który ma HPGrowingTextView(patrz aplikacja Facebook). Odrobina przestrzeni między inputview i klawiatura podczas obracania.

Dowiedziałem się, że podczas obracania urządzenia do poziomego, wyświetlana obecnie klawiatura pionowa nie "morph", ale raczej znika (wysyła powiadomienie "willHide") i pojawia się wersja pozioma (wysyłanie powiadomienia "willShow"). Przejście jest bardzo subtelne zanikanie i ewentualnie pewne zmiany rozmiaru.

Ponownie zaimplementowałem swój projekt za pomocą inputAccessoryView, aby zobaczyć, co się wtedy stanie i byłem mile zaskoczony. InputAccessoryView obraca się w idealnej synchronizacji z klawiaturą. Nie ma przestrzeni/przerwy między nimi.

Niestety nie mam jeszcze pomysłu, jak mieć dock inputAccessoryView na dole ekranu i NIE znikać/poruszać się z niego obok klawiatury...

To, czego nie chcę, to rozwiązania hakerskie, takie jak,..."lekkie opuszczenie ramki w układzie tointerface, a następnie przesunięcie jej z powrotem w górę, gdy nie zrobiłem tego... został wezwany."

Znam jeszcze jedną aplikację, która zdołała wdrożyć takie zachowanie i jest to "komunikator Kik".

Czy ktoś ma pomysł, radę lub link, którego jeszcze nie widziałem na ten temat?

Wielkie dzięki!

Uwaga: gdy ten problem zostanie rozwiązany, otworzę projekt źródłowy dla wszystkich, aby zyskać, ponieważ prawie każda implementacja, którą udało mi się znaleźć w ciągu ostatnich kilku dni, jest bałaganem.

Author: pkluz, 2011-12-11

5 answers

Ostatnio napotkałem ten sam problem i musiałem opracować niestandardowe rozwiązanie, ponieważ nie byłem całkowicie zadowolony z dostępnych bibliotek stron trzecich. Podzieliłem tę implementację na własny projekt GitHub:

MessageComposerView

Z kilku prostych testów na iOS 6.1 7 & 8 symulatory obroty wydają się prawidłowo podążać za klawiaturą. Widok będzie również powiększał się wraz z tekstem i zmieniał Rozmiar automatycznie podczas obracania.

MessageComposerView

Możesz użyć bardzo podstawowa init Funkcja tak aby utworzyć ją z szerokością ekranu i domyślną wysokością np.:

self.messageComposerView = [[MessageComposerView alloc] init];
self.messageComposerView.delegate = self;
[self.view addSubview:self.messageComposerView];

Istnieje kilka innych inicjalizatorów, które są również dostępne, aby umożliwić dostosowanie ramki, przesunięcia klawiatury i textview max wysokość. Więcej informacji znajdziesz w readme!

 11
Author: alexgophermix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-03 22:33:39

Udało mi się rozwiązać problem w dość elegancki sposób (myślę,...).

Kod zostanie wydany na Githubie w przyszłym tygodniu i będzie linkowany do tej odpowiedzi.

--

Jak to jest zrobione: wykonałem rotację wybierając inputAccessoryView-sposób jej wykonania.

Nomenklatura:

  1. 'MessageInputView' jest UIView zawierającym mój 'GrowingUITextView' (zawiera również przycisk "Wyślij" i tło obraz).
  2. 'Chatview' to Widok należący do kontrolera Chatview, który wyświetla wszystkie kulki Chatbuble i ma mój 'MessageInputView' zadokowany na dole.
  3. 'keyboardAccessoryView' jest pustym UIView o rozmiarze: CGRect (0,0,0,0).

Musiałem dowiedzieć się, jak mieć MessageInputView trzymać się na ekranie, gdy klawiatura została odrzucona. To była najtrudniejsza część. Zrobiłem to tworząc inny widok (keyboardAccessoryView ) i miał mój GrowingUITextView używać go jako jego inputAccessoryView. Zatrzymałem keyboardAccessoryView , ponieważ będę potrzebował odniesienia do niego później.

Potem przypomniałem sobie kilka rzeczy, które zrobiłem w mojej drugiej próbie (animowanie ramek MessageInputView wokół ekranu za każdym razem, gdy pojawiało się powiadomienie z klawiatury).

Dodałem mój MessageInputView jako subview do mojego ChatView (w bardzo dno). Za każdym razem, gdy jest aktywowana i metody willShow: są wywoływane przez powiadomienie klawiatury, ręcznie animuję ramkę MessageInputView do wyznaczonej pozycji u góry. Po zakończeniu animacji i wykonaniu bloku zakończeń usuwam subview z ChatView i dodaję go do keyboardAccessoryView . Powoduje to wyłączenie kolejnego powiadomienia, ponieważ klawiatura jest ponownie ładowana za każdym razem, gdy ramka/granice inputAccessoryView są zmiana!. Musisz być tego świadomy i odpowiednio sobie z tym poradzić!

Gdy klawiatura ma zostać wyłączona, konwertuję ramkę MessageInputView do układu współrzędnych ChatView i dodaję ją jako podgląd podrzędny. W ten sposób jest on usuwany z mojej keyboardAccessoryView . Następnie zmieniam rozmiar klatki keyboardAccessoryView z powrotem do cgrect (0,0,0,0), ponieważ w przeciwnym razie UIViewAnimationDuration nie będzie pasować! Wtedy pozwalam na zwolnienie klawiatury i niech mój MessageInputView podąża za nim z góry i zadokuje na dole ekranu.

To dość dużo pracy przy bardzo małym zysku.

--

Trzymaj się.

PS: Jeśli ktoś wymyśli jakiś łatwiejszy sposób (perfekcyjnie) daj znać.

 5
Author: pkluz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-12 05:33:55

Oto UITextView podklasa, która działa poprawnie na iOS 9.3.1 i 8.3.1 . Dba o wzrost i kurczenie się z ograniczeniami, utrzymując karetkę zawsze we właściwym miejscu i płynnie animując.

Przyklejanie widoku nad klawiaturą jest banalne, z wieloma rozwiązaniami, które można łatwo znaleźć, więc nie jest zakryte...

Nie mogłem znaleźć żadnych gotowych rozwiązań, które były gotowe do produkcji, więc skończyło się na tym, że pracowałem nad tym od podstaw. Musiałem rozwiązać wiele małych problemów. po drodze.

Komentarze do kodu powinny dać ci wyobrażenie o tym, co się dzieje.

Podzieliłem się tym na mój Github , wkład bardzo mile widziany.

Uwagi

  • nie testowane do obsługi krajobrazu
  • nie testowane na i6+

Demo

Tutaj wpisz opis obrazka

(Po osiągnięciu maksymalnej wysokości element staje się przewijalny. Zapomniałem przeciągnąć demo, ale to działa zgodnie z oczekiwaniami... )

Podklasa

class ruuiDynamicTextView: UITextView {
    var dynamicDelegate: ruuiDynamicTextViewDelegate?
    var minHeight: CGFloat!
    var maxHeight: CGFloat?
    private var contentOffsetCenterY: CGFloat!

    init(frame: CGRect, offset: CGFloat = 0.0) {
        super.init(frame: frame, textContainer: nil)
        minHeight = frame.size.height

        //center first line
        let size = self.sizeThatFits(CGSizeMake(self.bounds.size.width, CGFloat.max))
        contentOffsetCenterY = (-(frame.size.height - size.height * self.zoomScale) / 2.0) + offset

        //listen for text changes
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(textChanged), name: UITextViewTextDidChangeNotification, object: nil)

        //update offsets
        layoutSubviews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        //Use content size if more than min size, compensate for Y offset
        var height = max(self.contentSize.height - (contentOffsetCenterY * 2.0), minHeight)
        var updateContentOffsetY: CGFloat?
        //Max Height
        if maxHeight != nil && height > maxHeight {
            //Cap at maxHeight
            height = maxHeight!
        } else {
            //constrain Y to prevent odd skip and center content to view.
            updateContentOffsetY = contentOffsetCenterY
        }
        //update frame if needed & notify delegate
        if self.frame.size.height != height {
            self.frame.size.height = height
            dynamicDelegate?.dynamicTextViewDidResizeHeight(self, height: height)
        }
        //constrain Y must be done after setting frame
        if updateContentOffsetY != nil {
            self.contentOffset.y = updateContentOffsetY!
        }
    }

    func textChanged() {
        let caretRect = self.caretRectForPosition(self.selectedTextRange!.start)
        let overflow = caretRect.size.height + caretRect.origin.y - (self.contentOffset.y + self.bounds.size.height - self.contentInset.bottom - self.contentInset.top)
        if overflow > 0 {
            //Fix wrong offset when cursor jumps to next line un explisitly
            let seekEndY = self.contentSize.height - self.bounds.size.height
            if self.contentOffset.y != seekEndY {
                self.contentOffset.y = seekEndY
            }
        }
    }
}

protocol ruuiDynamicTextViewDelegate {
    func dynamicTextViewDidResizeHeight(textview: ruuiDynamicTextView, height: CGFloat)
}
 2
Author: Andres Canella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-22 05:57:14

Ostatnio napisałem post na blogu o tym dokładnie problem, który opisałeś i jak go rozwiązać w krótki i elegancki sposób za pomocą powiadomień klawiaturowych, ale bez użycia inputAccessoryView. I chociaż to pytanie jest dość stare, ten temat jest nadal aktualny, więc oto link do posta: Synchronizacja animacji obrotu między klawiaturą a załączonym widokiem

Jeśli nie chcesz zanurzyć się w długim wyjaśnieniu opisanym w poście na blogu, oto krótki opis przykład kodu:

Podstawową zasadą jest używanie tej samej metody , której używają wszyscy-obserwowanie powiadomień klawiaturowych do animowania załączonego widoku w górę iw dół. Ale oprócz tego musisz anulować te animacje, gdy powiadomienia klawiatury są wywoływane w wyniku zmiany orientacji interfejsu.

Przykład obracania bez anulowania animacji przy zmianie orientacji interfejsu:

Przykład rotacji z anulowaniem animacji na zmiana orientacji interfejsu:

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

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

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

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

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    self.animatingRotation = YES;
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    self.animatingRotation = NO;
}

- (void)adjustViewForKeyboardNotification:(NSNotification *)notification {
    NSDictionary *notificationInfo = [notification userInfo];

    // Get the end frame of the keyboard in screen coordinates.
    CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

    // Convert the finalKeyboardFrame to view coordinates to take into account any rotation
    // factors applied to the window’s contents as a result of interface orientation changes.
    finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];

    // Calculate new position of the commentBar
    CGRect commentBarFrame = self.commentBar.frame;
    commentBarFrame.origin.y = finalKeyboardFrame.origin.y - commentBarFrame.size.height;

    // Update tableView height.
    CGRect tableViewFrame = self.tableView.frame;
    tableViewFrame.size.height = commentBarFrame.origin.y;

    if (!self.animatingRotation) {
        // Get the animation curve and duration
        UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
        NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

        // Animate view size synchronously with the appearance of the keyboard. 
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:animationDuration];
        [UIView setAnimationCurve:animationCurve];
        [UIView setAnimationBeginsFromCurrentState:YES];

        self.commentBar.frame = commentBarFrame;
        self.tableView.frame = tableViewFrame;

        [UIView commitAnimations];
    } else {
        self.commentBar.frame = commentBarFrame;
        self.tableView.frame = tableViewFrame;
    }
}
 0
Author: smnh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-06 10:19:15

Jak rozwiązać ten problem dla mnie:

Mam ChatViewController i FooterViewController jako UIContainerView. Ponadto mam contentView outlet w FooterViewController. Wtedy w ChatViewController mam:

override func becomeFirstResponder() -> Bool {
    return true
}

override var inputAccessoryView: UIView? {
    if let childViewController = childViewControllers.first as? FooterViewController {
        childViewController.contentView.removeFromSuperview()
        return childViewController.contentView
    }
    return nil
}

Tutaj wpisz opis obrazka

Innym sposobem jest tworzenie widoku programowo i zwracanie jako inputAccessoryView.

 0
Author: ChikabuZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-27 17:16:52