Dapatkan paling top UIViewController

192

Sepertinya saya tidak bisa mendapatkan yang teratas UIViewControllertanpa akses ke a UINavigationController. Inilah yang saya miliki sejauh ini:

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

Namun, sepertinya tidak melakukan apa-apa. The keyWindowdan rootViewControllertampaknya nilai-nilai non-nihil juga, sehingga chaining opsional seharusnya tidak menjadi masalah.

CATATAN: Adalah ide yang buruk untuk melakukan sesuatu seperti ini. Itu merusak pola MVC.

Zoyt
sumber
Berikut adalah salah satu solusi alternatif yang tersedia stackoverflow.com/a/39994115/1872233
iDevAmit

Jawaban:

287

presentViewControllermenunjukkan pengontrol tampilan. Itu tidak mengembalikan view controller. Jika Anda tidak menggunakan UINavigationController, Anda mungkin mencari presentedViewControllerdan Anda harus mulai dari root dan beralih melalui tampilan yang disajikan.

if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

Untuk Swift 3+:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

Untuk iOS 13+

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

if var topController = keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

// topController should now be your topmost view controller
}
rickerbh
sumber
1
Bisakah seseorang menjelaskan loop sementara? Bagi saya, sepertinya tidak ada yang bisa dilewati; Saya bahkan tidak yakin mengapa ini mengkompilasi.
Profesor Tom
15
@ProfessorTom Loop berlanjut selama topController.presentedViewControllermengembalikan sesuatu (yaitu, pengontrol memiliki pengontrol anak yang disajikan). Ini while letuntuk menegakkan fakta yang topController.presentedViewControllerharus mengembalikan sesuatu. Jika mengembalikan nil (yaitu, ini controller tidak memiliki anak-anak yang disajikan), maka itu akan berhenti berulang. Dalam tubuh loop, ia menetapkan kembali anak sebagai arus topController, dan loop lagi, turun hierarki pengontrol tampilan. Itu dapat menugaskan kembali topControllerkarena itu vardalam ifpernyataan luar .
rickerbh
1
Terima kasih. Saya belum dapat menemukan contoh online dari while let. Tentu saja ada banyakif let contoh yang bisa ditemukan.
Profesor Tom
1
The let x = somethingThatCouldBeNilsintaks adalah trik super berguna untuk digunakan di mana saja nilai kebenaran / kondisi dapat digunakan. Jika kami tidak menggunakannya di sini, kami harus menetapkan nilai secara eksplisit, lalu menguji untuk melihat apakah itu benar-benar ada. Saya pikir ini sangat ringkas dan ekspresif.
rickerbh
1
Saya akrab dengan triknya, hanya sedikit lebih sulit untuk dipikirkan sementara loop - yang mana saya telah menemukan kelangkaan contoh - terutama yang ini.
Profesor Tom
272

memiliki ekstensi ini

Cepat 2. *

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(presented)
        }
        return controller
    }
}

Cepat 3

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(controller: presented)
        }
        return controller
    }
}

Anda dapat menggunakan ini di mana saja pada pengontrol Anda

if let topController = UIApplication.topViewController() {

}
DLende
sumber
1
Terima kasih atas tip ekstensi Anda :)
Thein
4
Saya berusaha membuat edit penting untuk jawaban ini, tetapi ditolak (Saya tidak tahu mengapa dan alasan templat yang diberikan tidak masuk akal): Penting untuk memeriksa apakah nav.visibleViewController nil sebelum menggunakannya dalam rekursif panggil (seperti bagaimana tab.selectedViewController diperiksa) karena jika tidak, jika nihil, Anda akan masuk ke loop tak terbatas rekursif.
Ethan G
@ EthanG Menurut pemahaman saya, jika nav.visibleViewController adalah nil, fungsi akan mengembalikan nil (turun ke yang terakhir return). Bagaimana itu bisa masuk ke loop tak terbatas?
Desmond DAI
3
Saya pikir akan lebih logis untuk menjadikan ini sebagai fungsi statis dari UIViewController
Leszek Zarna
1
Pemeriksaan 'disajikanViewController' mungkin harus didahulukan jika Anda ingin menangkap pengontrol tampilan yang disajikan secara
moderat
65

Untuk swift 4/5 + untuk mendapatkan viewController paling atas

// MARK: UIApplication extensions

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)

        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)

        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

