Scenariusz: użytkownik naciska przycisk na kontrolerze widoku. Kontroler widoku jest najwyżej (oczywiście) na stosie nawigacji. Stuknięcie wywołuje metodę klasy narzędziowej wywoływaną w innej klasie. Zdarza się tam coś złego i chcę tam wyświetlić alert, zanim formant powróci do kontrolera widoku.
+ (void)myUtilityMethod {
// do stuff
// something bad happened, display an alert.
}
Było to możliwe dzięki UIAlertView
(ale być może nie do końca właściwym).
W tym przypadku, jak można przedstawić UIAlertController
, tam w prawo myUtilityMethod
?
źródło
W WWDC zatrzymałem się w jednym z laboratoriów i zadałem inżynierowi Apple to samo pytanie: „Jaka była najlepsza praktyka wyświetlania
UIAlertController
?” Powiedział, że często zadawali sobie to pytanie, a my żartowaliśmy, że powinni byli o tym porozmawiać. Powiedział, że wewnętrznie Apple tworzyUIWindow
przezroczysty,UIViewController
a następnie prezentujeUIAlertController
na nim. Zasadniczo, co zawiera odpowiedź Dylana Bettermana.Ale nie chciałem używać podklasy,
UIAlertController
ponieważ wymagałoby to zmiany kodu w mojej aplikacji. Tak więc za pomocą powiązanego obiektu stworzyłem kategorię,UIAlertController
która zapewniashow
metodę w Objective-C.Oto odpowiedni kod:
Oto przykładowe użycie:
To,
UIWindow
co zostanie stworzone, zostanie zniszczone poUIAlertController
zwolnieniu, ponieważ jest to jedyny obiekt, który zachowujeUIWindow
. Ale jeśli przypiszeszUIAlertController
właściwość lub sprawisz, że jej liczba zatrzymań zwiększy się, uzyskując dostęp do alertu w jednym z bloków akcji,UIWindow
ikona pozostanie na ekranie, blokując interfejs użytkownika. Zobacz przykładowy kod użycia powyżej, aby uniknąć w przypadku konieczności uzyskania dostępuUITextField
.Zrobiłem repozytorium GitHub z projektem testowym: FFGlobalAlertController
źródło
viewDidDisappear:
kategorii wygląda jak zły pomysł. Zasadniczo konkurujesz z implementacją frameworkaviewDidDisappear:
. Na razie może być w porządku, ale jeśli Apple zdecyduje się wdrożyć tę metodę w przyszłości, nie ma możliwości, aby ją wywołać (tj. Nie ma analogiisuper
tych punktów do podstawowej implementacji metody z implementacji kategorii) .prefersStatusBarHidden
ipreferredStatusBarStyle
bez dodatkowej podklasy?Szybki
Cel C
źródło
W Swift 2.2 możesz wykonać następujące czynności:
I Swift 3.0:
źródło
Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!
.visibleViewController
właściwość w dowolnym momencie, aby zobaczyć, z którego kontrolera ma zostać wyświetlony alert. Sprawdź dokumentyCałkiem ogólny
UIAlertController
extension
dla wszystkich przypadkówUINavigationController
i / lubUITabBarController
. Działa również, jeśli na ekranie jest teraz modalne VC.Stosowanie:
To jest rozszerzenie:
źródło
UI
klasy, która przechowuje (słaby!)currentVC
TypuUIViewController
.I miećBaseViewController
który dziedziczyUIViewController
i zestawUI.currentVC
doself
naviewDidAppear
potemnil
naviewWillDisappear
. Wszystkie moje kontrolery widoku w aplikacji dziedzicząBaseViewController
. W ten sposób, jeśli coś maszUI.currentVC
(to nie jestnil
...) - zdecydowanie nie jest to środek animacji prezentacji i możesz poprosić o prezentacjęUIAlertController
.else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }
Poprawiając odpowiedź agilityvision , musisz utworzyć okno z przezroczystym kontrolerem widoku głównego i stamtąd wyświetlić widok alertu.
Jednak dopóki masz akcję kontrolera alertów, nie musisz utrzymywać odniesienia do okna . Jako ostatni krok bloku procedury obsługi wystarczy ukryć okno w ramach zadania czyszczenia. Dzięki odwołaniu do okna w bloku modułu obsługi powstaje tymczasowe odwołanie cykliczne, które zostanie zerwane po zwolnieniu kontrolera alertów.
źródło
Poniższe rozwiązanie nie działało, mimo że wyglądało dość obiecująco we wszystkich wersjach. To rozwiązanie generuje OSTRZEŻENIE .
Ostrzeżenie: Spróbuj przedstawić, którego widok nie znajduje się w hierarchii okien!
https://stackoverflow.com/a/34487871/2369867 => To wygląda wtedy obiecująco. Ale to było nie w
Swift 3
. Odpowiadam na to w Swift 3 i nie jest to przykładowy szablon.Jest to raczej w pełni funkcjonalny kod po wklejeniu do dowolnej funkcji.
Jest to testowane i działa kod w Swift 3.
źródło
UIWindow
okna, w przeciwnym razie okno zostanie zwolnione i zniknie wkrótce po wyjściu z zakresu.Oto odpowiedź mitycznego kodera jako rozszerzenia, przetestowanego i działającego w Swift 4:
Przykładowe użycie:
źródło
Działa to w Swift dla normalnych kontrolerów widoku, a nawet jeśli na ekranie jest kontroler nawigacji:
źródło
UIWindow
nie odpowiada. Ma to coś wspólnego zwindowLevel
prawdopodobnie. Jak mogę sprawić, by był responsywny?alertWindow
TOnil
po zakończeniu z nim.Dodając do odpowiedzi Zev (i wracając do Celu-C), możesz spotkać się z sytuacją, w której kontroler widoku root prezentuje inne VC poprzez segue lub coś innego. Wywołanie prezentacjiViewController na root VC zajmie się tym:
To rozwiązało problem, który miałem, gdy główny VC przekonywał do innego VC, i zamiast prezentować kontroler alertów, wydano ostrzeżenie podobne do opisanych powyżej:
Nie testowałem tego, ale może to być również konieczne, jeśli Twój root VC okazuje się być kontrolerem nawigacyjnym.
źródło
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UIAlertController: 0x15cd4afe0>)
rootViewController.presentedViewController
if, jeśli nie jest zero, w przeciwnym razie użyjrootViewController
. Aby uzyskać w pełni ogólne rozwiązanie, może być konieczne przejście łańcuchapresentedViewController
s, aby dostać się dotopmost
VCOdpowiedź @ agilityvision przetłumaczona na Swift4 / iOS11. Nie używałem zlokalizowanych ciągów, ale możesz to łatwo zmienić:
źródło
window.backgroundColor = UIColor.clear
naprawiłem to.viewController.view.backgroundColor = UIColor.clear
nie wydaje się konieczne.UIAlertController
podklasą:The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
developer.apple.com/documentation/uikit/uialertcontrollerUtwórz rozszerzenie jak w odpowiedzi Aviel Gross. Tutaj masz rozszerzenie Objective-C.
Tutaj masz plik nagłówka * .h
I wdrożenie: * .m
Używasz tego rozszerzenia w pliku implementacji w następujący sposób:
źródło
Prześlij moją odpowiedź, ponieważ te dwa wątki nie są oznaczone jako duplikaty ...
Teraz
UIViewController
jest to część łańcucha odpowiedzi, możesz zrobić coś takiego:źródło
Odpowiedź Zeva Eisenberga jest prosta i jednoznaczna, ale nie zawsze działa i może nie powieść się z tym komunikatem ostrzegawczym:
Wynika to z faktu, że rootViewController systemu Windows nie znajduje się na górze prezentowanych widoków. Aby to naprawić, musimy przejść do łańcucha prezentacji, jak pokazano w moim kodzie rozszerzenia UIAlertController napisanym w Swift 3:
Aktualizacje 15.09.2017:
Przetestowano i potwierdzono, że powyższa logika nadal działa świetnie w nowo dostępnym ziarnie iOS 11 GM. Metoda najczęściej wybierana przez agilityvision nie: jednak widok alertu przedstawiony w nowej wersji
UIWindow
znajduje się pod klawiaturą i potencjalnie uniemożliwia użytkownikowi dotykanie przycisków. Wynika to z faktu, że w iOS 11 wszystkie poziomy okna wyższe niż okna klawiatury są obniżone do poziomu poniżej.Jednym z artefaktów prezentacji
keyWindow
jest jednak animacja zsuwania się klawiatury po wyświetleniu alertu i przesuwania się ponownie po odrzuceniu alertu. Jeśli chcesz, aby klawiatura pozostała tam podczas prezentacji, możesz spróbować przedstawić ją z poziomu samego górnego okna, jak pokazano w poniższym kodzie:Jedyną niezbyt dużą częścią powyższego kodu jest to, że sprawdza nazwę klasy,
UIRemoteKeyboardWindow
aby upewnić się, że możemy ją również dołączyć. Niemniej jednak powyższy kod działa świetnie w ziarnach iOS 9, 10 i 11 GM, z odpowiednim kolorem odcienia i bez przesuwanych artefaktów klawiatury.źródło
Swift 4+
Rozwiązanie, którego używam od lat, bez żadnych problemów. Przede wszystkim rozszerzam,
UIWindow
aby zobaczyć, że jest widocznyViewController. UWAGA : jeśli używasz niestandardowych klas kolekcji * (takich jak menu boczne), powinieneś dodać moduł obsługi dla tego przypadku w następującym rozszerzeniu. Po zdobyciu najwyżej kontrolera widoku można go łatwo przedstawićUIAlertController
tak jakUIAlertView
.źródło
W przypadku iOS 13, w oparciu o odpowiedzi mitycznego kodera i bobbyrehm :
W iOS 13, jeśli tworzysz własne okno do wyświetlania alertu, musisz mieć silne odniesienie do tego okna, w przeciwnym razie alert nie zostanie wyświetlony, ponieważ okno zostanie natychmiast zwolnione, gdy odniesienie wyjdzie z zakresu.
Ponadto po odrzuceniu alertu należy ponownie ustawić wartość zerową, aby usunąć okno, aby umożliwić interakcję użytkownika w głównym oknie poniżej.
Możesz utworzyć
UIViewController
podklasę, która zawiera logikę zarządzania pamięcią okna:Możesz użyć tego tak, jak jest, lub jeśli chcesz mieć wygodną metodę
UIAlertController
, możesz wrzucić ją do rozszerzenia:źródło
dismiss
bezpośrednio do WindowAlertPresentationControlleralert.presentingViewController?.dismiss(animated: true, completion: nil)
Skrócony sposób przedstawienia ostrzeżenia w Celu C:
Gdzie
alertController
jest twójUIAlertController
przedmiotUWAGA: Musisz także upewnić się, że klasa pomocników się powiększyła
UIViewController
źródło
Jeśli ktoś jest zainteresowany, stworzyłem wersję Swift 3 odpowiedzi @agilityvision. Kod:
źródło
Dzięki temu możesz łatwo przedstawić swój alert w ten sposób
Należy zauważyć, że jeśli aktualnie wyświetlany jest UIAlertController,
UIApplication.topMostViewController
zwróciUIAlertController
. Prezentowanie na wierzchuUIAlertController
ma dziwne zachowanie i należy go unikać. W związku z tym należy albo ręcznie to sprawdzić!(UIApplication.topMostViewController is UIAlertController)
przed przedstawieniem, albo dodaćelse if
skrzynkę, aby zwrócić zero, jeśliself is UIAlertController
źródło
Możesz wysłać bieżący widok lub kontroler jako parametr:
źródło
Kevin Sliech zapewnił świetne rozwiązanie.
Teraz używam poniższego kodu w mojej głównej podklasie UIViewController.
Jedną małą zmianą, którą wprowadziłem, było sprawdzenie, czy najlepszy kontroler prezentacji nie jest zwykłym kontrolerem UIView. Jeśli nie, to musi być jakiś VC, który przedstawia zwykły VC. W ten sposób zwracamy VC, który jest prezentowany zamiast tego.
Wydaje mi się, że wszystkie sprawdzają się do tej pory w moich testach.
Dziękuję Kevin!
źródło
Oprócz świetnych odpowiedzi ( agilityvision , adib , malhal ). Aby osiągnąć zachowanie w kolejce, jak w starych dobrych UIAlertViews (unikaj nakładania się okien alarmowych), użyj tego bloku, aby obserwować dostępność na poziomie okna:
Kompletny przykład:
Pozwoli to uniknąć nakładania się okien alertów. Tej samej metody można użyć do oddzielenia i umieszczenia kontrolerów widoku kolejek dla dowolnej liczby warstw okien.
źródło
Próbowałem wszystkiego, co wspomniano, ale bezskutecznie. Metoda zastosowana w Swift 3.0:
źródło
Niektóre z tych odpowiedzi działały tylko częściowo dla mnie, rozwiązaniem było połączenie metody w klasie następnej w AppDelegate. Działa na iPadzie, w widokach UITabBarController, w UINavigationController, en podczas prezentacji modów. Testowane na iOS 10 i 13.
Stosowanie:
źródło
Obsługa scen iOS13 (podczas korzystania z UIWindowScene)
źródło
Możesz spróbować zaimplementować kategorię za
UIViewController
pomocą mehtod typu- (void)presentErrorMessage;
I, a wewnątrz tej metody zaimplementujesz UIAlertController, a następnie ją zaprezentujeszself
. Niż w kodzie klienta będziesz mieć coś takiego:[myViewController presentErrorMessage];
W ten sposób unikniesz niepotrzebnych parametrów i ostrzeżeń o braku wyświetlania widoku w hierarchii okien.
źródło
myViewController
w kodzie, w którym dzieje się coś złego. Jest to metoda użyteczności, która nie wie nic o kontrolerze widoku, który ją wywołał.UIAlertView
doprowadziła mnie do złamania tej zasady w kilku miejscach.Istnieją 2 podejścia, których możesz użyć:
-Używaj
UIAlertView
zamiast tego „UIActionSheet” (niezalecane, ponieważ jest przestarzałe w iOS 8, ale działa teraz)- Jakoś pamiętaj ostatni kontroler widoku, który jest prezentowany. Oto przykład.
Stosowanie:
źródło
Używam tego kodu z niewielkimi osobistymi zmianami w mojej klasie AppDelegate
źródło
Wydaje się działać:
źródło
utwórz klasę pomocnika AlertWindow, a następnie użyj jako
źródło