Próba załadowania widoku kontrolera widoku podczas zwalniania… UISearchController

80

Mam kod, który tworzy UISearchController' in my UIVIew'sviewDidLoad`.

 self.resultSearchController = ({
        let controller = UISearchController(searchResultsController: nil)
        controller.searchResultsUpdater = self
        controller.searchBar.delegate = self
        controller.dimsBackgroundDuringPresentation = false
        controller.searchBar.sizeToFit()
        controller.hidesNavigationBarDuringPresentation = false //prevent search bar from moving
        controller.searchBar.placeholder = "Search for song"

        self.myTableView.tableHeaderView = controller.searchBar

        return controller

    })()

Zaraz po zakończeniu tego zamknięcia w konsoli pojawi się to ostrzeżenie:

Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UISearchController: 0x154d39700>)

Nie rozumiem tego, co robię źle. To podobne pytanie nie jest tak naprawdę moją sytuacją (przynajmniej nie sądzę). Co się dzieje?

MortalMan
źródło
xkcd.com/583 Działa dobrze, jeśli wrzucę to do mojej tabeli VC viewDidLoad(). Zalecamy a) w tym całą listę źródeł VC oraz b) upewnienie się, że błąd rzeczywiście występuje tam, gdzie myślisz, że jest.
BaseZen
Zrób też więcej badań, takich jak: stackoverflow.com/questions/31006045/… który ma ten sam błąd
BaseZen
Zrobiłem więc szybki projekt, zrobiłem wszystko programowo, bez scenorysów, i nie mam problemu, czy to jest problem storyboardu, który masz, być może, nie wiem, ale zakładam, że używasz scenorysów, prawda? Kiedy mówię programowo, mam na myśli brak stalówek, scenorysów, cały kod i działa dobrze
Larry Pickles
@BaseZen Ustawiłem punkt przerwania przed })()i po })(). Błąd jest zgłaszany po zakończeniu zamykania. Mam, a UIViewControllernie tableViewController.
MortalMan
@Larcerax Mam jedną scenorys. Zawiera tylko kontroler nawigacji i kontroler UIViewController (są one połączone)
MortalMan

Odpowiedzi:

119

Widok UISearchControllera musi zostać usunięty z jego superviewu przed cofnięciem przydziału. (chyba to błąd)

Cel C...

-(void)dealloc { 
    [searchController.view removeFromSuperview]; // It works!
}

Swift 3 ...

deinit {
    self.searchController.view.removeFromSuperview()
}

Zmagałem się z tym problemem przez kilka tygodni. ^^

JJH
źródło
1
To naprawdę dziwne ... Ale mnie też to załatwiło. Myślę, że to rzeczywiście błąd.
Mihai Fratu
22
Miałem ten sam problem z UISearchControlerprzydzielaniem w -viewDidLoad. To zdecydowanie błąd - jeśli UISearchControllerzostanie zwolniony przed załadowaniem widoku, pojawi się to ostrzeżenie. Jeśli kliknę w pole wyszukiwania (w ten sposób ładuję widok), nie pojawia się. Więc w moim deallocdzwonię [self.searchController loadViewIfNeeded](nowość w iOS 9),
Leehro
4
Komentarz @ Leehro jest dla mnie odpowiedzią. wyszło jakoif #available(iOS 9.0, *) { self.searchController?.loadViewIfNeeded() }
Tim
6
Dodałbym do komentarza @ Leehro, że nie ma potrzeby tego robić w dealloc, zamiast tego możesz wykonać loadViewIfNeeded w viewDidLoad.
Clafou
Dzięki @Leehro i @Clafou ... dodanie wywołania [self.searchController loadViewIfNeeded];(Obj-C) to odpowiedź, która rozwiązała problem w moim kodzie.
andrewbuilder
36

Rozwiązany! To była prosta poprawka. Zmieniłem ten kod

class ViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {

    var resultSearchController = UISearchController()

do tego:

 class ViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {

    var resultSearchController: UISearchController!

To rozwiązuje problem.

MortalMan
źródło
1
Zmieniłem to na to, co miałeś na myśli. :-) W każdym razie dlatego uwzględnienie całego źródła w pytaniu jest lepsze, ale znalezienie go samodzielnie jest najlepsze ;-)
BaseZen
Miałem też to irytujące ostrzeżenie i podążając za twoją odpowiedzią, naprawiłem wszystko. Ale nie wiem, dlaczego! jest potrzebny zamiast przydzielania nowego kontrolera UISearchController ... czy mógłbyś mi wyjaśnić?
Strzelec A
Nie jestem pewien, też chciałbym wyjaśnienia.
MortalMan,
Jeśli zmienię z var resultSearchController = UISearchController () na var resultSearchController: UISearchController! Otrzymuję błąd krytyczny: nieoczekiwanie znaleziono zero podczas rozpakowywania wartości opcjonalnej w noOfRowInSection. Mam dużą liczbę tablic, czy to powoduje błąd? zasugeruj mi.
Pawriwes
Zorientowałem się, że zamiast robić delegata i źródło danych ze scenorysu, napisałem w kodzie i problem został rozwiązany.
Pawriwes
20

Oto wersja Swift, która działała dla mnie (podobna do odpowiedzi JJH):

deinit{
    if let superView = resultSearchController.view.superview
    {
        superView.removeFromSuperview()
    }
}
nijm
źródło
@alex umieściłem go na końcu kontrolera widoku, który inicjuje resultSearchController.
nijm
2
Tak naprawdę nie POTRZEBUJESZ, if letponieważ .removeFromSuperView()nic nie zrobisz, jeślisuperview == nil
NSGangster
11
class SampleClass: UITableViewController, UISearchBarDelegate {

private let searchController =  UISearchController(searchResultsController: nil)

 override func viewDidLoad() {
        super.viewDidLoad()

        searchController.loadViewIfNeeded() // Add this line before accessing searchController
 }

}
harsh_v
źródło
10

Łącząc kilka rozwiązań, udało mi się uruchomić moje, dodając linie do viewDidLoad przed pełnym skonfigurowaniem UISearchController:

override func viewDidLoad() {
    super.viewDidLoad()
    self.navigationItem.rightBarButtonItem = self.editButtonItem()

    if #available(iOS 9.0, *) {
        self.resultSearchController.loadViewIfNeeded()// iOS 9
    } else {
        // Fallback on earlier versions
        let _ = self.resultSearchController.view          // iOS 8
    }
    self.resultSearchController = ({
        let controller = UISearchController(searchResultsController: nil)
        controller.searchResultsUpdater = self
        controller.dimsBackgroundDuringPresentation = false
        controller.searchBar.sizeToFit()

        self.tableView.tableHeaderView = controller.searchBar

        return controller
    })()

    self.tableView.reloadData()

}
Derek
źródło
Próbowałem też każde inne rozwiązanie podane tutaj, a nikt nie tłumić ostrzeżenie (choć kontroler wyszukiwania robi pracy w czasie pracy); to zrobiło to. Dziękuję Ci!
Nicolas Miari
To również mi pomogło, ale musiałem dodać tę linię po zainicjowaniu UISearchController. self.searchController = ({ let controller = UISearchController(searchResultsController: nil) controller.searchResultsUpdater = self controller.dimsBackgroundDuringPresentation = false controller.searchBar.delegate = self definesPresentationContext = true controller.searchBar.sizeToFit() return controller })() potem self.searchController.loadViewIfNeeded()
yuzer
Dlaczego musimy inicjalizować w bloku?
code4latte
7

W Swift2 otrzymałem ten sam komunikat o błędzie z powodu oczywistego błędu:

let alertController = UIAlertController(title: "Oops",
    message:"bla.", preferredStyle: UIAlertControllerStyle.Alert)

alertController.addAction(UIAlertAction(title: "Ok", 
     style: UIAlertActionStyle.Default,handler: nil))

self.presentViewController(alertController, animated: true, completion: nil)

Z powodu popełnionego przeze mnie głupiego błędu kopiowania nie dołączyłem wiersza self.presentViewController. To spowodowało ten sam błąd.

Vincent
źródło
7

W wersji Swift 2.2, która działała dla mnie

deinit {
    self.searchController?.view.removeFromSuperview()
}

Myślę, że to pomocne!

Milos Mandic
źródło
2

To nie jest błąd. Wygląda na to, że musisz unikać tworzenia ViewControllerów bez ich prezentowania. Więc po SomeViewController()lub let variable: SomeViewControllermusisz zadzwonić do czegoś takiego self.presentViewController(yourViewController ...etc). Jeśli tego nie zrobisz, otrzymasz to ostrzeżenie, gdy ten kontroler widoku zostanie przydzielony.

Nikolai Ischuk
źródło
2

Mój tak działa

func initSearchControl(){

        searchController = UISearchController(searchResultsController: nil)

        if #available(iOS 9.0, *) {
            searchController.loadViewIfNeeded()
        } else {
            let _ = self.searchController.view
        }

        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        definesPresentationContext = true
        tableView.tableHeaderView = searchController.searchBar
        searchController.searchBar.sizeToFit()
    }

searchController.loadViewIfNeeded () rozwiązuje problem, ale musisz go wywołać po zainicjowaniu searchController

mehmetsen80
źródło
2

Utworzenie kontrolera wyszukiwania w programie viewDidLoad()i ustawienie jego paska wyszukiwania jako widoku tytułu elementu nawigacji nie tworzy silnego odniesienia do kontrolera wyszukiwania, dlatego jest on cofnięty.

Więc zamiast tego robić:

override func viewDidLoad() {
    super.viewDidLoad()
    // Create search controller
    let searchController = UISearchController(searchResultsController: nil)
    // Add search bar to navigation bar
    navigationItem.titleView = searchController.searchBar
    // Size search bar
    searchController.searchBar.sizeToFit()
}

Powinieneś to zrobić:

var searchController: UISearchController!

override func viewDidLoad() {
    super.viewDidLoad()
    // Create search controller
    searchController = UISearchController(searchResultsController: nil)
    // Add search bar to navigation bar
    navigationItem.titleView = searchController.searchBar
    // Size search bar
    searchController.searchBar.sizeToFit()
}
Niels
źródło
1

Użyłem odpowiedzi Dereka, ale musiałem ją nieco zmienić. Podana odpowiedź uległa awarii, ponieważ wywołanie loadViewIfNeeded () miało miejsce przed zdefiniowaniem resultSearchController. (Moja deklaracja brzmiała

var resultSearchController: UISearchController!

). Więc po prostu go przeniosłem i zadziałało.

Jeśli całkowicie pominąłem połączenie, błąd pozostał, więc jestem pewien, że jest to istotna część odpowiedzi. Nie mogłem go przetestować na iOS 8.

Michael L. Mehr
źródło
1

Wygląda na to, że widok jest ładowany z opóźnieniem, jeśli przydzieliłeś kontroler i nigdy go nie pokazałeś, widok nie jest załadowany. W takim przypadku, jeśli kontroler zostanie zwolniony, otrzymasz to ostrzeżenie. możesz to raz pokazać, wywołać metodę loadViewIfNeed () lub użyć 'let _ = controller.view', aby wymusić załadowanie widoku, aby uniknąć tego ostrzeżenia.

david
źródło
w iOS 8 możesz użyć tylko „let _ = controller.view”.
david
0

Trochę spóźniłem się na imprezę, ale oto moje rozwiązanie:

var resultSearchController: UISearchController!

override func viewDidLoad()
{
    super.viewDidLoad()

    self.resultSearchController = ({
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        searchController.searchBar.sizeToFit()
        return searchController
    })()

    self.tableView.tableHeaderView = self.resultSearchController.searchBar
    self.tableView.reloadData()
}

Mam nadzieję, że to zadziała dla ciebie.

titusmagnus
źródło
Czym się to różni od bezpośredniego inicjowania i konfigurowania; Mam na myśli bez użycia składni bloku?
code4latte