Cara Penggunaan

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}
Hardik Thakkar
sumber
2
Solusi brilian. Terima kasih!
Andrey M.
2
'keyWindow' sudah tidak digunakan lagi di iOS 13.0.
rs7
2
'keyWindow' sudah tidak digunakan lagi di iOS 13.0 stackoverflow.com/a/57899013/4514671
Rebeloper
19
extension UIWindow {

    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        if let navigationController = vc as? UINavigationController,
            let visibleController = navigationController.visibleViewController  {
            return UIWindow.getVisibleViewControllerFrom( vc: visibleController )
        } else if let tabBarController = vc as? UITabBarController,
            let selectedTabController = tabBarController.selectedViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: selectedTabController )
        } else {
            if let presentedViewController = vc.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
            } else {
                return vc
            }
        }
    }
}

Pemakaian:

if let topController = window.visibleViewController() {
    println(topController)
}
Bobj-C
sumber
solusi ini terlihat sangat menjanjikan, namun saya mencoba menjalankan ini untuk mendapatkan view controller yang saya aktifkan ketika saya menerima pemberitahuan push dan itu melempar kesalahan nihil padareturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike
@ Mike Anda hanya perlu menggunakan disajikanViewController, bukan disajikanViewController. disajikanViewController
allaire
@ allaire Jika Anda memang menampilkan pengontrol tampilan modal di atas pengontrol tampilan modal maka Anda perlu .presentedViewController.presentedViewController, atau tidak?
Baran Emre
6

Berdasarkan jawaban Dianz, versi Objective-C

- (UIViewController *) topViewController {
   UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
   if ([baseVC isKindOfClass:[UINavigationController class]]) {
       return ((UINavigationController *)baseVC).visibleViewController;
   }

   if ([baseVC isKindOfClass:[UITabBarController class]]) {
       UIViewController *selectedTVC = ((UITabBarController*)baseVC).selectedViewController;
       if (selectedTVC) {
           return selectedTVC;
       }
   }

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}
Tibidabo
sumber
Tidak akan bekerja untuk UINavigationController di UITabBarController. itu akan mengembalikan UINavigationController, harus mengembalikan topController di navigasi macet.
Mike.R
Tnx Tnx Tnx Bro
reza_khalafi
6

Saya menyukai jawaban @ dianz , dan ini adalah versi cepatnya. Ini pada dasarnya hal yang sama tetapi dia tidak memiliki kurung kurawal dan beberapa nama sintaks / variabel / metode telah berubah. Jadi begini!

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

Penggunaannya masih sama persis:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}
Ponyboy47
sumber
6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 Saya melakukan beberapa tes pada jawaban dan komentar di situs ini. Bagi saya, berikut ini berfungsi

extension UIViewController {
    func topMostViewController() -> UIViewController {

        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }

        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }

        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }

        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

Lalu, dapatkan viewController teratas dengan:

UIApplication.shared.topMostViewController()
Albert Zou
sumber
5

Gunakan kode ini untuk menemukan sebagian besar UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}
Bibin Joseph
sumber
2
Apa bedanya dengan jawaban rickerbh?
ElectroBuddha
5

Sedikit variasi pada @AlberZou menggunakan variabel yang dikomputasi daripada fungsi

extension UIViewController {
  var topMostViewController : UIViewController {

    if let presented = self.presentedViewController {
      return presented.topMostViewController
    }

    if let navigation = self as? UINavigationController {
      return navigation.visibleViewController?.topMostViewController ?? navigation
    }

    if let tab = self as? UITabBarController {
      return tab.selectedViewController?.topMostViewController ?? tab
    }

    return self
  }
}

extension UIApplication {
  var topMostViewController : UIViewController? {
    return self.keyWindow?.rootViewController?.topMostViewController
  }
}

Lalu berkata

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}
Ryan Heitner
sumber
4

Berdasarkan Bob -c di atas:

Swift 3.0

extension UIWindow {


    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController  = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {

        if vc.isKind(of: UINavigationController.self) {

            let navigationController = vc as! UINavigationController
            return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)

        } else if vc.isKind(of: UITabBarController.self) {

            let tabBarController = vc as! UITabBarController
            return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)

        } else {

            if let presentedViewController = vc.presentedViewController {

                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)

            } else {

                return vc;
            }
        }
    }
}
Daniel
sumber
4

Terlalu banyak rasa tetapi tidak ada yang berulang yang diuraikan. Digabungkan dari yang sebelumnya:

     func topMostController() -> UIViewController? {
        var from = UIApplication.shared.keyWindow?.rootViewController
        while (from != nil) {
            if let to = (from as? UITabBarController)?.selectedViewController {
                from = to
            } else if let to = (from as? UINavigationController)?.visibleViewController {
                from = to
            } else if let to = from?.presentedViewController {
                from = to
            } else {
                break
            }
        }
        return from
    }
Jaime Agudo
sumber
2

Anda dapat mendefinisikan variabel UIViewController di AppDelegate, dan di setiap viewWillAppear atur variabel menjadi self (tetapi jawaban dianz adalah jawaban terbaik.)

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}
Arash Jamshidi
sumber
1
terima kasih banyak itu berfungsi dengan baik untuk saya sebagai solusi lain ketika mencoba untuk mendapatkan navigasiControll itu kembali nihil jadi saya tidak dapat mendorong vc baru
Amr Angry
Pastikan currentVC didefinisikan sebagai referensi lemah, atau Anda akan memiliki kebocoran memori.
bubuxu
2

Untuk menemukan viewController yang terlihat di Swift 3

if let viewControllers = window?.rootViewController?.childViewControllers {

     let prefs = UserDefaults.standard

     if viewControllers[viewControllers.count - 1] is ABCController{
        print("[ABCController] is visible")

     }
}

Kode ini menemukan yang terakhir ditambahkan atau pengendali aktif terakhir terlihat.

Ini telah saya gunakan di AppDelegate untuk menemukan Pengontrol tampilan aktif

Prateekro
sumber
2
import UIKit

extension UIApplication {

    // MARK: Choose keyWindow as per your choice
    var currentWindow: UIWindow? {
        connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first
    }

    // MARK: Choose keyWindow as per your choice
    var keyWindow: UIWindow? {
        UIApplication.shared.windows.first { $0.isKeyWindow }
    }

    class func topMostViewController(base: UIViewController? = UIApplication.shared.currentWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topMostViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController, top.view.window != nil {
                return topMostViewController(base: top)
            } else if let selected = tab.selectedViewController {
                return topMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topMostViewController(base: presented)
        }
        return base
    }
}
Rakesh Kusuma
sumber
Penggunaan ambigu dari 'visibleViewController'
Omar N Shamali
1

Di mana Anda memasukkan kode?

Saya mencoba kode Anda di demo saya, saya tahu, jika Anda memasukkan kode

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

akan gagal, karena jendela kunci telah diatur.

Tapi saya meletakkan kode Anda di beberapa view controller

override func viewDidLoad() {

Itu hanya bekerja.

Tinyfool
sumber
Itu tidak ada didFinishLaunchingWithOptions. Saya hanya perlu ini untuk berbagai keperluan debug.
Zoyt
1

Dalam kasus yang sangat jarang, dengan segmen khusus, pengontrol tampilan terbanyak atas tidak ada di tumpukan navigasi atau pengontrol tab bar atau disajikan, tetapi pandangannya dimasukkan ke bagian atas tampilan bawah kunci utama windown.

Dalam situasi seperti itu, perlu untuk memeriksa apakah UIApplication.shared.keyWindow.subviews.last == self.viewmenentukan apakah pengendali tampilan saat ini adalah yang paling top.

BabyPanda
sumber
1

Bagi siapa pun yang mencari solusi cepat 5 / iOS 13+ ( keywindowsudah usang sejak iOS 13)

extension UIApplication {

    class func getTopMostViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        } else {
            return nil
        }
    }
}
Virendra
sumber
Bagaimana saya menggunakannya?
Chris Comas
Sebut saja seperti ini. UIApplication.getTopMostViewController()di dalam ViewController Anda. @ChrisComas
Virendra
0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }
Sarang
sumber
0

Solusi terbaik bagi saya adalah ekstensi dengan fungsi. Buat file cepat dengan ekstensi ini

Pertama adalah ekstensi UIWindow :

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

di dalam file itu tambahkan fungsi

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

Dan jika Anda ingin menggunakannya, Anda dapat memanggilnya di mana saja. Contoh :

  override func viewDidLoad() {
    super.viewDidLoad()
      if let topVC = visibleViewController() {
             //show some label or text field 
    }
}

Kode file seperti ini :

import UIKit

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}
tBug
sumber